diff options
Diffstat (limited to 'src')
1400 files changed, 86107 insertions, 66317 deletions
diff --git a/src/ArchiveDomain.cxx b/src/ArchiveDomain.cxx deleted file mode 100644 index 2beeebcbb..000000000 --- a/src/ArchiveDomain.cxx +++ /dev/null @@ -1,23 +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 "ArchiveDomain.hxx" -#include "util/Domain.hxx" - -const Domain archive_domain("archive"); diff --git a/src/ArchiveDomain.hxx b/src/ArchiveDomain.hxx deleted file mode 100644 index 7bc8a223e..000000000 --- a/src/ArchiveDomain.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_DOMAIN_HXX -#define MPD_ARCHIVE_DOMAIN_HXX - -extern const class Domain archive_domain; - -#endif diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx deleted file mode 100644 index 4bdba62ab..000000000 --- a/src/ArchiveFile.hxx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_FILE_HXX -#define MPD_ARCHIVE_FILE_HXX - -class Mutex; -class Cond; -class Error; - -class ArchiveFile { -public: - const struct archive_plugin &plugin; - - ArchiveFile(const struct archive_plugin &_plugin) - :plugin(_plugin) {} - -protected: - /** - * Use Close() instead of delete. - */ - ~ArchiveFile() {} - -public: - virtual void Close() = 0; - - /** - * Visit all entries inside this archive. - */ - virtual void Visit(ArchiveVisitor &visitor) = 0; - - /** - * Opens an InputStream of a file within the archive. - * - * @param path the path within the archive - */ - virtual InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) = 0; -}; - -#endif diff --git a/src/ArchiveList.cxx b/src/ArchiveList.cxx deleted file mode 100644 index f4a530506..000000000 --- a/src/ArchiveList.cxx +++ /dev/null @@ -1,90 +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 "ArchiveList.hxx" -#include "ArchivePlugin.hxx" -#include "util/StringUtil.hxx" -#include "archive/Bzip2ArchivePlugin.hxx" -#include "archive/Iso9660ArchivePlugin.hxx" -#include "archive/ZzipArchivePlugin.hxx" -#include "util/Macros.hxx" - -#include <string.h> - -const struct archive_plugin *const archive_plugins[] = { -#ifdef HAVE_BZ2 - &bz2_archive_plugin, -#endif -#ifdef HAVE_ZZIP - &zzip_archive_plugin, -#endif -#ifdef HAVE_ISO9660 - &iso9660_archive_plugin, -#endif - nullptr -}; - -/** which plugins have been initialized successfully? */ -static bool archive_plugins_enabled[ARRAY_SIZE(archive_plugins) - 1]; - -#define archive_plugins_for_each_enabled(plugin) \ - archive_plugins_for_each(plugin) \ - if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins]) - -const struct archive_plugin * -archive_plugin_from_suffix(const char *suffix) -{ - if (suffix == nullptr) - return nullptr; - - archive_plugins_for_each_enabled(plugin) - if (plugin->suffixes != nullptr && - string_array_contains(plugin->suffixes, suffix)) - return plugin; - - return nullptr; -} - -const struct archive_plugin * -archive_plugin_from_name(const char *name) -{ - archive_plugins_for_each_enabled(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return nullptr; -} - -void archive_plugin_init_all(void) -{ - for (unsigned i = 0; archive_plugins[i] != nullptr; ++i) { - const struct archive_plugin *plugin = archive_plugins[i]; - if (plugin->init == nullptr || archive_plugins[i]->init()) - archive_plugins_enabled[i] = true; - } -} - -void archive_plugin_deinit_all(void) -{ - archive_plugins_for_each_enabled(plugin) - if (plugin->finish != nullptr) - plugin->finish(); -} - diff --git a/src/ArchiveList.hxx b/src/ArchiveList.hxx deleted file mode 100644 index cbf159b2f..000000000 --- a/src/ArchiveList.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_LIST_HXX -#define MPD_ARCHIVE_LIST_HXX - -struct archive_plugin; - -extern const struct archive_plugin *const archive_plugins[]; - -#define archive_plugins_for_each(plugin) \ - for (const struct archive_plugin *plugin, \ - *const*archive_plugin_iterator = &archive_plugins[0]; \ - (plugin = *archive_plugin_iterator) != nullptr; \ - ++archive_plugin_iterator) - -/* interface for using plugins */ - -const struct archive_plugin * -archive_plugin_from_suffix(const char *suffix); - -const struct archive_plugin * -archive_plugin_from_name(const char *name); - -/* this is where we "load" all the "plugins" ;-) */ -void archive_plugin_init_all(void); - -/* this is where we "unload" all the "plugins" */ -void archive_plugin_deinit_all(void); - -#endif diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx deleted file mode 100644 index 7a93c136a..000000000 --- a/src/ArchiveLookup.cxx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "ArchiveLookup.hxx" -#include "ArchiveDomain.hxx" -#include "Log.hxx" - -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -gcc_pure -static char * -FindSlash(char *p, size_t i) -{ - for (; i > 0; --i) - if (p[i] == '/') - return p + i; - - return nullptr; -} - -gcc_pure -static const char * -FindSuffix(const char *p, const char *i) -{ - for (; i > p; --i) { - if (*i == '.') - return i + 1; - } - - return nullptr; -} - -bool -archive_lookup(char *pathname, const char **archive, - const char **inpath, const char **suffix) -{ - size_t idx = strlen(pathname); - - char *slash = nullptr; - - while (true) { - //try to stat if its real directory - struct stat st_info; - if (stat(pathname, &st_info) == -1) { - if (errno != ENOTDIR) { - FormatErrno(archive_domain, - "Failed to stat %s", pathname); - return false; - } - } else { - //is something found ins original path (is not an archive) - if (slash == nullptr) - return false; - - //its a file ? - if (S_ISREG(st_info.st_mode)) { - //so the upper should be file - *archive = pathname; - *inpath = slash + 1; - - //try to get suffix - *suffix = FindSuffix(pathname, slash - 1); - return true; - } else { - FormatError(archive_domain, - "Not a regular file: %s", - pathname); - return false; - } - } - - //find one dir up - if (slash != nullptr) - *slash = '/'; - - slash = FindSlash(pathname, idx - 1); - if (slash == nullptr) - return false; - - *slash = 0; - idx = slash - pathname; - } -} - diff --git a/src/ArchiveLookup.hxx b/src/ArchiveLookup.hxx deleted file mode 100644 index 0c4da9c93..000000000 --- a/src/ArchiveLookup.hxx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_LOOKUP_HXX -#define MPD_ARCHIVE_LOOKUP_HXX - -/** - * - * archive_lookup is used to determine if part of pathname refers to an regular - * file (archive). If so then its also used to split pathname into archive file - * and path used to locate file in archive. It also returns suffix of the file. - * How it works: - * We do stat of the parent of input pathname as long as we find an regular file - * Normally this should never happen. When routine returns true pathname modified - * and split into archive, inpath and suffix. Otherwise nothing happens - * - * For example: - * - * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3 - * is split into archive: /music/path/Talco.zip - * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3 - * and suffix: zip - */ -bool -archive_lookup(char *pathname, const char **archive, - const char **inpath, const char **suffix); - -#endif - diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx deleted file mode 100644 index 05085fb33..000000000 --- a/src/ArchivePlugin.cxx +++ /dev/null @@ -1,39 +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 "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "util/Error.hxx" - -#include <assert.h> - -ArchiveFile * -archive_file_open(const struct archive_plugin *plugin, const char *path, - Error &error) -{ - assert(plugin != nullptr); - assert(plugin->open != nullptr); - assert(path != nullptr); - - ArchiveFile *file = plugin->open(path, error); - assert((file == nullptr) == error.IsDefined()); - - return file; -} diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx deleted file mode 100644 index 6439c7242..000000000 --- a/src/ArchivePlugin.hxx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_PLUGIN_HXX -#define MPD_ARCHIVE_PLUGIN_HXX - -struct InputStream; -class ArchiveFile; -class ArchiveVisitor; -class Error; - -struct archive_plugin { - const char *name; - - /** - * optional, set this to nullptr if the archive plugin doesn't - * have/need one this must false if there is an error and - * true otherwise - */ - bool (*init)(void); - - /** - * optional, set this to nullptr if the archive plugin doesn't - * have/need one - */ - void (*finish)(void); - - /** - * tryes to open archive file and associates handle with archive - * returns pointer to handle used is all operations with this archive - * or nullptr when opening fails - */ - ArchiveFile *(*open)(const char *path_fs, Error &error); - - /** - * suffixes handled by this plugin. - * last element in these arrays must always be a nullptr - */ - const char *const*suffixes; -}; - -ArchiveFile * -archive_file_open(const struct archive_plugin *plugin, const char *path, - Error &error); - -#endif diff --git a/src/ArchiveVisitor.hxx b/src/ArchiveVisitor.hxx deleted file mode 100644 index e951cb5e9..000000000 --- a/src/ArchiveVisitor.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_VISITOR_HXX -#define MPD_ARCHIVE_VISITOR_HXX - -class ArchiveVisitor { -public: - virtual void VisitArchiveEntry(const char *path_utf8) = 0; -}; - -#endif diff --git a/src/AudioConfig.cxx b/src/AudioConfig.cxx index a8fc7aab3..d54f59e17 100644 --- a/src/AudioConfig.cxx +++ b/src/AudioConfig.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 @@ -21,9 +21,9 @@ #include "AudioConfig.hxx" #include "AudioFormat.hxx" #include "AudioParser.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "util/Error.hxx" #include "system/FatalError.hxx" diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx index ebe202974..471e60e51 100644 --- a/src/AudioConfig.hxx +++ b/src/AudioConfig.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/AudioFormat.cxx b/src/AudioFormat.cxx index 04636c1e2..edfb9d8fe 100644 --- a/src/AudioFormat.cxx +++ b/src/AudioFormat.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 diff --git a/src/AudioFormat.hxx b/src/AudioFormat.hxx index fae7625ea..0937ab8ae 100644 --- a/src/AudioFormat.hxx +++ b/src/AudioFormat.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/AudioParser.cxx b/src/AudioParser.cxx index a21dcafa7..74bb04abc 100644 --- a/src/AudioParser.cxx +++ b/src/AudioParser.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 diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx index cb6eb3ca0..07ad7cb4a 100644 --- a/src/AudioParser.hxx +++ b/src/AudioParser.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/AvahiPoll.cxx b/src/AvahiPoll.cxx deleted file mode 100644 index 0d5a43dad..000000000 --- a/src/AvahiPoll.cxx +++ /dev/null @@ -1,151 +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 "AvahiPoll.hxx" -#include "event/Loop.hxx" -#include "event/SocketMonitor.hxx" -#include "event/TimeoutMonitor.hxx" - -static unsigned -FromAvahiWatchEvent(AvahiWatchEvent e) -{ - return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) | - (e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) | - (e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) | - (e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0); -} - -static AvahiWatchEvent -ToAvahiWatchEvent(unsigned e) -{ - return AvahiWatchEvent((e & SocketMonitor::READ ? AVAHI_WATCH_IN : 0) | - (e & SocketMonitor::WRITE ? AVAHI_WATCH_OUT : 0) | - (e & SocketMonitor::ERROR ? AVAHI_WATCH_ERR : 0) | - (e & SocketMonitor::HANGUP ? AVAHI_WATCH_HUP : 0)); -} - -struct AvahiWatch final : private SocketMonitor { -private: - const AvahiWatchCallback callback; - void *const userdata; - - AvahiWatchEvent received; - -public: - AvahiWatch(int _fd, AvahiWatchEvent _event, - AvahiWatchCallback _callback, void *_userdata, - EventLoop &_loop) - :SocketMonitor(_fd, _loop), - callback(_callback), userdata(_userdata), - received(AvahiWatchEvent(0)) { - Schedule(FromAvahiWatchEvent(_event)); - } - - ~AvahiWatch() { - Steal(); - } - - static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) { - w->Schedule(FromAvahiWatchEvent(event)); - } - - static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) { - return w->received; - } - - static void WatchFree(AvahiWatch *w) { - delete w; - } - -protected: - virtual bool OnSocketReady(unsigned flags) { - received = ToAvahiWatchEvent(flags); - callback(this, Get(), received, userdata); - received = AvahiWatchEvent(0); - return true; - } -}; - -static constexpr unsigned -TimevalToMS(const timeval &tv) -{ - return tv.tv_sec * 1000 + (tv.tv_usec + 500) / 1000; -} - -struct AvahiTimeout final : private TimeoutMonitor { -private: - const AvahiTimeoutCallback callback; - void *const userdata; - -public: - AvahiTimeout(const struct timeval *tv, - AvahiTimeoutCallback _callback, void *_userdata, - EventLoop &_loop) - :TimeoutMonitor(_loop), - callback(_callback), userdata(_userdata) { - if (tv != nullptr) - Schedule(TimevalToMS(*tv)); - } - - static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) { - if (tv != nullptr) - t->Schedule(TimevalToMS(*tv)); - else - t->Cancel(); - } - - static void TimeoutFree(AvahiTimeout *t) { - delete t; - } - -protected: - virtual void OnTimeout() { - callback(this, userdata); - } -}; - -MyAvahiPoll::MyAvahiPoll(EventLoop &_loop):event_loop(_loop) -{ - watch_new = WatchNew; - watch_update = AvahiWatch::WatchUpdate; - watch_get_events = AvahiWatch::WatchGetEvents; - watch_free = AvahiWatch::WatchFree; - timeout_new = TimeoutNew; - timeout_update = AvahiTimeout::TimeoutUpdate; - timeout_free = AvahiTimeout::TimeoutFree; -} - -AvahiWatch * -MyAvahiPoll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event, - AvahiWatchCallback callback, void *userdata) { - const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; - - return new AvahiWatch(fd, event, callback, userdata, - poll.event_loop); -} - -AvahiTimeout * -MyAvahiPoll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv, - AvahiTimeoutCallback callback, void *userdata) { - const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; - - return new AvahiTimeout(tv, callback, userdata, - poll.event_loop); -} diff --git a/src/AvahiPoll.hxx b/src/AvahiPoll.hxx deleted file mode 100644 index 850205c46..000000000 --- a/src/AvahiPoll.hxx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AVAHI_POLL_HXX -#define MPD_AVAHI_POLL_HXX - -#include "check.h" -#include "Compiler.h" - -#include <avahi-common/watch.h> - -class EventLoop; - -class MyAvahiPoll final : public AvahiPoll { - EventLoop &event_loop; - -public: - MyAvahiPoll(EventLoop &_loop); - -private: - static AvahiWatch *WatchNew(const AvahiPoll *api, int fd, - AvahiWatchEvent event, - AvahiWatchCallback callback, - void *userdata); - - static AvahiTimeout *TimeoutNew(const AvahiPoll *api, - const struct timeval *tv, - AvahiTimeoutCallback callback, - void *userdata); -}; - -#endif diff --git a/src/CheckAudioFormat.cxx b/src/CheckAudioFormat.cxx index 1f3ef4925..03e67e07e 100644 --- a/src/CheckAudioFormat.cxx +++ b/src/CheckAudioFormat.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 diff --git a/src/CheckAudioFormat.hxx b/src/CheckAudioFormat.hxx index df952adc7..67bd88a61 100644 --- a/src/CheckAudioFormat.hxx +++ b/src/CheckAudioFormat.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/Client.cxx b/src/Client.cxx deleted file mode 100644 index 928747897..000000000 --- a/src/Client.cxx +++ /dev/null @@ -1,24 +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 "ClientInternal.hxx" -#include "util/Domain.hxx" - -const Domain client_domain("client"); diff --git a/src/Client.hxx b/src/Client.hxx deleted file mode 100644 index f0bc6b0f7..000000000 --- a/src/Client.hxx +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_H -#define MPD_CLIENT_H - -#include "check.h" -#include "ClientMessage.hxx" -#include "command/CommandListBuilder.hxx" -#include "event/FullyBufferedSocket.hxx" -#include "event/TimeoutMonitor.hxx" -#include "Compiler.h" - -#include <set> -#include <string> -#include <list> - -#include <stddef.h> -#include <stdarg.h> - -struct sockaddr; -class EventLoop; -struct Partition; - -class Client final : private FullyBufferedSocket, TimeoutMonitor { -public: - Partition &partition; - struct playlist &playlist; - struct PlayerControl &player_control; - - unsigned permission; - - /** the uid of the client process, or -1 if unknown */ - int uid; - - CommandListBuilder cmd_list; - - unsigned int num; /* client number */ - - /** is this client waiting for an "idle" response? */ - bool idle_waiting; - - /** idle flags pending on this client, to be sent as soon as - the client enters "idle" */ - unsigned idle_flags; - - /** idle flags that the client wants to receive */ - unsigned idle_subscriptions; - - /** - * A list of channel names this client is subscribed to. - */ - std::set<std::string> subscriptions; - - /** - * The number of subscriptions in #subscriptions. Used to - * limit the number of subscriptions. - */ - unsigned num_subscriptions; - - /** - * A list of messages this client has received. - */ - std::list<ClientMessage> messages; - - Client(EventLoop &loop, Partition &partition, - int fd, int uid, int num); - - bool IsConnected() const { - return FullyBufferedSocket::IsDefined(); - } - - gcc_pure - bool IsExpired() const { - return !FullyBufferedSocket::IsDefined(); - } - - void Close(); - void SetExpired(); - - using FullyBufferedSocket::Write; - - /** - * returns the uid of the client process, or a negative value - * if the uid is unknown - */ - int GetUID() const { - return uid; - } - - /** - * Is this client running on the same machine, connected with - * a local (UNIX domain) socket? - */ - bool IsLocal() const { - return uid > 0; - } - - unsigned GetPermission() const { - return permission; - } - - void SetPermission(unsigned _permission) { - permission = _permission; - } - - /** - * Send "idle" response to this client. - */ - void IdleNotify(); - void IdleAdd(unsigned flags); - bool IdleWait(unsigned flags); - - enum class SubscribeResult { - /** success */ - OK, - - /** invalid channel name */ - INVALID, - - /** already subscribed to this channel */ - ALREADY, - - /** too many subscriptions */ - FULL, - }; - - gcc_pure - bool IsSubscribed(const char *channel_name) const { - return subscriptions.find(channel_name) != subscriptions.end(); - } - - SubscribeResult Subscribe(const char *channel); - bool Unsubscribe(const char *channel); - void UnsubscribeAll(); - bool PushMessage(const ClientMessage &msg); - -private: - /* virtual methods from class BufferedSocket */ - virtual InputResult OnSocketInput(void *data, size_t length) override; - virtual void OnSocketError(Error &&error) override; - virtual void OnSocketClosed() override; - - /* virtual methods from class TimeoutMonitor */ - virtual void OnTimeout() override; -}; - -void client_manager_init(void); - -void -client_new(EventLoop &loop, Partition &partition, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); - -/** - * Write a C string to the client. - */ -void client_puts(Client &client, const char *s); - -/** - * Write a printf-like formatted string to the client. - */ -void client_vprintf(Client &client, const char *fmt, va_list args); - -/** - * Write a printf-like formatted string to the client. - */ -gcc_printf(2,3) -void -client_printf(Client &client, const char *fmt, ...); - -#endif diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx deleted file mode 100644 index 6c86057a1..000000000 --- a/src/ClientEvent.cxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ClientInternal.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -void -Client::OnSocketError(Error &&error) -{ - FormatError(error, "error on client %d", num); - - SetExpired(); -} - -void -Client::OnSocketClosed() -{ - SetExpired(); -} diff --git a/src/ClientExpire.cxx b/src/ClientExpire.cxx deleted file mode 100644 index 5954bba8b..000000000 --- a/src/ClientExpire.cxx +++ /dev/null @@ -1,43 +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 "ClientInternal.hxx" -#include "Log.hxx" - -void -Client::SetExpired() -{ - if (IsExpired()) - return; - - FullyBufferedSocket::Close(); - TimeoutMonitor::Schedule(0); -} - -void -Client::OnTimeout() -{ - if (!IsExpired()) { - assert(!idle_waiting); - FormatDebug(client_domain, "[%u] timeout", num); - } - - Close(); -} diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx deleted file mode 100644 index 382b76083..000000000 --- a/src/ClientFile.cxx +++ /dev/null @@ -1,70 +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 "ClientFile.hxx" -#include "Client.hxx" -#include "protocol/Ack.hxx" -#include "fs/Path.hxx" -#include "fs/FileSystem.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <sys/stat.h> -#include <sys/types.h> -#include <errno.h> -#include <unistd.h> - -bool -client_allow_file(const Client &client, Path path_fs, Error &error) -{ -#ifdef WIN32 - (void)client; - (void)path_fs; - - error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); - return false; -#else - const int uid = client.GetUID(); - if (uid >= 0 && (uid_t)uid == geteuid()) - /* always allow access if user runs his own MPD - instance */ - return true; - - if (uid <= 0) { - /* unauthenticated client */ - error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); - return false; - } - - struct stat st; - if (!StatFile(path_fs, st)) { - error.SetErrno(); - return false; - } - - if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { - /* client is not owner */ - error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); - return false; - } - - return true; -#endif -} diff --git a/src/ClientFile.hxx b/src/ClientFile.hxx deleted file mode 100644 index b06fbf212..000000000 --- a/src/ClientFile.hxx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_FILE_HXX -#define MPD_CLIENT_FILE_HXX - -class Client; -class Path; -class Error; - -/** - * Is this client allowed to use the specified local file? - * - * Note that this function is vulnerable to timing/symlink attacks. - * We cannot fix this as long as there are plugins that open a file by - * its name, and not by file descriptor / callbacks. - * - * @param path_fs the absolute path name in filesystem encoding - * @return true if access is allowed - */ -bool -client_allow_file(const Client &client, Path path_fs, Error &error); - -#endif diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx deleted file mode 100644 index e79f3430b..000000000 --- a/src/ClientGlobal.cxx +++ /dev/null @@ -1,46 +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 "ClientInternal.hxx" -#include "ClientList.hxx" -#include "ConfigGlobal.hxx" - -#define CLIENT_TIMEOUT_DEFAULT (60) -#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) -#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) - -int client_timeout; -size_t client_max_command_list_size; -size_t client_max_output_buffer_size; - -void client_manager_init(void) -{ - client_timeout = config_get_positive(CONF_CONN_TIMEOUT, - CLIENT_TIMEOUT_DEFAULT); - client_max_command_list_size = - config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, - CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) - * 1024; - - client_max_output_buffer_size = - config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, - CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) - * 1024; -} diff --git a/src/ClientIdle.cxx b/src/ClientIdle.cxx deleted file mode 100644 index f9778a645..000000000 --- a/src/ClientIdle.cxx +++ /dev/null @@ -1,75 +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 "ClientInternal.hxx" -#include "Idle.hxx" - -#include <assert.h> - -void -Client::IdleNotify() -{ - assert(idle_waiting); - assert(idle_flags != 0); - - unsigned flags = idle_flags; - idle_flags = 0; - idle_waiting = false; - - const char *const*idle_names = idle_get_names(); - for (unsigned i = 0; idle_names[i]; ++i) { - if (flags & (1 << i) & idle_subscriptions) - client_printf(*this, "changed: %s\n", - idle_names[i]); - } - - client_puts(*this, "OK\n"); - - TimeoutMonitor::ScheduleSeconds(client_timeout); -} - -void -Client::IdleAdd(unsigned flags) -{ - if (IsExpired()) - return; - - idle_flags |= flags; - if (idle_waiting && (idle_flags & idle_subscriptions)) - IdleNotify(); -} - -bool -Client::IdleWait(unsigned flags) -{ - assert(!idle_waiting); - - idle_waiting = true; - idle_subscriptions = flags; - - if (idle_flags & idle_subscriptions) { - IdleNotify(); - return true; - } else { - /* disable timeouts while in "idle" */ - TimeoutMonitor::Cancel(); - return false; - } -} diff --git a/src/ClientInternal.hxx b/src/ClientInternal.hxx deleted file mode 100644 index c64310093..000000000 --- a/src/ClientInternal.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_INTERNAL_HXX -#define MPD_CLIENT_INTERNAL_HXX - -#include "check.h" -#include "Client.hxx" -#include "command/CommandResult.hxx" - -static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16; -static constexpr unsigned CLIENT_MAX_MESSAGES = 64; - -extern const class Domain client_domain; - -extern int client_timeout; -extern size_t client_max_command_list_size; -extern size_t client_max_output_buffer_size; - -CommandResult -client_process_line(Client &client, char *line); - -#endif diff --git a/src/ClientList.cxx b/src/ClientList.cxx deleted file mode 100644 index 37e6f1289..000000000 --- a/src/ClientList.cxx +++ /dev/null @@ -1,56 +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 "ClientList.hxx" -#include "ClientInternal.hxx" - -#include <algorithm> - -#include <assert.h> - -void -ClientList::Remove(Client &client) -{ - assert(size > 0); - assert(!list.empty()); - - auto i = std::find(list.begin(), list.end(), &client); - assert(i != list.end()); - list.erase(i); - --size; -} - -void -ClientList::CloseAll() -{ - while (!list.empty()) - list.front()->Close(); - - assert(size == 0); -} - -void -ClientList::IdleAdd(unsigned flags) -{ - assert(flags != 0); - - for (const auto &client : list) - client->IdleAdd(flags); -} diff --git a/src/ClientList.hxx b/src/ClientList.hxx deleted file mode 100644 index 8c6b15755..000000000 --- a/src/ClientList.hxx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_LIST_HXX -#define MPD_CLIENT_LIST_HXX - -#include <list> - -class Client; - -class ClientList { - const unsigned max_size; - - unsigned size; - std::list<Client *> list; - -public: - ClientList(unsigned _max_size) - :max_size(_max_size), size(0) {} - ~ClientList() { - CloseAll(); - } - - std::list<Client *>::iterator begin() { - return list.begin(); - } - - std::list<Client *>::iterator end() { - return list.end(); - } - - bool IsFull() const { - return size >= max_size; - } - - void Add(Client &client) { - list.push_front(&client); - ++size; - } - - void Remove(Client &client); - - void CloseAll(); - - void IdleAdd(unsigned flags); -}; - -#endif diff --git a/src/ClientMessage.cxx b/src/ClientMessage.cxx deleted file mode 100644 index 4b124c6ca..000000000 --- a/src/ClientMessage.cxx +++ /dev/null @@ -1,41 +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 "ClientMessage.hxx" -#include "util/CharUtil.hxx" -#include "Compiler.h" - -gcc_const -static bool -valid_channel_char(const char ch) -{ - return IsAlphaNumericASCII(ch) || - ch == '_' || ch == '-' || ch == '.' || ch == ':'; -} - -bool -client_message_valid_channel_name(const char *name) -{ - do { - if (!valid_channel_char(*name)) - return false; - } while (*++name != 0); - - return true; -} diff --git a/src/ClientMessage.hxx b/src/ClientMessage.hxx deleted file mode 100644 index 4579e523e..000000000 --- a/src/ClientMessage.hxx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CLIENT_MESSAGE_H -#define MPD_CLIENT_MESSAGE_H - -#include "Compiler.h" - -#include <string> - -/** - * A client-to-client message. - */ -class ClientMessage { - std::string channel, message; - -public: - template<typename T, typename U> - ClientMessage(T &&_channel, U &&_message) - :channel(std::forward<T>(_channel)), - message(std::forward<U>(_message)) {} - - const char *GetChannel() const { - return channel.c_str(); - } - - const char *GetMessage() const { - return message.c_str(); - } -}; - -gcc_pure -bool -client_message_valid_channel_name(const char *name); - -#endif diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx deleted file mode 100644 index e84887072..000000000 --- a/src/ClientNew.cxx +++ /dev/null @@ -1,126 +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 "ClientInternal.hxx" -#include "ClientList.hxx" -#include "Partition.hxx" -#include "Instance.hxx" -#include "system/fd_util.h" -#include "system/Resolver.hxx" -#include "Permission.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <sys/types.h> -#ifdef WIN32 -#include <winsock2.h> -#else -#include <sys/socket.h> -#endif -#include <unistd.h> - -#ifdef HAVE_LIBWRAP -#include <tcpd.h> -#endif - -static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; - -Client::Client(EventLoop &_loop, Partition &_partition, - int _fd, int _uid, int _num) - :FullyBufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size), - TimeoutMonitor(_loop), - partition(_partition), - playlist(partition.playlist), player_control(partition.pc), - permission(getDefaultPermissions()), - uid(_uid), - num(_num), - idle_waiting(false), idle_flags(0), - num_subscriptions(0) -{ - TimeoutMonitor::ScheduleSeconds(client_timeout); -} - -void -client_new(EventLoop &loop, Partition &partition, - int fd, const struct sockaddr *sa, size_t sa_length, int uid) -{ - static unsigned int next_client_num; - char *remote; - - assert(fd >= 0); - -#ifdef HAVE_LIBWRAP - if (sa->sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(sa, sa_length, - IgnoreError()); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - FormatWarning(client_domain, - "libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - - g_free(hostaddr); - close_socket(fd); - return; - } - - g_free(hostaddr); - } -#endif /* HAVE_WRAP */ - - ClientList &client_list = *partition.instance.client_list; - if (client_list.IsFull()) { - LogWarning(client_domain, "Max connections reached"); - close_socket(fd); - return; - } - - Client *client = new Client(loop, partition, fd, uid, - next_client_num++); - - (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); - - client_list.Add(*client); - - remote = sockaddr_to_string(sa, sa_length, IgnoreError()); - FormatInfo(client_domain, "[%u] opened from %s", client->num, remote); - g_free(remote); -} - -void -Client::Close() -{ - partition.instance.client_list->Remove(*this); - - SetExpired(); - - FormatInfo(client_domain, "[%u] closed", num); - delete this; -} diff --git a/src/ClientProcess.cxx b/src/ClientProcess.cxx deleted file mode 100644 index 485e687c9..000000000 --- a/src/ClientProcess.cxx +++ /dev/null @@ -1,141 +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 "ClientInternal.hxx" -#include "protocol/Result.hxx" -#include "command/AllCommands.hxx" -#include "Log.hxx" - -#include <string.h> - -#define CLIENT_LIST_MODE_BEGIN "command_list_begin" -#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" -#define CLIENT_LIST_MODE_END "command_list_end" - -static CommandResult -client_process_command_list(Client &client, bool list_ok, - std::list<std::string> &&list) -{ - CommandResult ret = CommandResult::OK; - unsigned num = 0; - - for (auto &&i : list) { - char *cmd = &*i.begin(); - - FormatDebug(client_domain, "process command \"%s\"", cmd); - ret = command_process(client, num++, cmd); - FormatDebug(client_domain, "command returned %i", ret); - if (ret != CommandResult::OK || client.IsExpired()) - break; - else if (list_ok) - client_puts(client, "list_OK\n"); - } - - return ret; -} - -CommandResult -client_process_line(Client &client, char *line) -{ - CommandResult ret; - - if (strcmp(line, "noidle") == 0) { - if (client.idle_waiting) { - /* send empty idle response and leave idle mode */ - client.idle_waiting = false; - command_success(client); - } - - /* do nothing if the client wasn't idling: the client - has already received the full idle response from - client_idle_notify(), which he can now evaluate */ - - return CommandResult::OK; - } else if (client.idle_waiting) { - /* during idle mode, clients must not send anything - except "noidle" */ - FormatWarning(client_domain, - "[%u] command \"%s\" during idle", - client.num, line); - return CommandResult::CLOSE; - } - - if (client.cmd_list.IsActive()) { - if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { - FormatDebug(client_domain, - "[%u] process command list", - client.num); - - auto &&cmd_list = client.cmd_list.Commit(); - - ret = client_process_command_list(client, - client.cmd_list.IsOKMode(), - std::move(cmd_list)); - FormatDebug(client_domain, - "[%u] process command " - "list returned %i", client.num, ret); - - if (ret == CommandResult::CLOSE || - client.IsExpired()) - return CommandResult::CLOSE; - - if (ret == CommandResult::OK) - command_success(client); - - client.cmd_list.Reset(); - } else { - if (!client.cmd_list.Add(line)) { - FormatWarning(client_domain, - "[%u] command list size " - "is larger than the max (%lu)", - client.num, - (unsigned long)client_max_command_list_size); - return CommandResult::CLOSE; - } - - ret = CommandResult::OK; - } - } else { - if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client.cmd_list.Begin(false); - ret = CommandResult::OK; - } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client.cmd_list.Begin(true); - ret = CommandResult::OK; - } else { - FormatDebug(client_domain, - "[%u] process command \"%s\"", - client.num, line); - ret = command_process(client, 0, line); - FormatDebug(client_domain, - "[%u] command returned %i", - client.num, ret); - - if (ret == CommandResult::CLOSE || - client.IsExpired()) - return CommandResult::CLOSE; - - if (ret == CommandResult::OK) - command_success(client); - } - } - - return ret; -} diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx deleted file mode 100644 index 22edefe60..000000000 --- a/src/ClientRead.cxx +++ /dev/null @@ -1,76 +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 "ClientInternal.hxx" -#include "Main.hxx" -#include "event/Loop.hxx" -#include "util/CharUtil.hxx" - -#include <assert.h> -#include <string.h> - -BufferedSocket::InputResult -Client::OnSocketInput(void *data, size_t length) -{ - char *p = (char *)data; - char *newline = (char *)memchr(p, '\n', length); - if (newline == nullptr) - return InputResult::MORE; - - TimeoutMonitor::ScheduleSeconds(client_timeout); - - BufferedSocket::ConsumeInput(newline + 1 - p); - - /* skip whitespace at the end of the line */ - while (newline > p && IsWhitespaceOrNull(newline[-1])) - --newline; - - /* terminate the string at the end of the line */ - *newline = 0; - - CommandResult result = client_process_line(*this, p); - switch (result) { - case CommandResult::OK: - case CommandResult::IDLE: - case CommandResult::ERROR: - break; - - case CommandResult::KILL: - Close(); - main_loop->Break(); - return InputResult::CLOSED; - - case CommandResult::FINISH: - if (Flush()) - Close(); - return InputResult::CLOSED; - - case CommandResult::CLOSE: - Close(); - return InputResult::CLOSED; - } - - if (IsExpired()) { - Close(); - return InputResult::CLOSED; - } - - return InputResult::AGAIN; -} diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx deleted file mode 100644 index 3a9f1b19c..000000000 --- a/src/ClientSubscribe.cxx +++ /dev/null @@ -1,92 +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 "ClientInternal.hxx" -#include "Idle.hxx" - -#include <assert.h> -#include <string.h> - - bool Unsubscribe(const char *channel); - void UnsubscribeAll(); - bool PushMessage(const ClientMessage &msg); - -Client::SubscribeResult -Client::Subscribe(const char *channel) -{ - assert(channel != nullptr); - - if (!client_message_valid_channel_name(channel)) - return Client::SubscribeResult::INVALID; - - if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) - return Client::SubscribeResult::FULL; - - auto r = subscriptions.insert(channel); - if (!r.second) - return Client::SubscribeResult::ALREADY; - - ++num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - return Client::SubscribeResult::OK; -} - -bool -Client::Unsubscribe(const char *channel) -{ - const auto i = subscriptions.find(channel); - if (i == subscriptions.end()) - return false; - - assert(num_subscriptions > 0); - - subscriptions.erase(i); - --num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - assert((num_subscriptions == 0) == - subscriptions.empty()); - - return true; -} - -void -Client::UnsubscribeAll() -{ - subscriptions.clear(); - num_subscriptions = 0; -} - -bool -Client::PushMessage(const ClientMessage &msg) -{ - if (messages.size() >= CLIENT_MAX_MESSAGES || - !IsSubscribed(msg.GetChannel())) - return false; - - if (messages.empty()) - IdleAdd(IDLE_MESSAGE); - - messages.push_back(msg); - return true; -} diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx deleted file mode 100644 index e66197eb7..000000000 --- a/src/ClientWrite.cxx +++ /dev/null @@ -1,61 +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 "ClientInternal.hxx" -#include "util/FormatString.hxx" - -#include <string.h> - -/** - * Write a block of data to the client. - */ -static void -client_write(Client &client, const char *data, size_t length) -{ - /* if the client is going to be closed, do nothing */ - if (client.IsExpired() || length == 0) - return; - - client.Write(data, length); -} - -void -client_puts(Client &client, const char *s) -{ - client_write(client, s, strlen(s)); -} - -void -client_vprintf(Client &client, const char *fmt, va_list args) -{ - char *p = FormatNewV(fmt, args); - client_write(client, p, strlen(p)); - delete[] p; -} - -void -client_printf(Client &client, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - client_vprintf(client, fmt, args); - va_end(args); -} diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index 05f0a358c..cc278c0fd 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.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,57 +22,115 @@ #include "ls.hxx" #include "LogInit.hxx" #include "Log.hxx" -#include "ConfigGlobal.hxx" -#include "DecoderList.hxx" -#include "DecoderPlugin.hxx" -#include "OutputList.hxx" -#include "OutputPlugin.hxx" -#include "InputRegistry.hxx" -#include "InputPlugin.hxx" -#include "PlaylistRegistry.hxx" -#include "PlaylistPlugin.hxx" +#include "config/ConfigGlobal.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "output/Registry.hxx" +#include "output/OutputPlugin.hxx" +#include "input/Registry.hxx" +#include "input/InputPlugin.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "playlist/PlaylistPlugin.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" +#include "fs/StandardDirectory.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include "system/FatalError.hxx" +#include "util/OptionDef.hxx" +#include "util/OptionParser.hxx" + +#ifdef ENABLE_DATABASE +#include "db/Registry.hxx" +#include "db/DatabasePlugin.hxx" +#include "storage/Registry.hxx" +#include "storage/StoragePlugin.hxx" +#endif + +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Registry.hxx" +#include "neighbor/NeighborPlugin.hxx" +#endif #ifdef ENABLE_ENCODER -#include "EncoderList.hxx" -#include "EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "encoder/EncoderPlugin.hxx" #endif #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" #endif -#include <glib.h> - #include <stdio.h> #include <stdlib.h> #ifdef WIN32 -#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf" +#define CONFIG_FILE_LOCATION "mpd\\mpd.conf" +#define APP_CONFIG_FILE_LOCATION "conf\\mpd.conf" #else #define USER_CONFIG_FILE_LOCATION1 ".mpdconf" #define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf" #define USER_CONFIG_FILE_LOCATION_XDG "mpd/mpd.conf" #endif +static const OptionDef opt_kill( + "kill", "kill the currently running mpd session"); +static const OptionDef opt_no_config( + "no-config", "don't read from config"); +static const OptionDef opt_no_daemon( + "no-daemon", "don't detach from console"); +static const OptionDef opt_stdout( + "stdout", nullptr); // hidden, compatibility with old versions +static const OptionDef opt_stderr( + "stderr", "print messages to stderr"); +static const OptionDef opt_verbose( + "verbose", 'v', "verbose logging"); +static const OptionDef opt_version( + "version", 'V', "print version number"); +static const OptionDef opt_help( + "help", 'h', "show help options"); +static const OptionDef opt_help_alt( + nullptr, '?', nullptr); // hidden, standard alias for --help + static constexpr Domain cmdline_domain("cmdline"); gcc_noreturn static void version(void) { - puts("Music Player Daemon " VERSION "\n" + puts("Music Player Daemon " VERSION +#ifdef GIT_COMMIT + " (" GIT_COMMIT ")" +#endif + "\n" "\n" "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008-2013 Max Kellermann <max@duempel.org>\n" + "Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n" "This is free software; see the source for copying conditions. There is NO\n" - "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" - "\n" + "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); + +#ifdef ENABLE_DATABASE + puts("\n" + "Database plugins:"); + + for (auto i = database_plugins; *i != nullptr; ++i) + printf(" %s", (*i)->name); + + puts("\n\n" + "Storage plugins:"); + + for (auto i = storage_plugins; *i != nullptr; ++i) + printf(" %s", (*i)->name); +#endif + +#ifdef ENABLE_NEIGHBOR_PLUGINS + puts("\n\n" + "Neighbor plugins:"); + for (auto i = neighbor_plugins; *i != nullptr; ++i) + printf(" %s", (*i)->name); +#endif + + puts("\n\n" "Decoders plugins:"); decoder_plugins_for_each([](const DecoderPlugin &plugin){ @@ -132,122 +190,165 @@ static void version(void) exit(EXIT_SUCCESS); } -static const char *summary = - "Music Player Daemon - a daemon for playing music."; +static void PrintOption(const OptionDef &opt) +{ + if (opt.HasShortOption()) + printf(" -%c, --%-12s%s\n", + opt.GetShortOption(), + opt.GetLongOption(), + opt.GetDescription()); + else + printf(" --%-16s%s\n", + opt.GetLongOption(), + opt.GetDescription()); +} -gcc_pure -static AllocatedPath -PathBuildChecked(const AllocatedPath &a, PathTraits::const_pointer b) +gcc_noreturn +static void help(void) { - if (a.IsNull()) - return AllocatedPath::Null(); + puts("Usage:\n" + " mpd [OPTION...] [path/to/mpd.conf]\n" + "\n" + "Music Player Daemon - a daemon for playing music.\n" + "\n" + "Options:"); + + PrintOption(opt_help); + PrintOption(opt_kill); + PrintOption(opt_no_config); + PrintOption(opt_no_daemon); + PrintOption(opt_stderr); + PrintOption(opt_verbose); + PrintOption(opt_version); - return AllocatedPath::Build(a, b); + exit(EXIT_SUCCESS); +} + +class ConfigLoader +{ + Error &error; + bool result; +public: + ConfigLoader(Error &_error) : error(_error), result(false) { } + + bool GetResult() const { return result; } + + bool TryFile(const Path path); + bool TryFile(const AllocatedPath &base_path, + PathTraitsFS::const_pointer path); +}; + +bool ConfigLoader::TryFile(Path path) +{ + if (FileExists(path)) { + result = ReadConfigFile(path, error); + return true; + } + return false; +} + +bool ConfigLoader::TryFile(const AllocatedPath &base_path, + PathTraitsFS::const_pointer path) +{ + if (base_path.IsNull()) + return false; + auto full_path = AllocatedPath::Build(base_path, path); + return TryFile(full_path); } bool parse_cmdline(int argc, char **argv, struct options *options, Error &error) { - GOptionContext *context; - bool ret; - static gboolean option_version, - option_no_daemon, - option_no_config; - const GOptionEntry entries[] = { - { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill, - "kill the currently running mpd session", nullptr }, - { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, - "don't read from config", nullptr }, - { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, - "don't detach from console", nullptr }, - { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - nullptr, nullptr }, - { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - "print messages to stderr", nullptr }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, - "verbose logging", nullptr }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, - "print version number", nullptr }, - { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } - }; - + bool use_config_file = true; options->kill = false; options->daemon = true; options->log_stderr = false; options->verbose = false; - context = g_option_context_new("[path/to/mpd.conf]"); - g_option_context_add_main_entries(context, entries, nullptr); - - g_option_context_set_summary(context, summary); - - GError *gerror = nullptr; - ret = g_option_context_parse(context, &argc, &argv, &gerror); - g_option_context_free(context); - - if (!ret) - FatalError("option parsing failed", gerror); + // First pass: handle command line options + OptionParser parser(argc, argv); + while (parser.HasEntries()) { + if (!parser.ParseNext()) + continue; + if (parser.CheckOption(opt_kill)) { + options->kill = true; + continue; + } + if (parser.CheckOption(opt_no_config)) { + use_config_file = false; + continue; + } + if (parser.CheckOption(opt_no_daemon)) { + options->daemon = false; + continue; + } + if (parser.CheckOption(opt_stderr, opt_stdout)) { + options->log_stderr = true; + continue; + } + if (parser.CheckOption(opt_verbose)) { + options->verbose = true; + continue; + } + if (parser.CheckOption(opt_version)) + version(); + if (parser.CheckOption(opt_help, opt_help_alt)) + help(); - if (option_version) - version(); + error.Format(cmdline_domain, "invalid option: %s", + parser.GetOption()); + return false; + } /* initialize the logging library, so the configuration file parser can use it already */ log_early_init(options->verbose); - options->daemon = !option_no_daemon; - - if (option_no_config) { + if (!use_config_file) { LogDebug(cmdline_domain, "Ignoring config, using daemon defaults"); return true; - } else if (argc <= 1) { - /* default configuration file path */ + } -#ifdef WIN32 - AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), - CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - const char *const*system_config_dirs = - g_get_system_config_dirs(); - - for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) { - path = PathBuildChecked(AllocatedPath::FromUTF8(system_config_dirs[i]), - CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + // Second pass: find non-option parameters (i.e. config file) + const char *config_file = nullptr; + for (int i = 1; i < argc; ++i) { + if (OptionParser::IsOption(argv[i])) + continue; + if (config_file == nullptr) { + config_file = argv[i]; + continue; } + error.Set(cmdline_domain, "too many arguments"); + return false; + } + + if (config_file != nullptr) { + /* use specified configuration file */ + return ReadConfigFile(Path::FromFS(config_file), error); + } + + /* use default configuration file path */ + + ConfigLoader loader(error); + + bool found = +#ifdef WIN32 + loader.TryFile(GetUserConfigDir(), CONFIG_FILE_LOCATION) || + loader.TryFile(GetSystemConfigDir(), CONFIG_FILE_LOCATION) || + loader.TryFile(GetAppBaseDir(), APP_CONFIG_FILE_LOCATION); #else - AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), - USER_CONFIG_FILE_LOCATION_XDG); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), - USER_CONFIG_FILE_LOCATION1); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), - USER_CONFIG_FILE_LOCATION2); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - path = AllocatedPath::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + loader.TryFile(GetUserConfigDir(), + USER_CONFIG_FILE_LOCATION_XDG) || + loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION1) || + loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION2) || + loader.TryFile(Path::FromFS(SYSTEM_CONFIG_FILE_LOCATION)); #endif - + if (!found) { error.Set(cmdline_domain, "No configuration file found"); return false; - } else if (argc == 2) { - /* specified configuration file */ - return ReadConfigFile(Path::FromFS(argv[1]), error); - } else { - error.Set(cmdline_domain, "too many arguments"); - return false; } + + return loader.GetResult(); } diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx index 214150eae..d8dedb1fb 100644 --- a/src/CommandLine.hxx +++ b/src/CommandLine.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 @@ -20,15 +20,13 @@ #ifndef MPD_COMMAND_LINE_HXX #define MPD_COMMAND_LINE_HXX -#include <glib.h> - class Error; struct options { - gboolean kill; - gboolean daemon; - gboolean log_stderr; - gboolean verbose; + bool kill; + bool daemon; + bool log_stderr; + bool verbose; }; bool diff --git a/src/Compiler.h b/src/Compiler.h index 94abdcff3..44a87c7ba 100644 --- a/src/Compiler.h +++ b/src/Compiler.h @@ -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/ConfigData.cxx b/src/ConfigData.cxx deleted file mode 100644 index c6cabee6e..000000000 --- a/src/ConfigData.cxx +++ /dev/null @@ -1,160 +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 "ConfigData.hxx" -#include "ConfigParser.hxx" -#include "ConfigPath.hxx" -#include "util/Error.hxx" -#include "fs/AllocatedPath.hxx" -#include "system/FatalError.hxx" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -int -block_param::GetIntValue() const -{ - char *endptr; - long value2 = strtol(value.c_str(), &endptr, 0); - if (*endptr != 0) - FormatFatalError("Not a valid number in line %i", line); - - return value2; -} - -unsigned -block_param::GetUnsignedValue() const -{ - char *endptr; - unsigned long value2 = strtoul(value.c_str(), &endptr, 0); - if (*endptr != 0) - FormatFatalError("Not a valid number in line %i", line); - - return (unsigned)value2; -} - -bool -block_param::GetBoolValue() const -{ - bool value2; - if (!get_bool(value.c_str(), &value2)) - FormatFatalError("%s is not a boolean value (yes, true, 1) or " - "(no, false, 0) on line %i\n", - name.c_str(), line); - - return value2; -} - -config_param::config_param(const char *_value, int _line) - :next(nullptr), value(_value), line(_line) {} - -config_param::~config_param() -{ - delete next; -} - -const block_param * -config_param::GetBlockParam(const char *name) const -{ - for (const auto &i : block_params) { - if (i.name == name) { - i.used = true; - return &i; - } - } - - return NULL; -} - -const char * -config_param::GetBlockValue(const char *name, const char *default_value) const -{ - const block_param *bp = GetBlockParam(name); - if (bp == nullptr) - return default_value; - - return bp->value.c_str(); -} - -AllocatedPath -config_param::GetBlockPath(const char *name, const char *default_value, - Error &error) const -{ - assert(!error.IsDefined()); - - int line2 = line; - const char *s; - - const block_param *bp = GetBlockParam(name); - if (bp != nullptr) { - line2 = bp->line; - s = bp->value.c_str(); - } else { - if (default_value == nullptr) - return AllocatedPath::Null(); - - s = default_value; - } - - AllocatedPath path = ParsePath(s, error); - if (gcc_unlikely(path.IsNull())) - error.FormatPrefix("Invalid path in \"%s\" at line %i: ", - name, line2); - - return path; -} - -AllocatedPath -config_param::GetBlockPath(const char *name, Error &error) const -{ - return GetBlockPath(name, nullptr, error); -} - -int -config_param::GetBlockValue(const char *name, int default_value) const -{ - const block_param *bp = GetBlockParam(name); - if (bp == nullptr) - return default_value; - - return bp->GetIntValue(); -} - -unsigned -config_param::GetBlockValue(const char *name, unsigned default_value) const -{ - const block_param *bp = GetBlockParam(name); - if (bp == nullptr) - return default_value; - - return bp->GetUnsignedValue(); -} - -gcc_pure -bool -config_param::GetBlockValue(const char *name, bool default_value) const -{ - const block_param *bp = GetBlockParam(name); - if (bp == NULL) - return default_value; - - return bp->GetBoolValue(); -} diff --git a/src/ConfigData.hxx b/src/ConfigData.hxx deleted file mode 100644 index b41b27420..000000000 --- a/src/ConfigData.hxx +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_DATA_HXX -#define MPD_CONFIG_DATA_HXX - -#include "ConfigOption.hxx" -#include "Compiler.h" - -#include <string> -#include <array> -#include <vector> - -class AllocatedPath; -class Error; - -struct block_param { - std::string name; - std::string value; - int line; - - /** - * This flag is false when nobody has queried the value of - * this option yet. - */ - mutable bool used; - - gcc_nonnull_all - block_param(const char *_name, const char *_value, int _line=-1) - :name(_name), value(_value), line(_line), used(false) {} - - gcc_pure - int GetIntValue() const; - - gcc_pure - unsigned GetUnsignedValue() const; - - gcc_pure - bool GetBoolValue() const; -}; - -struct config_param { - /** - * The next config_param with the same name. The destructor - * deletes the whole chain. - */ - struct config_param *next; - - std::string value; - - unsigned int line; - - std::vector<block_param> block_params; - - /** - * This flag is false when nobody has queried the value of - * this option yet. - */ - bool used; - - config_param(int _line=-1) - :next(nullptr), line(_line), used(false) {} - - gcc_nonnull_all - config_param(const char *_value, int _line=-1); - - config_param(const config_param &) = delete; - - ~config_param(); - - config_param &operator=(const config_param &) = delete; - - /** - * Determine if this is a "null" instance, i.e. an empty - * object that was synthesized and not loaded from a - * configuration file. - */ - bool IsNull() const { - return line == unsigned(-1); - } - - gcc_nonnull_all - void AddBlockParam(const char *_name, const char *_value, - int _line=-1) { - block_params.emplace_back(_name, _value, _line); - } - - gcc_nonnull_all gcc_pure - const block_param *GetBlockParam(const char *_name) const; - - gcc_pure - const char *GetBlockValue(const char *name, - const char *default_value=nullptr) const; - - /** - * Same as config_dup_path(), but looks up the setting in the - * specified block. - */ - AllocatedPath GetBlockPath(const char *name, const char *default_value, - Error &error) const; - - AllocatedPath GetBlockPath(const char *name, Error &error) const; - - gcc_pure - int GetBlockValue(const char *name, int default_value) const; - - gcc_pure - unsigned GetBlockValue(const char *name, unsigned default_value) const; - - gcc_pure - bool GetBlockValue(const char *name, bool default_value) const; -}; - -struct ConfigData { - std::array<config_param *, std::size_t(CONF_MAX)> params; -}; - -#endif diff --git a/src/ConfigDefaults.hxx b/src/ConfigDefaults.hxx deleted file mode 100644 index 9cfe61582..000000000 --- a/src/ConfigDefaults.hxx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_DEFAULTS_HXX -#define MPD_CONFIG_DEFAULTS_HXX - -static constexpr unsigned DEFAULT_PLAYLIST_MAX_LENGTH = 16 * 1024; -static constexpr bool DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS = false; - -#endif diff --git a/src/ConfigError.cxx b/src/ConfigError.cxx deleted file mode 100644 index bd529e670..000000000 --- a/src/ConfigError.cxx +++ /dev/null @@ -1,23 +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 "ConfigError.hxx" -#include "util/Domain.hxx" - -const Domain config_domain("config"); diff --git a/src/ConfigError.hxx b/src/ConfigError.hxx deleted file mode 100644 index a8917d7d1..000000000 --- a/src/ConfigError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_ERROR_HXX -#define MPD_CONFIG_ERROR_HXX - -extern const class Domain config_domain; - -#endif diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx deleted file mode 100644 index 90859a89a..000000000 --- a/src/ConfigFile.cxx +++ /dev/null @@ -1,272 +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 "ConfigFile.hxx" -#include "ConfigError.hxx" -#include "ConfigData.hxx" -#include "ConfigTemplates.hxx" -#include "util/Tokenizer.hxx" -#include "util/StringUtil.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/Limits.hxx" -#include "fs/Path.hxx" -#include "fs/FileSystem.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> -#include <stdio.h> -#include <errno.h> - -#define MAX_STRING_SIZE MPD_PATH_MAX+80 - -#define CONF_COMMENT '#' - -static constexpr Domain config_file_domain("config_file"); - -static bool -config_read_name_value(struct config_param *param, char *input, unsigned line, - Error &error) -{ - Tokenizer tokenizer(input); - - const char *name = tokenizer.NextWord(error); - if (name == nullptr) { - assert(!tokenizer.IsEnd()); - return false; - } - - const char *value = tokenizer.NextString(error); - if (value == nullptr) { - if (tokenizer.IsEnd()) { - error.Set(config_file_domain, "Value missing"); - } else { - assert(error.IsDefined()); - } - - return false; - } - - if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) { - error.Set(config_file_domain, "Unknown tokens after value"); - return false; - } - - const struct block_param *bp = param->GetBlockParam(name); - if (bp != nullptr) { - error.Format(config_file_domain, - "\"%s\" is duplicate, first defined on line %i", - name, bp->line); - return false; - } - - param->AddBlockParam(name, value, line); - return true; -} - -static struct config_param * -config_read_block(FILE *fp, int *count, char *string, Error &error) -{ - struct config_param *ret = new config_param(*count); - - while (true) { - char *line; - - line = fgets(string, MAX_STRING_SIZE, fp); - if (line == nullptr) { - delete ret; - error.Set(config_file_domain, - "Expected '}' before end-of-file"); - return nullptr; - } - - (*count)++; - line = strchug_fast(line); - if (*line == 0 || *line == CONF_COMMENT) - continue; - - if (*line == '}') { - /* end of this block; return from the function - (and from this "while" loop) */ - - line = strchug_fast(line + 1); - if (*line != 0 && *line != CONF_COMMENT) { - delete ret; - error.Format(config_file_domain, - "line %i: Unknown tokens after '}'", - *count); - return nullptr; - } - - return ret; - } - - /* parse name and value */ - - if (!config_read_name_value(ret, line, *count, error)) { - assert(*line != 0); - delete ret; - error.FormatPrefix("line %i: ", *count); - return nullptr; - } - } -} - -gcc_nonnull_all -static void -Append(config_param *&head, config_param *p) -{ - assert(p->next == nullptr); - - config_param **i = &head; - while (*i != nullptr) - i = &(*i)->next; - - *i = p; -} - -static bool -ReadConfigFile(ConfigData &config_data, FILE *fp, Error &error) -{ - assert(fp != nullptr); - - char string[MAX_STRING_SIZE + 1]; - int count = 0; - struct config_param *param; - - while (fgets(string, MAX_STRING_SIZE, fp)) { - char *line; - const char *name, *value; - - count++; - - line = strchug_fast(string); - if (*line == 0 || *line == CONF_COMMENT) - continue; - - /* the first token in each line is the name, followed - by either the value or '{' */ - - Tokenizer tokenizer(line); - name = tokenizer.NextWord(error); - if (name == nullptr) { - assert(!tokenizer.IsEnd()); - error.FormatPrefix("line %i: ", count); - return false; - } - - /* get the definition of that option, and check the - "repeatable" flag */ - - const ConfigOption o = ParseConfigOptionName(name); - if (o == CONF_MAX) { - error.Format(config_file_domain, - "unrecognized parameter in config file at " - "line %i: %s\n", count, name); - return false; - } - - const unsigned i = unsigned(o); - const ConfigTemplate &option = config_templates[i]; - config_param *&head = config_data.params[i]; - - if (head != nullptr && !option.repeatable) { - param = head; - error.Format(config_file_domain, - "config parameter \"%s\" is first defined " - "on line %i and redefined on line %i\n", - name, param->line, count); - return false; - } - - /* now parse the block or the value */ - - if (option.block) { - /* it's a block, call config_read_block() */ - - if (tokenizer.CurrentChar() != '{') { - error.Format(config_file_domain, - "line %i: '{' expected", count); - return false; - } - - line = strchug_fast(tokenizer.Rest() + 1); - if (*line != 0 && *line != CONF_COMMENT) { - error.Format(config_file_domain, - "line %i: Unknown tokens after '{'", - count); - return false; - } - - param = config_read_block(fp, &count, string, error); - if (param == nullptr) { - return false; - } - } else { - /* a string value */ - - value = tokenizer.NextString(error); - if (value == nullptr) { - if (tokenizer.IsEnd()) - error.Format(config_file_domain, - "line %i: Value missing", - count); - else - error.FormatPrefix("line %i: ", count); - - return false; - } - - if (!tokenizer.IsEnd() && - tokenizer.CurrentChar() != CONF_COMMENT) { - error.Format(config_file_domain, - "line %i: Unknown tokens after value", - count); - return false; - } - - param = new config_param(value, count); - } - - Append(head, param); - } - - return true; -} - -bool -ReadConfigFile(ConfigData &config_data, Path path, Error &error) -{ - assert(!path.IsNull()); - const std::string path_utf8 = path.ToUTF8(); - - FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str()); - - FILE *fp = FOpen(path, FOpenMode::ReadText); - if (fp == nullptr) { - error.FormatErrno("Failed to open %s", path_utf8.c_str()); - return false; - } - - bool result = ReadConfigFile(config_data, fp, error); - fclose(fp); - return result; -} diff --git a/src/ConfigFile.hxx b/src/ConfigFile.hxx deleted file mode 100644 index 8c4ddbbd3..000000000 --- a/src/ConfigFile.hxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_FILE_HXX -#define MPD_CONFIG_FILE_HXX - -class Error; -class Path; -struct ConfigData; - -bool -ReadConfigFile(ConfigData &data, Path path, Error &error); - -#endif diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx deleted file mode 100644 index 49b9c08fb..000000000 --- a/src/ConfigGlobal.cxx +++ /dev/null @@ -1,177 +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 "ConfigGlobal.hxx" -#include "ConfigParser.hxx" -#include "ConfigData.hxx" -#include "ConfigFile.hxx" -#include "ConfigPath.hxx" -#include "ConfigError.hxx" -#include "fs/Path.hxx" -#include "fs/AllocatedPath.hxx" -#include "util/Error.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -static ConfigData config_data; - -void config_global_finish(void) -{ - for (auto i : config_data.params) - delete i; -} - -void config_global_init(void) -{ -} - -bool -ReadConfigFile(Path path, Error &error) -{ - return ReadConfigFile(config_data, path, error); -} - -static void -Check(const config_param *param) -{ - if (!param->used) - /* this whole config_param was not queried at all - - the feature might be disabled at compile time? - Silently ignore it here. */ - return; - - for (const auto &i : param->block_params) { - if (!i.used) - FormatWarning(config_domain, - "option '%s' on line %i was not recognized", - i.name.c_str(), i.line); - } -} - -void config_global_check(void) -{ - for (auto i : config_data.params) - for (const config_param *p = i; p != nullptr; p = p->next) - Check(p); -} - -const struct config_param * -config_get_next_param(ConfigOption option, const struct config_param * last) -{ - config_param *param = last != nullptr - ? last->next - : config_data.params[unsigned(option)]; - if (param != nullptr) - param->used = true; - return param; -} - -const char * -config_get_string(ConfigOption option, const char *default_value) -{ - const struct config_param *param = config_get_param(option); - - if (param == nullptr) - return default_value; - - return param->value.c_str(); -} - -AllocatedPath -config_get_path(ConfigOption option, Error &error) -{ - const struct config_param *param = config_get_param(option); - if (param == nullptr) - return AllocatedPath::Null(); - - return config_parse_path(param, error); -} - -AllocatedPath -config_parse_path(const struct config_param *param, Error & error) -{ - AllocatedPath path = ParsePath(param->value.c_str(), error); - if (gcc_unlikely(path.IsNull())) - error.FormatPrefix("Invalid path at line %i: ", - param->line); - - return path; -} - -unsigned -config_get_unsigned(ConfigOption option, unsigned default_value) -{ - const struct config_param *param = config_get_param(option); - long value; - char *endptr; - - if (param == nullptr) - return default_value; - - value = strtol(param->value.c_str(), &endptr, 0); - if (*endptr != 0 || value < 0) - FormatFatalError("Not a valid non-negative number in line %i", - param->line); - - return (unsigned)value; -} - -unsigned -config_get_positive(ConfigOption option, unsigned default_value) -{ - const struct config_param *param = config_get_param(option); - long value; - char *endptr; - - if (param == nullptr) - return default_value; - - value = strtol(param->value.c_str(), &endptr, 0); - if (*endptr != 0) - FormatFatalError("Not a valid number in line %i", param->line); - - if (value <= 0) - FormatFatalError("Not a positive number in line %i", - param->line); - - return (unsigned)value; -} - -bool -config_get_bool(ConfigOption option, bool default_value) -{ - const struct config_param *param = config_get_param(option); - bool success, value; - - if (param == nullptr) - return default_value; - - success = get_bool(param->value.c_str(), &value); - if (!success) - FormatFatalError("Expected boolean value (yes, true, 1) or " - "(no, false, 0) on line %i\n", - param->line); - - return value; -} diff --git a/src/ConfigGlobal.hxx b/src/ConfigGlobal.hxx deleted file mode 100644 index 978db3145..000000000 --- a/src/ConfigGlobal.hxx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_GLOBAL_HXX -#define MPD_CONFIG_GLOBAL_HXX - -#include "ConfigOption.hxx" -#include "Compiler.h" - -class Error; -class Path; -class AllocatedPath; - -void config_global_init(void); -void config_global_finish(void); - -/** - * Call this function after all configuration has been evaluated. It - * checks for unused parameters, and logs warnings. - */ -void config_global_check(void); - -bool -ReadConfigFile(Path path, Error &error); - -/* don't free the returned value - set _last_ to nullptr to get first entry */ -gcc_pure -const struct config_param * -config_get_next_param(enum ConfigOption option, - const struct config_param *last); - -gcc_pure -static inline const struct config_param * -config_get_param(enum ConfigOption option) -{ - return config_get_next_param(option, nullptr); -} - -/* Note on gcc_pure: Some of the functions declared pure are not - really pure in strict sense. They have side effect such that they - validate parameter's value and signal an error if it's invalid. - However, if the argument was already validated or we don't care - about the argument at all, this may be ignored so in the end, we - should be fine with calling those functions pure. */ - -gcc_pure -const char * -config_get_string(enum ConfigOption option, const char *default_value); - -/** - * Returns an optional configuration variable which contains an - * absolute path. If there is a tilde prefix, it is expanded. - * Returns AllocatedPath::Null() if the value is not present. If the path - * could not be parsed, returns AllocatedPath::Null() and sets the error. - */ -AllocatedPath -config_get_path(enum ConfigOption option, Error &error); - -/** - * Parse a configuration parameter as a path. - * If there is a tilde prefix, it is expanded. If the path could - * not be parsed, returns AllocatedPath::Null() and sets the error. - */ -AllocatedPath -config_parse_path(const struct config_param *param, Error & error_r); - -gcc_pure -unsigned -config_get_unsigned(enum ConfigOption option, unsigned default_value); - -gcc_pure -unsigned -config_get_positive(enum ConfigOption option, unsigned default_value); - -gcc_pure -bool config_get_bool(enum ConfigOption option, bool default_value); - -#endif diff --git a/src/ConfigOption.hxx b/src/ConfigOption.hxx deleted file mode 100644 index e8a2a86c8..000000000 --- a/src/ConfigOption.hxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_OPTION_HXX -#define MPD_CONFIG_OPTION_HXX - -#include "Compiler.h" - -enum ConfigOption { - CONF_MUSIC_DIR, - CONF_PLAYLIST_DIR, - CONF_FOLLOW_INSIDE_SYMLINKS, - CONF_FOLLOW_OUTSIDE_SYMLINKS, - CONF_DB_FILE, - CONF_STICKER_FILE, - CONF_LOG_FILE, - CONF_PID_FILE, - CONF_STATE_FILE, - CONF_RESTORE_PAUSED, - CONF_USER, - CONF_GROUP, - CONF_BIND_TO_ADDRESS, - CONF_PORT, - CONF_LOG_LEVEL, - CONF_ZEROCONF_NAME, - CONF_ZEROCONF_ENABLED, - CONF_PASSWORD, - CONF_DEFAULT_PERMS, - CONF_AUDIO_OUTPUT, - CONF_AUDIO_OUTPUT_FORMAT, - CONF_MIXER_TYPE, - CONF_REPLAYGAIN, - CONF_REPLAYGAIN_PREAMP, - CONF_REPLAYGAIN_MISSING_PREAMP, - CONF_REPLAYGAIN_LIMIT, - CONF_VOLUME_NORMALIZATION, - CONF_SAMPLERATE_CONVERTER, - CONF_AUDIO_BUFFER_SIZE, - CONF_BUFFER_BEFORE_PLAY, - CONF_HTTP_PROXY_HOST, - CONF_HTTP_PROXY_PORT, - CONF_HTTP_PROXY_USER, - CONF_HTTP_PROXY_PASSWORD, - CONF_CONN_TIMEOUT, - CONF_MAX_CONN, - CONF_MAX_PLAYLIST_LENGTH, - CONF_MAX_COMMAND_LIST_SIZE, - CONF_MAX_OUTPUT_BUFFER_SIZE, - CONF_FS_CHARSET, - CONF_ID3V1_ENCODING, - CONF_METADATA_TO_USE, - CONF_SAVE_ABSOLUTE_PATHS, - CONF_DECODER, - CONF_INPUT, - CONF_GAPLESS_MP3_PLAYBACK, - CONF_PLAYLIST_PLUGIN, - CONF_AUTO_UPDATE, - CONF_AUTO_UPDATE_DEPTH, - CONF_DESPOTIFY_USER, - CONF_DESPOTIFY_PASSWORD, - CONF_DESPOTIFY_HIGH_BITRATE, - CONF_AUDIO_FILTER, - CONF_DATABASE, - CONF_MAX -}; - -/** - * @return #CONF_MAX if not found - */ -gcc_pure -enum ConfigOption -ParseConfigOptionName(const char *name); - -#endif diff --git a/src/ConfigParser.cxx b/src/ConfigParser.cxx deleted file mode 100644 index 73381d8a0..000000000 --- a/src/ConfigParser.cxx +++ /dev/null @@ -1,40 +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 "ConfigParser.hxx" -#include "util/StringUtil.hxx" - -bool -get_bool(const char *value, bool *value_r) -{ - static const char *t[] = { "yes", "true", "1", nullptr }; - static const char *f[] = { "no", "false", "0", nullptr }; - - if (string_array_contains(t, value)) { - *value_r = true; - return true; - } - - if (string_array_contains(f, value)) { - *value_r = false; - return true; - } - - return false; -} diff --git a/src/ConfigParser.hxx b/src/ConfigParser.hxx deleted file mode 100644 index 00fd42fe3..000000000 --- a/src/ConfigParser.hxx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_PARSER_HXX -#define MPD_CONFIG_PARSER_HXX - -bool -get_bool(const char *value, bool *value_r); - -#endif diff --git a/src/ConfigPath.cxx b/src/ConfigPath.cxx deleted file mode 100644 index b88de3934..000000000 --- a/src/ConfigPath.cxx +++ /dev/null @@ -1,132 +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 "ConfigPath.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/Traits.hxx" -#include "fs/Domain.hxx" -#include "util/Error.hxx" -#include "ConfigGlobal.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#ifndef WIN32 -#include <pwd.h> - -/** - * Determine a given user's home directory. - */ -static AllocatedPath -GetHome(const char *user, Error &error) -{ - passwd *pw = getpwnam(user); - if (pw == nullptr) { - error.Format(path_domain, - "no such user: %s", user); - return AllocatedPath::Null(); - } - - return AllocatedPath::FromFS(pw->pw_dir); -} - -/** - * Determine the current user's home directory. - */ -static AllocatedPath -GetHome(Error &error) -{ - const char *home = g_get_home_dir(); - if (home == nullptr) { - error.Set(path_domain, - "problems getting home for current user"); - return AllocatedPath::Null(); - } - - return AllocatedPath::FromUTF8(home, error); -} - -/** - * Determine the configured user's home directory. - */ -static AllocatedPath -GetConfiguredHome(Error &error) -{ - const char *user = config_get_string(CONF_USER, nullptr); - return user != nullptr - ? GetHome(user, error) - : GetHome(error); -} - -#endif - -AllocatedPath -ParsePath(const char *path, Error &error) -{ - assert(path != nullptr); - -#ifndef WIN32 - if (path[0] == '~') { - ++path; - - if (*path == '\0') - return GetConfiguredHome(error); - - AllocatedPath home = AllocatedPath::Null(); - - if (*path == '/') { - home = GetConfiguredHome(error); - - ++path; - } else { - const char *slash = strchr(path, '/'); - const char *end = slash == nullptr - ? path + strlen(path) - : slash; - const std::string user(path, end); - home = GetHome(user.c_str(), error); - - if (slash == nullptr) - return home; - - path = slash + 1; - } - - if (home.IsNull()) - return AllocatedPath::Null(); - - AllocatedPath path2 = AllocatedPath::FromUTF8(path, error); - if (path2.IsNull()) - return AllocatedPath::Null(); - - return AllocatedPath::Build(home, path2); - } else if (!PathTraits::IsAbsoluteUTF8(path)) { - error.Format(path_domain, - "not an absolute path: %s", path); - return AllocatedPath::Null(); - } else { -#endif - return AllocatedPath::FromUTF8(path, error); -#ifndef WIN32 - } -#endif -} diff --git a/src/ConfigPath.hxx b/src/ConfigPath.hxx deleted file mode 100644 index 99abd85b7..000000000 --- a/src/ConfigPath.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_PATH_HXX -#define MPD_CONFIG_PATH_HXX - -class AllocatedPath; -class Error; - -AllocatedPath -ParsePath(const char *path, Error &error); - -#endif diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx deleted file mode 100644 index 6c6bf1689..000000000 --- a/src/ConfigTemplates.cxx +++ /dev/null @@ -1,96 +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 "ConfigTemplates.hxx" -#include "ConfigOption.hxx" - -#include <string.h> - -const ConfigTemplate config_templates[] = { - { "music_directory", false, false }, - { "playlist_directory", false, false }, - { "follow_inside_symlinks", false, false }, - { "follow_outside_symlinks", false, false }, - { "db_file", false, false }, - { "sticker_file", false, false }, - { "log_file", false, false }, - { "pid_file", false, false }, - { "state_file", false, false }, - { "restore_paused", false, false }, - { "user", false, false }, - { "group", false, false }, - { "bind_to_address", true, false }, - { "port", false, false }, - { "log_level", false, false }, - { "zeroconf_name", false, false }, - { "zeroconf_enabled", false, false }, - { "password", true, false }, - { "default_permissions", false, false }, - { "audio_output", true, true }, - { "audio_output_format", false, false }, - { "mixer_type", false, false }, - { "replaygain", false, false }, - { "replaygain_preamp", false, false }, - { "replaygain_missing_preamp", false, false }, - { "replaygain_limit", false, false }, - { "volume_normalization", false, false }, - { "samplerate_converter", false, false }, - { "audio_buffer_size", false, false }, - { "buffer_before_play", false, false }, - { "http_proxy_host", false, false }, - { "http_proxy_port", false, false }, - { "http_proxy_user", false, false }, - { "http_proxy_password", false, false }, - { "connection_timeout", false, false }, - { "max_connections", false, false }, - { "max_playlist_length", false, false }, - { "max_command_list_size", false, false }, - { "max_output_buffer_size", false, false }, - { "filesystem_charset", false, false }, - { "id3v1_encoding", false, false }, - { "metadata_to_use", false, false }, - { "save_absolute_paths_in_playlists", false, false }, - { "decoder", true, true }, - { "input", true, true }, - { "gapless_mp3_playback", false, false }, - { "playlist_plugin", true, true }, - { "auto_update", false, false }, - { "auto_update_depth", false, false }, - { "despotify_user", false, false }, - { "despotify_password", false, false}, - { "despotify_high_bitrate", false, false }, - { "filter", true, true }, - { "database", false, true }, -}; - -static constexpr unsigned n_config_templates = - sizeof(config_templates) / sizeof(config_templates[0]); - -static_assert(n_config_templates == unsigned(CONF_MAX), - "Wrong number of config_templates"); - -ConfigOption -ParseConfigOptionName(const char *name) -{ - for (unsigned i = 0; i < n_config_templates; ++i) - if (strcmp(config_templates[i].name, name) == 0) - return ConfigOption(i); - - return CONF_MAX; -} diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx deleted file mode 100644 index 4f5460460..000000000 --- a/src/ConfigTemplates.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONFIG_TEMPLATES_HXX -#define MPD_CONFIG_TEMPLATES_HXX - -#include "ConfigOption.hxx" - -struct ConfigTemplate { - const char *const name; - const bool repeatable; - const bool block; -}; - -extern const ConfigTemplate config_templates[]; - -#endif diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx index 601d74dc2..bb585db5a 100644 --- a/src/CrossFade.cxx +++ b/src/CrossFade.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 @@ -26,8 +26,6 @@ #include "Log.hxx" #include <assert.h> -#include <string.h> -#include <stdlib.h> static constexpr Domain cross_fade_domain("cross_fade"); diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx index c47db84e1..b385ea316 100644 --- a/src/CrossFade.hxx +++ b/src/CrossFade.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/Daemon.cxx b/src/Daemon.cxx deleted file mode 100644 index 557c47777..000000000 --- a/src/Daemon.cxx +++ /dev/null @@ -1,247 +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 "Daemon.hxx" -#include "system/FatalError.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -#ifndef WIN32 -#include <signal.h> -#include <pwd.h> -#include <grp.h> -#endif - -static constexpr Domain daemon_domain("daemon"); - -#ifndef WIN32 - -/** the Unix user name which MPD runs as */ -static char *user_name; - -/** the Unix user id which MPD runs as */ -static uid_t user_uid = (uid_t)-1; - -/** the Unix group id which MPD runs as */ -static gid_t user_gid = (gid_t)-1; - -/** the absolute path of the pidfile */ -static AllocatedPath pidfile = AllocatedPath::Null(); - -/* whether "group" conf. option was given */ -static bool had_group = false; - - -void -daemonize_kill(void) -{ - FILE *fp; - int pid, ret; - - if (pidfile.IsNull()) - FatalError("no pid_file specified in the config file"); - - fp = FOpen(pidfile, "r"); - if (fp == nullptr) { - const std::string utf8 = pidfile.ToUTF8(); - FormatFatalSystemError("Unable to open pid file \"%s\"", - utf8.c_str()); - } - - if (fscanf(fp, "%i", &pid) != 1) { - const std::string utf8 = pidfile.ToUTF8(); - FormatFatalError("unable to read the pid from file \"%s\"", - utf8.c_str()); - } - fclose(fp); - - ret = kill(pid, SIGTERM); - if (ret < 0) - FormatFatalSystemError("unable to kill process %i", - int(pid)); - - exit(EXIT_SUCCESS); -} - -void -daemonize_close_stdin(void) -{ - close(STDIN_FILENO); - open("/dev/null", O_RDONLY); -} - -void -daemonize_set_user(void) -{ - if (user_name == nullptr) - return; - - /* set gid */ - if (user_gid != (gid_t)-1 && user_gid != getgid() && - setgid(user_gid) == -1) { - FormatFatalSystemError("Failed to set group %d", - (int)user_gid); - } - -#ifdef _BSD_SOURCE - /* init supplementary groups - * (must be done before we change our uid) - */ - if (!had_group && - /* no need to set the new user's supplementary groups if - we are already this user */ - user_uid != getuid() && - initgroups(user_name, user_gid) == -1) { - FormatFatalSystemError("Failed to set supplementary groups " - "of user \"%s\"", - user_name); - } -#endif - - /* set uid */ - if (user_uid != (uid_t)-1 && user_uid != getuid() && - setuid(user_uid) == -1) { - FormatFatalSystemError("Failed to set user \"%s\"", - user_name); - } -} - -static void -daemonize_detach(void) -{ - /* flush all file handles before duplicating the buffers */ - - fflush(nullptr); - -#ifdef HAVE_DAEMON - - if (daemon(0, 1)) - FatalSystemError("daemon() failed"); - -#elif defined(HAVE_FORK) - - /* detach from parent process */ - - switch (fork()) { - case -1: - FatalSystemError("fork() failed"); - case 0: - break; - default: - /* exit the parent process */ - _exit(EXIT_SUCCESS); - } - - /* release the current working directory */ - - if (chdir("/") < 0) - FatalError("problems changing to root directory"); - - /* detach from the current session */ - - setsid(); - -#else - FatalError("no support for daemonizing"); -#endif - - LogDebug(daemon_domain, "daemonized"); -} - -void -daemonize(bool detach) -{ - FILE *fp = nullptr; - - if (!pidfile.IsNull()) { - /* do this before daemon'izing so we can fail gracefully if we can't - * write to the pid file */ - LogDebug(daemon_domain, "opening pid file"); - fp = FOpen(pidfile, "w+"); - if (!fp) { - const std::string utf8 = pidfile.ToUTF8(); - FormatFatalSystemError("Failed to create pid file \"%s\"", - pidfile.c_str()); - } - } - - if (detach) - daemonize_detach(); - - if (!pidfile.IsNull()) { - LogDebug(daemon_domain, "writing pid file"); - fprintf(fp, "%lu\n", (unsigned long)getpid()); - fclose(fp); - } -} - -void -daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile) -{ - if (user) { - struct passwd *pwd = getpwnam(user); - if (pwd == nullptr) - FormatFatalError("no such user \"%s\"", user); - - user_uid = pwd->pw_uid; - user_gid = pwd->pw_gid; - - user_name = g_strdup(user); - - /* this is needed by libs such as arts */ - g_setenv("HOME", pwd->pw_dir, true); - } - - if (group) { - struct group *grp = getgrnam(group); - if (grp == nullptr) - FormatFatalError("no such group \"%s\"", group); - user_gid = grp->gr_gid; - had_group = true; - } - - - pidfile = std::move(_pidfile); -} - -void -daemonize_finish(void) -{ - if (!pidfile.IsNull()) { - RemoveFile(pidfile); - pidfile = AllocatedPath::Null(); - } - - g_free(user_name); -} - -#endif diff --git a/src/Daemon.hxx b/src/Daemon.hxx deleted file mode 100644 index ecd090d5b..000000000 --- a/src/Daemon.hxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DAEMON_HXX -#define MPD_DAEMON_HXX - -class AllocatedPath; - -#ifndef WIN32 -void -daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile); -#else -static inline void -daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile) -{ (void)user; (void)group; (void)pidfile; } -#endif - -#ifndef WIN32 -void -daemonize_finish(void); -#else -static inline void -daemonize_finish(void) -{ /* nop */ } -#endif - -/** - * Kill the MPD which is currently running, pid determined from the - * pid file. - */ -#ifndef WIN32 -void -daemonize_kill(void); -#else -#include "system/FatalError.hxx" -static inline void -daemonize_kill(void) -{ - FatalError("--kill is not available on WIN32"); -} -#endif - -/** - * Close stdin (fd 0) and re-open it as /dev/null. - */ -#ifndef WIN32 -void -daemonize_close_stdin(void); -#else -static inline void -daemonize_close_stdin(void) {} -#endif - -/** - * Change to the configured Unix user. - */ -#ifndef WIN32 -void -daemonize_set_user(void); -#else -static inline void -daemonize_set_user(void) -{ /* nop */ } -#endif - -#ifndef WIN32 -void -daemonize(bool detach); -#else -static inline void -daemonize(bool detach) -{ (void)detach; } -#endif - -#endif diff --git a/src/DatabaseError.cxx b/src/DatabaseError.cxx deleted file mode 100644 index 800442b9f..000000000 --- a/src/DatabaseError.cxx +++ /dev/null @@ -1,24 +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 "DatabaseError.hxx" -#include "util/Domain.hxx" - -const Domain db_domain("db"); diff --git a/src/DatabaseError.hxx b/src/DatabaseError.hxx deleted file mode 100644 index c133ee357..000000000 --- a/src/DatabaseError.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_ERROR_HXX -#define MPD_DB_ERROR_HXX - -class Domain; - -enum db_error { - /** - * The database is disabled, i.e. none is configured in this - * MPD instance. - */ - DB_DISABLED, - - DB_NOT_FOUND, -}; - -extern const Domain db_domain; - -#endif diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx deleted file mode 100644 index 013a3e329..000000000 --- a/src/DatabaseGlue.cxx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "DatabaseGlue.hxx" -#include "DatabaseSimple.hxx" -#include "DatabaseRegistry.hxx" -#include "DatabaseSave.hxx" -#include "DatabaseError.hxx" -#include "Directory.hxx" -#include "util/Error.hxx" -#include "ConfigData.hxx" -#include "Stats.hxx" -#include "DatabasePlugin.hxx" -#include "db/SimpleDatabasePlugin.hxx" - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <assert.h> -#include <string.h> -#include <errno.h> - - -static Database *db; -static bool db_is_open; -static bool is_simple; - -bool -DatabaseGlobalInit(const config_param ¶m, Error &error) -{ - assert(db == nullptr); - assert(!db_is_open); - - const char *plugin_name = - param.GetBlockValue("plugin", "simple"); - is_simple = strcmp(plugin_name, "simple") == 0; - - const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); - if (plugin == nullptr) { - error.Format(db_domain, - "No such database plugin: %s", plugin_name); - return false; - } - - db = plugin->create(param, error); - return db != nullptr; -} - -void -DatabaseGlobalDeinit(void) -{ - if (db_is_open) - db->Close(); - - if (db != nullptr) - delete db; -} - -const Database * -GetDatabase() -{ - assert(db == nullptr || db_is_open); - - return db; -} - -const Database * -GetDatabase(Error &error) -{ - assert(db == nullptr || db_is_open); - - if (db == nullptr) - error.Set(db_domain, DB_DISABLED, "No database"); - - return db; -} - -bool -db_is_simple(void) -{ - assert(db == nullptr || db_is_open); - - return is_simple; -} - -Directory * -db_get_root(void) -{ - assert(db != nullptr); - assert(db_is_simple()); - - return ((SimpleDatabase *)db)->GetRoot(); -} - -Directory * -db_get_directory(const char *name) -{ - if (db == nullptr) - return nullptr; - - Directory *music_root = db_get_root(); - if (name == nullptr) - return music_root; - - return music_root->LookupDirectory(name); -} - -bool -db_save(Error &error) -{ - assert(db != nullptr); - assert(db_is_open); - assert(db_is_simple()); - - return ((SimpleDatabase *)db)->Save(error); -} - -bool -DatabaseGlobalOpen(Error &error) -{ - assert(db != nullptr); - assert(!db_is_open); - - if (!db->Open(error)) - return false; - - db_is_open = true; - - stats_update(); - - return true; -} - -bool -db_exists() -{ - assert(db != nullptr); - assert(db_is_open); - assert(db_is_simple()); - - return ((SimpleDatabase *)db)->GetUpdateStamp() > 0; -} diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx deleted file mode 100644 index bc05942e0..000000000 --- a/src/DatabaseGlue.hxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_GLUE_HXX -#define MPD_DATABASE_GLUE_HXX - -#include "Compiler.h" - -struct config_param; -class Database; -class Error; - -/** - * Initialize the database library. - * - * @param param the database configuration block - */ -bool -DatabaseGlobalInit(const config_param ¶m, Error &error); - -void -DatabaseGlobalDeinit(void); - -bool -DatabaseGlobalOpen(Error &error); - -/** - * Returns the global #Database instance. May return nullptr if this MPD - * configuration has no database (no music_directory was configured). - */ -gcc_pure -const Database * -GetDatabase(); - -/** - * Returns the global #Database instance. May return nullptr if this MPD - * configuration has no database (no music_directory was configured). - */ -gcc_pure -const Database * -GetDatabase(Error &error); - -#endif diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx deleted file mode 100644 index 4202b1247..000000000 --- a/src/DatabaseHelpers.cxx +++ /dev/null @@ -1,134 +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 "DatabaseHelpers.hxx" -#include "DatabasePlugin.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" - -#include <functional> -#include <set> - -#include <string.h> - -struct StringLess { - gcc_pure - bool operator()(const char *a, const char *b) const { - return strcmp(a, b) < 0; - } -}; - -typedef std::set<const char *, StringLess> StringSet; - -static bool -CollectTags(StringSet &set, TagType tag_type, Song &song) -{ - Tag *tag = song.tag; - if (tag == nullptr) - return true; - - bool found = false; - for (unsigned i = 0; i < tag->num_items; ++i) { - if (tag->items[i]->type == tag_type) { - set.insert(tag->items[i]->value); - found = true; - } - } - - if (!found) - set.insert(""); - - return true; -} - -bool -VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) -{ - StringSet set; - - using namespace std::placeholders; - const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1); - if (!db.Visit(selection, f, error)) - return false; - - for (auto value : set) - if (!visit_string(value, error)) - return false; - - return true; -} - -static void -StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, - const Tag &tag) -{ - if (tag.time > 0) - stats.total_duration += tag.time; - - for (unsigned i = 0; i < tag.num_items; ++i) { - const TagItem &item = *tag.items[i]; - - switch (item.type) { - case TAG_ARTIST: - artists.insert(item.value); - break; - - case TAG_ALBUM: - albums.insert(item.value); - break; - - default: - break; - } - } -} - -static bool -StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums, - Song &song) -{ - ++stats.song_count; - - if (song.tag != nullptr) - StatsVisitTag(stats, artists, albums, *song.tag); - - return true; -} - -bool -GetStats(const Database &db, const DatabaseSelection &selection, - DatabaseStats &stats, Error &error) -{ - stats.Clear(); - - StringSet artists, albums; - using namespace std::placeholders; - const auto f = std::bind(StatsVisitSong, - std::ref(stats), std::ref(artists), - std::ref(albums), _1); - if (!db.Visit(selection, f, error)) - return false; - - stats.artist_count = artists.size(); - stats.album_count = albums.size(); - return true; -} diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx deleted file mode 100644 index d8806bc69..000000000 --- a/src/DatabaseHelpers.hxx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MEMORY_DATABASE_PLUGIN_HXX -#define MPD_MEMORY_DATABASE_PLUGIN_HXX - -#include "DatabaseVisitor.hxx" -#include "tag/TagType.h" -#include "Compiler.h" - -class Error; -class Database; -struct DatabaseSelection; -struct DatabaseStats; - -bool -VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error); - -bool -GetStats(const Database &db, const DatabaseSelection &selection, - DatabaseStats &stats, Error &error); - -#endif diff --git a/src/DatabaseLock.cxx b/src/DatabaseLock.cxx deleted file mode 100644 index d85f72d3b..000000000 --- a/src/DatabaseLock.cxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "DatabaseLock.hxx" -#include "Compiler.h" - -Mutex db_mutex; - -#ifndef NDEBUG -ThreadId db_mutex_holder; -#endif diff --git a/src/DatabaseLock.hxx b/src/DatabaseLock.hxx deleted file mode 100644 index 1bd5cbe61..000000000 --- a/src/DatabaseLock.hxx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Support for locking data structures from the database, for safe - * multi-threading. - */ - -#ifndef MPD_DB_LOCK_HXX -#define MPD_DB_LOCK_HXX - -#include "check.h" -#include "thread/Mutex.hxx" -#include "Compiler.h" - -#include <assert.h> - -extern Mutex db_mutex; - -#ifndef NDEBUG - -#include "thread/Id.hxx" - -extern ThreadId db_mutex_holder; - -/** - * Does the current thread hold the database lock? - */ -gcc_pure -static inline bool -holding_db_lock(void) -{ - return db_mutex_holder.IsInside(); -} - -#endif - -/** - * Obtain the global database lock. This is needed before - * dereferencing a #song or #directory. It is not recursive. - */ -static inline void -db_lock(void) -{ - assert(!holding_db_lock()); - - db_mutex.lock(); - - assert(db_mutex_holder.IsNull()); -#ifndef NDEBUG - db_mutex_holder = ThreadId::GetCurrent(); -#endif -} - -/** - * Release the global database lock. - */ -static inline void -db_unlock(void) -{ - assert(holding_db_lock()); -#ifndef NDEBUG - db_mutex_holder = ThreadId::Null(); -#endif - - db_mutex.unlock(); -} - -class ScopeDatabaseLock { -public: - ScopeDatabaseLock() { - db_lock(); - } - - ~ScopeDatabaseLock() { - db_unlock(); - } -}; - -#endif diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx deleted file mode 100644 index b0cb19589..000000000 --- a/src/DatabasePlaylist.cxx +++ /dev/null @@ -1,50 +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 "DatabasePlaylist.hxx" -#include "DatabaseSelection.hxx" -#include "PlaylistFile.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" - -#include <functional> - -static bool -AddSong(const char *playlist_path_utf8, - Song &song, Error &error) -{ - return spl_append_song(playlist_path_utf8, song, error); -} - -bool -search_add_to_playlist(const char *uri, const char *playlist_path_utf8, - const SongFilter *filter, - Error &error) -{ - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - const DatabaseSelection selection(uri, true, filter); - - using namespace std::placeholders; - const auto f = std::bind(AddSong, playlist_path_utf8, _1, _2); - return db->Visit(selection, f, error); -} diff --git a/src/DatabasePlaylist.hxx b/src/DatabasePlaylist.hxx deleted file mode 100644 index d5f1648d5..000000000 --- a/src/DatabasePlaylist.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_PLAYLIST_HXX -#define MPD_DATABASE_PLAYLIST_HXX - -#include "Compiler.h" - -class SongFilter; -class Error; - -gcc_nonnull(1,2) -bool -search_add_to_playlist(const char *uri, const char *path_utf8, - const SongFilter *filter, - Error &error); - -#endif diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx deleted file mode 100644 index ccf899389..000000000 --- a/src/DatabasePlugin.hxx +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the db_plugin class. It describes a - * plugin API for databases of song metadata. - */ - -#ifndef MPD_DATABASE_PLUGIN_HXX -#define MPD_DATABASE_PLUGIN_HXX - -#include "DatabaseVisitor.hxx" -#include "tag/TagType.h" -#include "Compiler.h" - -#include <time.h> - -struct config_param; -struct DatabaseSelection; -struct db_visitor; -struct Song; -class Error; - -struct DatabaseStats { - /** - * Number of songs. - */ - unsigned song_count; - - /** - * Total duration of all songs (in seconds). - */ - unsigned long total_duration; - - /** - * Number of distinct artist names. - */ - unsigned artist_count; - - /** - * Number of distinct album names. - */ - unsigned album_count; - - void Clear() { - song_count = 0; - total_duration = 0; - artist_count = album_count = 0; - } -}; - -class Database { -public: - /** - * Free instance data. - */ - virtual ~Database() {} - - /** - * Open the database. Read it into memory if applicable. - */ - virtual bool Open(gcc_unused Error &error) { - return true; - } - - /** - * Close the database, free allocated memory. - */ - virtual void Close() {} - - /** - * Look up a song (including tag data) in the database. When - * you don't need this anymore, call ReturnSong(). - * - * @param uri_utf8 the URI of the song within the music - * directory (UTF-8) - */ - virtual Song *GetSong(const char *uri_utf8, - Error &error) const = 0; - - /** - * Mark the song object as "unused". Call this on objects - * returned by GetSong(). - */ - virtual void ReturnSong(Song *song) const = 0; - - /** - * Visit the selected entities. - */ - virtual bool Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const = 0; - - bool Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - Error &error) const { - return Visit(selection, visit_directory, visit_song, - VisitPlaylist(), error); - } - - bool Visit(const DatabaseSelection &selection, VisitSong visit_song, - Error &error) const { - return Visit(selection, VisitDirectory(), visit_song, error); - } - - /** - * Visit all unique tag values. - */ - virtual bool VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) const = 0; - - virtual bool GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, - Error &error) const = 0; - - /** - * Returns the time stamp of the last database update. - * Returns 0 if that is not not known/available. - */ - gcc_pure - virtual time_t GetUpdateStamp() const = 0; -}; - -struct DatabasePlugin { - const char *name; - - /** - * Allocates and configures a database. - */ - Database *(*create)(const config_param ¶m, - Error &error); -}; - -#endif diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx deleted file mode 100644 index 3732e98f3..000000000 --- a/src/DatabasePrint.cxx +++ /dev/null @@ -1,241 +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 "DatabasePrint.hxx" -#include "DatabaseSelection.hxx" -#include "SongFilter.hxx" -#include "PlaylistVector.hxx" -#include "SongPrint.hxx" -#include "TimePrint.hxx" -#include "Directory.hxx" -#include "Client.hxx" -#include "tag/Tag.hxx" -#include "Song.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" - -#include <functional> - -static bool -PrintDirectoryBrief(Client &client, const Directory &directory) -{ - if (!directory.IsRoot()) - client_printf(client, "directory: %s\n", directory.GetPath()); - - return true; -} - -static bool -PrintDirectoryFull(Client &client, const Directory &directory) -{ - if (!directory.IsRoot()) { - client_printf(client, "directory: %s\n", directory.GetPath()); - time_print(client, "Last-Modified", directory.mtime); - } - - return true; -} - - -static void -print_playlist_in_directory(Client &client, - const Directory &directory, - const char *name_utf8) -{ - if (directory.IsRoot()) - client_printf(client, "playlist: %s\n", name_utf8); - else - client_printf(client, "playlist: %s/%s\n", - directory.GetPath(), name_utf8); -} - -static bool -PrintSongBrief(Client &client, const Song &song) -{ - assert(song.parent != nullptr); - - song_print_uri(client, song); - - if (song.tag != nullptr && song.tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, *song.parent, song.uri); - - return true; -} - -static bool -PrintSongFull(Client &client, const Song &song) -{ - assert(song.parent != nullptr); - - song_print_info(client, song); - - if (song.tag != nullptr && song.tag->has_playlist) - /* this song file has an embedded CUE sheet */ - print_playlist_in_directory(client, *song.parent, song.uri); - - return true; -} - -static bool -PrintPlaylistBrief(Client &client, - const PlaylistInfo &playlist, - const Directory &directory) -{ - print_playlist_in_directory(client, directory, playlist.name.c_str()); - return true; -} - -static bool -PrintPlaylistFull(Client &client, - const PlaylistInfo &playlist, - const Directory &directory) -{ - print_playlist_in_directory(client, directory, playlist.name.c_str()); - - if (playlist.mtime > 0) - time_print(client, "Last-Modified", playlist.mtime); - - return true; -} - -bool -db_selection_print(Client &client, const DatabaseSelection &selection, - bool full, Error &error) -{ - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - using namespace std::placeholders; - const auto d = selection.filter == nullptr - ? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief, - std::ref(client), _1) - : VisitDirectory(); - const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, - std::ref(client), _1); - const auto p = selection.filter == nullptr - ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, - std::ref(client), _1, _2) - : VisitPlaylist(); - - return db->Visit(selection, d, s, p, error); -} - -struct SearchStats { - int numberOfSongs; - unsigned long playTime; -}; - -static void printSearchStats(Client &client, SearchStats *stats) -{ - client_printf(client, "songs: %i\n", stats->numberOfSongs); - client_printf(client, "playtime: %li\n", stats->playTime); -} - -static bool -stats_visitor_song(SearchStats &stats, Song &song) -{ - stats.numberOfSongs++; - stats.playTime += song.GetDuration(); - - return true; -} - -bool -searchStatsForSongsIn(Client &client, const char *name, - const SongFilter *filter, - Error &error) -{ - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - const DatabaseSelection selection(name, true, filter); - - SearchStats stats; - stats.numberOfSongs = 0; - stats.playTime = 0; - - using namespace std::placeholders; - const auto f = std::bind(stats_visitor_song, std::ref(stats), - _1); - if (!db->Visit(selection, f, error)) - return false; - - printSearchStats(client, &stats); - return true; -} - -bool -printAllIn(Client &client, const char *uri_utf8, Error &error) -{ - const DatabaseSelection selection(uri_utf8, true); - return db_selection_print(client, selection, false, error); -} - -bool -printInfoForAllIn(Client &client, const char *uri_utf8, - Error &error) -{ - const DatabaseSelection selection(uri_utf8, true); - return db_selection_print(client, selection, true, error); -} - -static bool -PrintSongURIVisitor(Client &client, Song &song) -{ - song_print_uri(client, song); - - return true; -} - -static bool -PrintUniqueTag(Client &client, TagType tag_type, - const char *value) -{ - client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); - return true; -} - -bool -listAllUniqueTags(Client &client, int type, - const SongFilter *filter, - Error &error) -{ - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - const DatabaseSelection selection("", true, filter); - - if (type == LOCATE_TAG_FILE_TYPE) { - using namespace std::placeholders; - const auto f = std::bind(PrintSongURIVisitor, - std::ref(client), _1); - return db->Visit(selection, f, error); - } else { - using namespace std::placeholders; - const auto f = std::bind(PrintUniqueTag, std::ref(client), - (TagType)type, _1); - return db->VisitUniqueTags(selection, (TagType)type, - f, error); - } -} diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx deleted file mode 100644 index 36a68d87b..000000000 --- a/src/DatabasePrint.hxx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DB_PRINT_H -#define MPD_DB_PRINT_H - -#include "Compiler.h" - -class SongFilter; -struct DatabaseSelection; -struct db_visitor; -class Client; -class Error; - -bool -db_selection_print(Client &client, const DatabaseSelection &selection, - bool full, Error &error); - -gcc_nonnull(2) -bool -printAllIn(Client &client, const char *uri_utf8, Error &error); - -gcc_nonnull(2) -bool -printInfoForAllIn(Client &client, const char *uri_utf8, - Error &error); - -gcc_nonnull(2) -bool -searchStatsForSongsIn(Client &client, const char *name, - const SongFilter *filter, - Error &error); - -bool -listAllUniqueTags(Client &client, int type, - const SongFilter *filter, - Error &error); - -#endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx deleted file mode 100644 index 98f80c5b6..000000000 --- a/src/DatabaseQueue.cxx +++ /dev/null @@ -1,54 +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 "DatabaseQueue.hxx" -#include "DatabaseSelection.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "Partition.hxx" -#include "util/Error.hxx" - -#include <functional> - -static bool -AddToQueue(Partition &partition, Song &song, Error &error) -{ - PlaylistResult result = - partition.playlist.AppendSong(partition.pc, &song, nullptr); - if (result != PlaylistResult::SUCCESS) { - error.Set(playlist_domain, int(result), "Playlist error"); - return false; - } - - return true; -} - -bool -AddFromDatabase(Partition &partition, const DatabaseSelection &selection, - Error &error) -{ - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - using namespace std::placeholders; - const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2); - return db->Visit(selection, f, error); -} diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx deleted file mode 100644 index 4faa708c2..000000000 --- a/src/DatabaseQueue.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_QUEUE_HXX -#define MPD_DATABASE_QUEUE_HXX - -struct Partition; -struct DatabaseSelection; -class Error; - -bool -AddFromDatabase(Partition &partition, const DatabaseSelection &selection, - Error &error); - -#endif diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx deleted file mode 100644 index 4ce4a62cb..000000000 --- a/src/DatabaseRegistry.cxx +++ /dev/null @@ -1,43 +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 "DatabaseRegistry.hxx" -#include "db/SimpleDatabasePlugin.hxx" -#include "db/ProxyDatabasePlugin.hxx" - -#include <string.h> - -const DatabasePlugin *const database_plugins[] = { - &simple_db_plugin, -#ifdef HAVE_LIBMPDCLIENT - &proxy_db_plugin, -#endif - nullptr -}; - -const DatabasePlugin * -GetDatabasePluginByName(const char *name) -{ - for (auto i = database_plugins; *i != nullptr; ++i) - if (strcmp((*i)->name, name) == 0) - return *i; - - return nullptr; -} diff --git a/src/DatabaseRegistry.hxx b/src/DatabaseRegistry.hxx deleted file mode 100644 index d8c5dff12..000000000 --- a/src/DatabaseRegistry.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_REGISTRY_HXX -#define MPD_DATABASE_REGISTRY_HXX - -#include "Compiler.h" - -struct DatabasePlugin; - -/** - * nullptr terminated list of all database plugins which were enabled at - * compile time. - */ -extern const DatabasePlugin *const database_plugins[]; - -gcc_pure -const DatabasePlugin * -GetDatabasePluginByName(const char *name); - -#endif diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx deleted file mode 100644 index abfd4a34f..000000000 --- a/src/DatabaseSave.cxx +++ /dev/null @@ -1,157 +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 "DatabaseSave.hxx" -#include "DatabaseLock.hxx" -#include "DatabaseError.hxx" -#include "Directory.hxx" -#include "DirectorySave.hxx" -#include "Song.hxx" -#include "TextFile.hxx" -#include "tag/Tag.hxx" -#include "tag/TagSettings.h" -#include "fs/Charset.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -#define DIRECTORY_INFO_BEGIN "info_begin" -#define DIRECTORY_INFO_END "info_end" -#define DB_FORMAT_PREFIX "format: " -#define DIRECTORY_MPD_VERSION "mpd_version: " -#define DIRECTORY_FS_CHARSET "fs_charset: " -#define DB_TAG_PREFIX "tag: " - -static constexpr unsigned DB_FORMAT = 1; - -void -db_save_internal(FILE *fp, const Directory &music_root) -{ - fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); - fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); - fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); - fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, GetFSCharset()); - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (!ignore_tag_items[i]) - fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); - - fprintf(fp, "%s\n", DIRECTORY_INFO_END); - - directory_save(fp, music_root); -} - -bool -db_load_internal(TextFile &file, Directory &music_root, Error &error) -{ - char *line; - unsigned format = 0; - bool found_charset = false, found_version = false; - bool success; - bool tags[TAG_NUM_OF_ITEM_TYPES]; - - /* get initial info */ - line = file.ReadLine(); - if (line == nullptr || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { - error.Set(db_domain, "Database corrupted"); - return false; - } - - memset(tags, false, sizeof(tags)); - - while ((line = file.ReadLine()) != nullptr && - strcmp(line, DIRECTORY_INFO_END) != 0) { - if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { - format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); - } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { - if (found_version) { - error.Set(db_domain, "Duplicate version line"); - return false; - } - - found_version = true; - } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { - const char *new_charset; - - if (found_charset) { - error.Set(db_domain, "Duplicate charset line"); - return false; - } - - found_charset = true; - - new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; - const char *const old_charset = GetFSCharset(); - if (*old_charset != 0 - && strcmp(new_charset, old_charset) != 0) { - error.Format(db_domain, - "Existing database has charset " - "\"%s\" instead of \"%s\"; " - "discarding database file", - new_charset, old_charset); - return false; - } - } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { - const char *name = line + sizeof(DB_TAG_PREFIX) - 1; - TagType tag = tag_name_parse(name); - if (tag == TAG_NUM_OF_ITEM_TYPES) { - error.Format(db_domain, - "Unrecognized tag '%s', " - "discarding database file", - name); - return false; - } - - tags[tag] = true; - } else { - error.Format(db_domain, "Malformed line: %s", line); - return false; - } - } - - if (format != DB_FORMAT) { - error.Set(db_domain, - "Database format mismatch, " - "discarding database file"); - return false; - } - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - if (!ignore_tag_items[i] && !tags[i]) { - error.Set(db_domain, - "Tag list mismatch, " - "discarding database file"); - return false; - } - } - - LogDebug(db_domain, "reading DB"); - - db_lock(); - success = directory_load(file, music_root, error); - db_unlock(); - - return success; -} diff --git a/src/DatabaseSave.hxx b/src/DatabaseSave.hxx deleted file mode 100644 index 741189973..000000000 --- a/src/DatabaseSave.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_SAVE_HXX -#define MPD_DATABASE_SAVE_HXX - -#include <stdio.h> - -struct Directory; -class TextFile; -class Error; - -void -db_save_internal(FILE *file, const Directory &root); - -bool -db_load_internal(TextFile &file, Directory &root, Error &error); - -#endif diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx deleted file mode 100644 index ffe5f7d39..000000000 --- a/src/DatabaseSelection.cxx +++ /dev/null @@ -1,49 +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 "DatabaseSelection.hxx" -#include "SongFilter.hxx" - -DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive, - const SongFilter *_filter) - :uri(_uri), recursive(_recursive), filter(_filter) -{ - /* optimization: if the caller didn't specify a base URI, pick - the one from SongFilter */ - if (uri.empty() && filter != nullptr) - uri = filter->GetBase(); -} - -bool -DatabaseSelection::IsEmpty() const -{ - return uri.empty() && (filter == nullptr || filter->IsEmpty()); -} - -bool -DatabaseSelection::HasOtherThanBase() const -{ - return filter != nullptr && filter->HasOtherThanBase(); -} - -bool -DatabaseSelection::Match(const Song &song) const -{ - return filter == nullptr || filter->Match(song); -} diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx deleted file mode 100644 index 4825c738c..000000000 --- a/src/DatabaseSelection.hxx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_SELECTION_HXX -#define MPD_DATABASE_SELECTION_HXX - -#include "Compiler.h" - -#include <string> - -class SongFilter; -struct Song; - -struct DatabaseSelection { - /** - * The base URI of the search (UTF-8). Must not begin or end - * with a slash. An empty string searches the whole database. - */ - std::string uri; - - /** - * Recursively search all sub directories? - */ - bool recursive; - - const SongFilter *filter; - - DatabaseSelection(const char *_uri, bool _recursive, - const SongFilter *_filter=nullptr); - - gcc_pure - bool IsEmpty() const; - - /** - * Does this selection contain constraints other than "base"? - */ - gcc_pure - bool HasOtherThanBase() const; - - gcc_pure - bool Match(const Song &song) const; -}; - -#endif diff --git a/src/DatabaseSimple.hxx b/src/DatabaseSimple.hxx deleted file mode 100644 index 6d52ac0b3..000000000 --- a/src/DatabaseSimple.hxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_SIMPLE_HXX -#define MPD_DATABASE_SIMPLE_HXX - -#include "Compiler.h" - -#include <sys/time.h> - -struct config_param; -struct Directory; -struct db_selection; -struct db_visitor; -class Error; - -/** - * Check whether the default #SimpleDatabasePlugin is used. This - * allows using db_get_root(), db_save(), db_get_mtime() and - * db_exists(). - */ -bool -db_is_simple(void); - -/** - * Returns the root directory object. Returns NULL if there is no - * configured music directory. - * - * May only be used if db_is_simple() returns true. - */ -gcc_pure -Directory * -db_get_root(void); - -/** - * Caller must lock the #db_mutex. - */ -gcc_nonnull(1) -gcc_pure -Directory * -db_get_directory(const char *name); - -/** - * May only be used if db_is_simple() returns true. - */ -bool -db_save(Error &error); - -/** - * Returns true if there is a valid database file on the disk. - * - * May only be used if db_is_simple() returns true. - */ -gcc_pure -bool -db_exists(); - -#endif diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx deleted file mode 100644 index 256b08442..000000000 --- a/src/DatabaseVisitor.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DATABASE_VISITOR_HXX -#define MPD_DATABASE_VISITOR_HXX - -#include <functional> - -struct Directory; -struct Song; -struct PlaylistInfo; -class Error; - -typedef std::function<bool(const Directory &, Error &)> VisitDirectory; -typedef std::function<bool(struct Song &, Error &)> VisitSong; -typedef std::function<bool(const PlaylistInfo &, const Directory &, - Error &)> VisitPlaylist; - -typedef std::function<bool(const char *, Error &)> VisitString; - -#endif diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx deleted file mode 100644 index e4122d60e..000000000 --- a/src/DecoderAPI.cxx +++ /dev/null @@ -1,581 +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 "DecoderAPI.hxx" -#include "DecoderError.hxx" -#include "AudioConfig.hxx" -#include "ReplayGainConfig.hxx" -#include "MusicChunk.hxx" -#include "MusicBuffer.hxx" -#include "MusicPipe.hxx" -#include "DecoderControl.hxx" -#include "DecoderInternal.hxx" -#include "Song.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> - -void -decoder_initialized(Decoder &decoder, - const AudioFormat audio_format, - bool seekable, float total_time) -{ - DecoderControl &dc = decoder.dc; - struct audio_format_string af_string; - - assert(dc.state == DecoderState::START); - assert(dc.pipe != nullptr); - assert(decoder.stream_tag == nullptr); - assert(decoder.decoder_tag == nullptr); - assert(!decoder.seeking); - assert(audio_format.IsDefined()); - assert(audio_format.IsValid()); - - dc.in_audio_format = audio_format; - dc.out_audio_format = getOutputAudioFormat(audio_format); - - dc.seekable = seekable; - dc.total_time = total_time; - - dc.Lock(); - dc.state = DecoderState::DECODE; - dc.client_cond.signal(); - dc.Unlock(); - - FormatDebug(decoder_domain, "audio_format=%s, seekable=%s", - audio_format_to_string(dc.in_audio_format, &af_string), - seekable ? "true" : "false"); - - if (dc.in_audio_format != dc.out_audio_format) - FormatDebug(decoder_domain, "converting to %s", - audio_format_to_string(dc.out_audio_format, - &af_string)); -} - -/** - * Checks if we need an "initial seek". If so, then the initial seek - * is prepared, and the function returns true. - */ -gcc_pure -static bool -decoder_prepare_initial_seek(Decoder &decoder) -{ - const DecoderControl &dc = decoder.dc; - assert(dc.pipe != nullptr); - - if (dc.state != DecoderState::DECODE) - /* wait until the decoder has finished initialisation - (reading file headers etc.) before emitting the - virtual "SEEK" command */ - return false; - - if (decoder.initial_seek_running) - /* initial seek has already begun - override any other - command */ - return true; - - if (decoder.initial_seek_pending) { - if (!dc.seekable) { - /* seeking is not possible */ - decoder.initial_seek_pending = false; - return false; - } - - if (dc.command == DecoderCommand::NONE) { - /* begin initial seek */ - - decoder.initial_seek_pending = false; - decoder.initial_seek_running = true; - return true; - } - - /* skip initial seek when there's another command - (e.g. STOP) */ - - decoder.initial_seek_pending = false; - } - - return false; -} - -/** - * Returns the current decoder command. May return a "virtual" - * synthesized command, e.g. to seek to the beginning of the CUE - * track. - */ -gcc_pure -static DecoderCommand -decoder_get_virtual_command(Decoder &decoder) -{ - const DecoderControl &dc = decoder.dc; - assert(dc.pipe != nullptr); - - if (decoder_prepare_initial_seek(decoder)) - return DecoderCommand::SEEK; - - return dc.command; -} - -DecoderCommand -decoder_get_command(Decoder &decoder) -{ - return decoder_get_virtual_command(decoder); -} - -void -decoder_command_finished(Decoder &decoder) -{ - DecoderControl &dc = decoder.dc; - - dc.Lock(); - - assert(dc.command != DecoderCommand::NONE || - decoder.initial_seek_running); - assert(dc.command != DecoderCommand::SEEK || - decoder.initial_seek_running || - dc.seek_error || decoder.seeking); - assert(dc.pipe != nullptr); - - if (decoder.initial_seek_running) { - assert(!decoder.seeking); - assert(decoder.chunk == nullptr); - assert(dc.pipe->IsEmpty()); - - decoder.initial_seek_running = false; - decoder.timestamp = dc.start_ms / 1000.; - dc.Unlock(); - return; - } - - if (decoder.seeking) { - decoder.seeking = false; - - /* delete frames from the old song position */ - - if (decoder.chunk != nullptr) { - dc.buffer->Return(decoder.chunk); - decoder.chunk = nullptr; - } - - dc.pipe->Clear(*dc.buffer); - - decoder.timestamp = dc.seek_where; - } - - dc.command = DecoderCommand::NONE; - dc.client_cond.signal(); - dc.Unlock(); -} - -double decoder_seek_where(gcc_unused Decoder & decoder) -{ - const DecoderControl &dc = decoder.dc; - - assert(dc.pipe != nullptr); - - if (decoder.initial_seek_running) - return dc.start_ms / 1000.; - - assert(dc.command == DecoderCommand::SEEK); - - decoder.seeking = true; - - return dc.seek_where; -} - -void decoder_seek_error(Decoder & decoder) -{ - DecoderControl &dc = decoder.dc; - - assert(dc.pipe != nullptr); - - if (decoder.initial_seek_running) { - /* d'oh, we can't seek to the sub-song start position, - what now? - no idea, ignoring the problem for now. */ - decoder.initial_seek_running = false; - return; - } - - assert(dc.command == DecoderCommand::SEEK); - - dc.seek_error = true; - decoder.seeking = false; - - decoder_command_finished(decoder); -} - -/** - * Should be read operation be cancelled? That is the case when the - * player thread has sent a command such as "STOP". - */ -gcc_pure -static inline bool -decoder_check_cancel_read(const Decoder *decoder) -{ - if (decoder == nullptr) - return false; - - const DecoderControl &dc = decoder->dc; - if (dc.command == DecoderCommand::NONE) - return false; - - /* ignore the SEEK command during initialization, the plugin - should handle that after it has initialized successfully */ - if (dc.command == DecoderCommand::SEEK && - (dc.state == DecoderState::START || decoder->seeking)) - return false; - - return true; -} - -size_t -decoder_read(Decoder *decoder, - InputStream &is, - void *buffer, size_t length) -{ - /* XXX don't allow decoder==nullptr */ - - assert(decoder == nullptr || - decoder->dc.state == DecoderState::START || - decoder->dc.state == DecoderState::DECODE); - assert(buffer != nullptr); - - if (length == 0) - return 0; - - is.Lock(); - - while (true) { - if (decoder_check_cancel_read(decoder)) { - is.Unlock(); - return 0; - } - - if (is.IsAvailable()) - break; - - is.cond.wait(is.mutex); - } - - Error error; - size_t nbytes = is.Read(buffer, length, error); - assert(nbytes == 0 || !error.IsDefined()); - assert(nbytes > 0 || error.IsDefined() || is.IsEOF()); - - is.Unlock(); - - if (gcc_unlikely(nbytes == 0 && error.IsDefined())) - LogError(error); - - return nbytes; -} - -bool -decoder_read_full(Decoder *decoder, InputStream &is, - void *_buffer, size_t size) -{ - uint8_t *buffer = (uint8_t *)_buffer; - - while (size > 0) { - size_t nbytes = decoder_read(decoder, is, buffer, size); - if (nbytes == 0) - return false; - - buffer += nbytes; - size -= nbytes; - } - - return true; -} - -bool -decoder_skip(Decoder *decoder, InputStream &is, size_t size) -{ - while (size > 0) { - char buffer[1024]; - size_t nbytes = decoder_read(decoder, is, buffer, - std::min(sizeof(buffer), size)); - if (nbytes == 0) - return false; - - size -= nbytes; - } - - return true; -} - -void -decoder_timestamp(Decoder &decoder, double t) -{ - assert(t >= 0); - - decoder.timestamp = t; -} - -/** - * Sends a #tag as-is to the music pipe. Flushes the current chunk - * (decoder.chunk) if there is one. - */ -static DecoderCommand -do_send_tag(Decoder &decoder, const Tag &tag) -{ - struct music_chunk *chunk; - - if (decoder.chunk != nullptr) { - /* there is a partial chunk - flush it, we want the - tag in a new chunk */ - decoder_flush_chunk(decoder); - } - - assert(decoder.chunk == nullptr); - - chunk = decoder_get_chunk(decoder); - if (chunk == nullptr) { - assert(decoder.dc.command != DecoderCommand::NONE); - return decoder.dc.command; - } - - chunk->tag = new Tag(tag); - return DecoderCommand::NONE; -} - -static bool -update_stream_tag(Decoder &decoder, InputStream *is) -{ - Tag *tag; - - tag = is != nullptr - ? is->LockReadTag() - : nullptr; - if (tag == nullptr) { - tag = decoder.song_tag; - if (tag == nullptr) - return false; - - /* no stream tag present - submit the song tag - instead */ - decoder.song_tag = nullptr; - } - - delete decoder.stream_tag; - decoder.stream_tag = tag; - return true; -} - -DecoderCommand -decoder_data(Decoder &decoder, - InputStream *is, - const void *data, size_t length, - uint16_t kbit_rate) -{ - DecoderControl &dc = decoder.dc; - DecoderCommand cmd; - - assert(dc.state == DecoderState::DECODE); - assert(dc.pipe != nullptr); - assert(length % dc.in_audio_format.GetFrameSize() == 0); - - dc.Lock(); - cmd = decoder_get_virtual_command(decoder); - dc.Unlock(); - - if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK || - length == 0) - return cmd; - - /* send stream tags */ - - if (update_stream_tag(decoder, is)) { - if (decoder.decoder_tag != nullptr) { - /* merge with tag from decoder plugin */ - Tag *tag = Tag::Merge(*decoder.decoder_tag, - *decoder.stream_tag); - cmd = do_send_tag(decoder, *tag); - delete tag; - } else - /* send only the stream tag */ - cmd = do_send_tag(decoder, *decoder.stream_tag); - - if (cmd != DecoderCommand::NONE) - return cmd; - } - - if (dc.in_audio_format != dc.out_audio_format) { - Error error; - data = decoder.conv_state.Convert(dc.in_audio_format, - data, length, - dc.out_audio_format, - &length, - error); - if (data == nullptr) { - /* the PCM conversion has failed - stop - playback, since we have no better way to - bail out */ - LogError(error); - return DecoderCommand::STOP; - } - } - - while (length > 0) { - struct music_chunk *chunk; - bool full; - - chunk = decoder_get_chunk(decoder); - if (chunk == nullptr) { - assert(dc.command != DecoderCommand::NONE); - return dc.command; - } - - const auto dest = - chunk->Write(dc.out_audio_format, - decoder.timestamp - - dc.song->start_ms / 1000.0, - kbit_rate); - if (dest.IsNull()) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - continue; - } - - size_t nbytes = dest.size; - assert(nbytes > 0); - - if (nbytes > length) - nbytes = length; - - /* copy the buffer */ - - memcpy(dest.data, data, nbytes); - - /* expand the music pipe chunk */ - - full = chunk->Expand(dc.out_audio_format, nbytes); - if (full) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - } - - data = (const uint8_t *)data + nbytes; - length -= nbytes; - - decoder.timestamp += (double)nbytes / - dc.out_audio_format.GetTimeToSize(); - - if (dc.end_ms > 0 && - decoder.timestamp >= dc.end_ms / 1000.0) - /* the end of this range has been reached: - stop decoding */ - return DecoderCommand::STOP; - } - - return DecoderCommand::NONE; -} - -DecoderCommand -decoder_tag(Decoder &decoder, InputStream *is, - Tag &&tag) -{ - gcc_unused const DecoderControl &dc = decoder.dc; - DecoderCommand cmd; - - assert(dc.state == DecoderState::DECODE); - assert(dc.pipe != nullptr); - - /* save the tag */ - - delete decoder.decoder_tag; - decoder.decoder_tag = new Tag(tag); - - /* check for a new stream tag */ - - update_stream_tag(decoder, is); - - /* check if we're seeking */ - - if (decoder_prepare_initial_seek(decoder)) - /* during initial seek, no music chunk must be created - until seeking is finished; skip the rest of the - function here */ - return DecoderCommand::SEEK; - - /* send tag to music pipe */ - - if (decoder.stream_tag != nullptr) { - /* merge with tag from input stream */ - Tag *merged; - - merged = Tag::Merge(*decoder.stream_tag, - *decoder.decoder_tag); - cmd = do_send_tag(decoder, *merged); - delete merged; - } else - /* send only the decoder tag */ - cmd = do_send_tag(decoder, *decoder.decoder_tag); - - return cmd; -} - -void -decoder_replay_gain(Decoder &decoder, - const ReplayGainInfo *replay_gain_info) -{ - if (replay_gain_info != nullptr) { - static unsigned serial; - if (++serial == 0) - serial = 1; - - if (REPLAY_GAIN_OFF != replay_gain_mode) { - ReplayGainMode rgm = replay_gain_mode; - if (rgm != REPLAY_GAIN_ALBUM) - rgm = REPLAY_GAIN_TRACK; - - const auto &tuple = replay_gain_info->tuples[rgm]; - const auto scale = - tuple.CalculateScale(replay_gain_preamp, - replay_gain_missing_preamp, - replay_gain_limit); - decoder.dc.replay_gain_db = 20.0 * log10f(scale); - } - - decoder.replay_gain_info = *replay_gain_info; - decoder.replay_gain_serial = serial; - - if (decoder.chunk != nullptr) { - /* flush the current chunk because the new - replay gain values affect the following - samples */ - decoder_flush_chunk(decoder); - } - } else - decoder.replay_gain_serial = 0; -} - -void -decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp) -{ - DecoderControl &dc = decoder.dc; - - dc.SetMixRamp(std::move(mix_ramp)); -} diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx deleted file mode 100644 index a9da76305..000000000 --- a/src/DecoderAPI.hxx +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/*! \file - * \brief The MPD Decoder API - * - * This is the public API which is used by decoder plugins to - * communicate with the mpd core. - */ - -#ifndef MPD_DECODER_API_HXX -#define MPD_DECODER_API_HXX - -#include "check.h" -#include "DecoderCommand.hxx" -#include "DecoderPlugin.hxx" -#include "ReplayGainInfo.hxx" -#include "tag/Tag.hxx" -#include "AudioFormat.hxx" -#include "MixRampInfo.hxx" -#include "ConfigData.hxx" - -/** - * Notify the player thread that it has finished initialization and - * that it has read the song's meta data. - * - * @param decoder the decoder object - * @param audio_format the audio format which is going to be sent to - * decoder_data() - * @param seekable true if the song is seekable - * @param total_time the total number of seconds in this song; -1 if unknown - */ -void -decoder_initialized(Decoder &decoder, - AudioFormat audio_format, - bool seekable, float total_time); - -/** - * Determines the pending decoder command. - * - * @param decoder the decoder object - * @return the current command, or DecoderCommand::NONE if there is no - * command pending - */ -gcc_pure -DecoderCommand -decoder_get_command(Decoder &decoder); - -/** - * Called by the decoder when it has performed the requested command - * (dc->command). This function resets dc->command and wakes up the - * player thread. - * - * @param decoder the decoder object - */ -void -decoder_command_finished(Decoder &decoder); - -/** - * Call this when you have received the DecoderCommand::SEEK command. - * - * @param decoder the decoder object - * @return the destination position for the week - */ -gcc_pure -double -decoder_seek_where(Decoder &decoder); - -/** - * Call this instead of decoder_command_finished() when seeking has - * failed. - * - * @param decoder the decoder object - */ -void -decoder_seek_error(Decoder &decoder); - -/** - * Blocking read from the input stream. - * - * @param decoder the decoder object - * @param is the input stream to read from - * @param buffer the destination buffer - * @param length the maximum number of bytes to read - * @return the number of bytes read, or 0 if one of the following - * occurs: end of file; error; command (like SEEK or STOP). - */ -size_t -decoder_read(Decoder *decoder, InputStream &is, - void *buffer, size_t length); - -static inline size_t -decoder_read(Decoder &decoder, InputStream &is, - void *buffer, size_t length) -{ - return decoder_read(&decoder, is, buffer, length); -} - -/** - * Blocking read from the input stream. Attempts to fill the buffer - * completely; there is no partial result. - * - * @return true on success, false on error or command or not enough - * data - */ -bool -decoder_read_full(Decoder *decoder, InputStream &is, - void *buffer, size_t size); - -/** - * Skip data on the #InputStream. - * - * @return true on success, false on error or command - */ -bool -decoder_skip(Decoder *decoder, InputStream &is, size_t size); - -/** - * Sets the time stamp for the next data chunk [seconds]. The MPD - * core automatically counts it up, and a decoder plugin only needs to - * use this function if it thinks that adding to the time stamp based - * on the buffer size won't work. - */ -void -decoder_timestamp(Decoder &decoder, double t); - -/** - * This function is called by the decoder plugin when it has - * successfully decoded block of input data. - * - * @param decoder the decoder object - * @param is an input stream which is buffering while we are waiting - * for the player - * @param data the source buffer - * @param length the number of bytes in the buffer - * @return the current command, or DecoderCommand::NONE if there is no - * command pending - */ -DecoderCommand -decoder_data(Decoder &decoder, InputStream *is, - const void *data, size_t length, - uint16_t kbit_rate); - -static inline DecoderCommand -decoder_data(Decoder &decoder, InputStream &is, - const void *data, size_t length, - uint16_t kbit_rate) -{ - return decoder_data(decoder, &is, data, length, kbit_rate); -} - -/** - * This function is called by the decoder plugin when it has - * successfully decoded a tag. - * - * @param decoder the decoder object - * @param is an input stream which is buffering while we are waiting - * for the player - * @param tag the tag to send - * @return the current command, or DecoderCommand::NONE if there is no - * command pending - */ -DecoderCommand -decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag); - -static inline DecoderCommand -decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag) -{ - return decoder_tag(decoder, &is, std::move(tag)); -} - -/** - * Set replay gain values for the following chunks. - * - * @param decoder the decoder object - * @param rgi the replay_gain_info object; may be nullptr to invalidate - * the previous replay gain values - */ -void -decoder_replay_gain(Decoder &decoder, - const ReplayGainInfo *replay_gain_info); - -/** - * Store MixRamp tags. - * - * @param decoder the decoder object - * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate - * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate - */ -void -decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp); - -#endif diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx deleted file mode 100644 index 4a5125bc5..000000000 --- a/src/DecoderBuffer.cxx +++ /dev/null @@ -1,185 +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 "DecoderBuffer.hxx" -#include "DecoderAPI.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -struct DecoderBuffer { - Decoder *decoder; - InputStream *is; - - /** the allocated size of the buffer */ - size_t size; - - /** the current length of the buffer */ - size_t length; - - /** number of bytes already consumed at the beginning of the - buffer */ - size_t consumed; - - /** the actual buffer (dynamic size) */ - unsigned char data[sizeof(size_t)]; -}; - -DecoderBuffer * -decoder_buffer_new(Decoder *decoder, InputStream &is, - size_t size) -{ - DecoderBuffer *buffer = (DecoderBuffer *) - g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - - assert(size > 0); - - buffer->decoder = decoder; - buffer->is = &is; - buffer->size = size; - buffer->length = 0; - buffer->consumed = 0; - - return buffer; -} - -void -decoder_buffer_free(DecoderBuffer *buffer) -{ - assert(buffer != nullptr); - - g_free(buffer); -} - -const InputStream & -decoder_buffer_get_stream(const DecoderBuffer *buffer) -{ - return *buffer->is; -} - -bool -decoder_buffer_is_empty(const DecoderBuffer *buffer) -{ - return buffer->consumed == buffer->length; -} - -bool -decoder_buffer_is_full(const DecoderBuffer *buffer) -{ - return buffer->consumed == 0 && buffer->length == buffer->size; -} - -void -decoder_buffer_clear(DecoderBuffer *buffer) -{ - buffer->length = buffer->consumed = 0; -} - -static void -decoder_buffer_shift(DecoderBuffer *buffer) -{ - assert(buffer->consumed > 0); - - buffer->length -= buffer->consumed; - memmove(buffer->data, buffer->data + buffer->consumed, buffer->length); - buffer->consumed = 0; -} - -bool -decoder_buffer_fill(DecoderBuffer *buffer) -{ - size_t nbytes; - - if (buffer->consumed > 0) - decoder_buffer_shift(buffer); - - if (buffer->length >= buffer->size) - /* buffer is full */ - return false; - - nbytes = decoder_read(buffer->decoder, *buffer->is, - buffer->data + buffer->length, - buffer->size - buffer->length); - if (nbytes == 0) - /* end of file, I/O error or decoder command - received */ - return false; - - buffer->length += nbytes; - assert(buffer->length <= buffer->size); - - return true; -} - -size_t -decoder_buffer_available(const DecoderBuffer *buffer) -{ - return buffer->length - buffer->consumed;; -} - -const void * -decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r) -{ - if (buffer->consumed >= buffer->length) - /* buffer is empty */ - return nullptr; - - *length_r = buffer->length - buffer->consumed; - return buffer->data + buffer->consumed; -} - -void -decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes) -{ - /* just move the "consumed" pointer - decoder_buffer_shift() - will do the real work later (called by - decoder_buffer_fill()) */ - buffer->consumed += nbytes; - - assert(buffer->consumed <= buffer->length); -} - -bool -decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes) -{ - size_t length; - const void *data; - bool success; - - /* this could probably be optimized by seeking */ - - while (true) { - data = decoder_buffer_read(buffer, &length); - if (data != nullptr) { - if (length > nbytes) - length = nbytes; - decoder_buffer_consume(buffer, length); - nbytes -= length; - if (nbytes == 0) - return true; - } - - success = decoder_buffer_fill(buffer); - if (!success) - return false; - } -} diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx deleted file mode 100644 index 65c6e0d2e..000000000 --- a/src/DecoderBuffer.hxx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_BUFFER_HXX -#define MPD_DECODER_BUFFER_HXX - -#include "Compiler.h" - -#include <stddef.h> - -/** - * This objects handles buffered reads in decoder plugins easily. You - * create a buffer object, and use its high-level methods to fill and - * read it. It will automatically handle shifting the buffer. - */ -struct DecoderBuffer; - -struct Decoder; -struct InputStream; - -/** - * Creates a new buffer. - * - * @param decoder the decoder object, used for decoder_read(), may be nullptr - * @param is the input stream object where we should read from - * @param size the maximum size of the buffer - * @return the new decoder_buffer object - */ -DecoderBuffer * -decoder_buffer_new(Decoder *decoder, InputStream &is, - size_t size); - -/** - * Frees resources used by the decoder_buffer object. - */ -void -decoder_buffer_free(DecoderBuffer *buffer); - -gcc_pure -const InputStream & -decoder_buffer_get_stream(const DecoderBuffer *buffer); - -gcc_pure -bool -decoder_buffer_is_empty(const DecoderBuffer *buffer); - -gcc_pure -bool -decoder_buffer_is_full(const DecoderBuffer *buffer); - -void -decoder_buffer_clear(DecoderBuffer *buffer); - -/** - * Read data from the input_stream and append it to the buffer. - * - * @return true if data was appended; false if there is no data - * available (yet), end of file, I/O error or a decoder command was - * received - */ -bool -decoder_buffer_fill(DecoderBuffer *buffer); - -/** - * How many bytes are stored in the buffer? - */ -gcc_pure -size_t -decoder_buffer_available(const DecoderBuffer *buffer); - -/** - * Reads data from the buffer. This data is not yet consumed, you - * have to call decoder_buffer_consume() to do that. The returned - * buffer becomes invalid after a decoder_buffer_fill() or a - * decoder_buffer_consume() call. - * - * @param buffer the decoder_buffer object - * @param length_r pointer to a size_t where you will receive the - * number of bytes available - * @return a pointer to the read buffer, or nullptr if there is no data - * available - */ -const void * -decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r); - -/** - * Consume (delete, invalidate) a part of the buffer. The "nbytes" - * parameter must not be larger than the length returned by - * decoder_buffer_read(). - * - * @param buffer the decoder_buffer object - * @param nbytes the number of bytes to consume - */ -void -decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes); - -/** - * Skips the specified number of bytes, discarding its data. - * - * @param buffer the decoder_buffer object - * @param nbytes the number of bytes to skip - * @return true on success, false on error - */ -bool -decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes); - -#endif diff --git a/src/DecoderCommand.hxx b/src/DecoderCommand.hxx deleted file mode 100644 index 394f270c2..000000000 --- a/src/DecoderCommand.hxx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_COMMAND_HXX -#define MPD_DECODER_COMMAND_HXX - -#include <stdint.h> - -enum class DecoderCommand : uint8_t { - NONE = 0, - START, - STOP, - SEEK -}; - -#endif diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx deleted file mode 100644 index ab460ced0..000000000 --- a/src/DecoderControl.cxx +++ /dev/null @@ -1,145 +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 "DecoderControl.hxx" -#include "MusicPipe.hxx" -#include "Song.hxx" - -#include <glib.h> - -#include <assert.h> - -DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond) - :mutex(_mutex), client_cond(_client_cond), - state(DecoderState::STOP), - command(DecoderCommand::NONE), - client_is_waiting(false), - song(nullptr), - replay_gain_db(0), replay_gain_prev_db(0) {} - -DecoderControl::~DecoderControl() -{ - ClearError(); - - if (song != nullptr) - song->Free(); -} - -void -DecoderControl::WaitForDecoder() -{ - assert(!client_is_waiting); - client_is_waiting = true; - - client_cond.wait(mutex); - - assert(client_is_waiting); - client_is_waiting = false; -} - -bool -DecoderControl::IsCurrentSong(const Song &_song) const -{ - switch (state) { - case DecoderState::STOP: - case DecoderState::ERROR: - return false; - - case DecoderState::START: - case DecoderState::DECODE: - return SongEquals(*song, _song); - } - - assert(false); - gcc_unreachable(); -} - -void -DecoderControl::Start(Song *_song, - unsigned _start_ms, unsigned _end_ms, - MusicBuffer &_buffer, MusicPipe &_pipe) -{ - assert(_song != nullptr); - assert(_pipe.IsEmpty()); - - if (song != nullptr) - song->Free(); - - song = _song; - start_ms = _start_ms; - end_ms = _end_ms; - buffer = &_buffer; - pipe = &_pipe; - - LockSynchronousCommand(DecoderCommand::START); -} - -void -DecoderControl::Stop() -{ - Lock(); - - if (command != DecoderCommand::NONE) - /* Attempt to cancel the current command. If it's too - late and the decoder thread is already executing - the old command, we'll call STOP again in this - function (see below). */ - SynchronousCommandLocked(DecoderCommand::STOP); - - if (state != DecoderState::STOP && state != DecoderState::ERROR) - SynchronousCommandLocked(DecoderCommand::STOP); - - Unlock(); -} - -bool -DecoderControl::Seek(double where) -{ - assert(state != DecoderState::START); - assert(where >= 0.0); - - if (state == DecoderState::STOP || - state == DecoderState::ERROR || !seekable) - return false; - - seek_where = where; - seek_error = false; - LockSynchronousCommand(DecoderCommand::SEEK); - - return !seek_error; -} - -void -DecoderControl::Quit() -{ - assert(thread.IsDefined()); - - quit = true; - LockAsynchronousCommand(DecoderCommand::STOP); - - thread.Join(); -} - -void -DecoderControl::CycleMixRamp() -{ - previous_mix_ramp = std::move(mix_ramp); - mix_ramp.Clear(); -} diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx deleted file mode 100644 index 863398dca..000000000 --- a/src/DecoderControl.hxx +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_CONTROL_HXX -#define MPD_DECODER_CONTROL_HXX - -#include "DecoderCommand.hxx" -#include "AudioFormat.hxx" -#include "MixRampInfo.hxx" -#include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "thread/Thread.hxx" -#include "util/Error.hxx" - -#include <assert.h> -#include <stdint.h> - -/* damn you, windows.h! */ -#ifdef ERROR -#undef ERROR -#endif - -struct Song; -class MusicBuffer; -class MusicPipe; - -enum class DecoderState : uint8_t { - STOP = 0, - START, - DECODE, - - /** - * The last "START" command failed, because there was an I/O - * error or because no decoder was able to decode the file. - * This state will only come after START; once the state has - * turned to DECODE, by definition no such error can occur. - */ - ERROR, -}; - -struct DecoderControl { - /** - * The handle of the decoder thread. - */ - Thread thread; - - /** - * This lock protects #state and #command. - * - * This is usually a reference to PlayerControl::mutex, so - * that both player thread and decoder thread share a mutex. - * This simplifies synchronization with #cond and - * #client_cond. - */ - Mutex &mutex; - - /** - * Trigger this object after you have modified #command. This - * is also used by the decoder thread to notify the caller - * when it has finished a command. - */ - Cond cond; - - /** - * The trigger of this object's client. It is signalled - * whenever an event occurs. - * - * This is usually a reference to PlayerControl::cond. - */ - Cond &client_cond; - - DecoderState state; - DecoderCommand command; - - /** - * The error that occurred in the decoder thread. This - * attribute is only valid if #state is #DecoderState::ERROR. - * The object must be freed when this object transitions to - * any other state (usually #DecoderState::START). - */ - Error error; - - bool quit; - - /** - * Is the client currently waiting for the DecoderThread? If - * false, the DecoderThread may omit invoking Cond::signal(), - * reducing the number of system calls. - */ - bool client_is_waiting; - - bool seek_error; - bool seekable; - double seek_where; - - /** the format of the song file */ - AudioFormat in_audio_format; - - /** the format being sent to the music pipe */ - AudioFormat out_audio_format; - - /** - * The song currently being decoded. This attribute is set by - * the player thread, when it sends the #DecoderCommand::START - * command. - * - * This is a duplicate, and must be freed when this attribute - * is cleared. - */ - Song *song; - - /** - * The initial seek position (in milliseconds), e.g. to the - * start of a sub-track described by a CUE file. - * - * This attribute is set by dc_start(). - */ - unsigned start_ms; - - /** - * The decoder will stop when it reaches this position (in - * milliseconds). 0 means don't stop before the end of the - * file. - * - * This attribute is set by dc_start(). - */ - unsigned end_ms; - - float total_time; - - /** the #music_chunk allocator */ - MusicBuffer *buffer; - - /** - * The destination pipe for decoded chunks. The caller thread - * owns this object, and is responsible for freeing it. - */ - MusicPipe *pipe; - - float replay_gain_db; - float replay_gain_prev_db; - - MixRampInfo mix_ramp, previous_mix_ramp; - - /** - * @param _mutex see #mutex - * @param _client_cond see #client_cond - */ - DecoderControl(Mutex &_mutex, Cond &_client_cond); - ~DecoderControl(); - - /** - * Locks the object. - */ - void Lock() const { - mutex.lock(); - } - - /** - * Unlocks the object. - */ - void Unlock() const { - mutex.unlock(); - } - - /** - * Signals the object. This function is only valid in the - * player thread. The object should be locked prior to - * calling this function. - */ - void Signal() { - cond.signal(); - } - - /** - * Waits for a signal on the #DecoderControl object. This function - * is only valid in the decoder thread. The object must be locked - * prior to calling this function. - */ - void Wait() { - cond.wait(mutex); - } - - /** - * Waits for a signal from the decoder thread. This object - * must be locked prior to calling this function. This method - * is only valid in the player thread. - * - * Caller must hold the lock. - */ - void WaitForDecoder(); - - bool IsIdle() const { - return state == DecoderState::STOP || - state == DecoderState::ERROR; - } - - gcc_pure - bool LockIsIdle() const { - Lock(); - bool result = IsIdle(); - Unlock(); - return result; - } - - bool IsStarting() const { - return state == DecoderState::START; - } - - gcc_pure - bool LockIsStarting() const { - Lock(); - bool result = IsStarting(); - Unlock(); - return result; - } - - bool HasFailed() const { - assert(command == DecoderCommand::NONE); - - return state == DecoderState::ERROR; - } - - gcc_pure - bool LockHasFailed() const { - Lock(); - bool result = HasFailed(); - Unlock(); - return result; - } - - /** - * Checks whether an error has occurred, and if so, returns a - * copy of the #Error object. - * - * Caller must lock the object. - */ - gcc_pure - Error GetError() const { - assert(command == DecoderCommand::NONE); - assert(state != DecoderState::ERROR || error.IsDefined()); - - Error result; - if (state == DecoderState::ERROR) - result.Set(error); - return result; - } - - /** - * Like dc_get_error(), but locks and unlocks the object. - */ - gcc_pure - Error LockGetError() const { - Lock(); - Error result = GetError(); - Unlock(); - return result; - } - - /** - * Clear the error condition and free the #Error object (if any). - * - * Caller must lock the object. - */ - void ClearError() { - if (state == DecoderState::ERROR) { - error.Clear(); - state = DecoderState::STOP; - } - } - - /** - * Check if the specified song is currently being decoded. If the - * decoder is not running currently (or being started), then this - * function returns false in any case. - * - * Caller must lock the object. - */ - gcc_pure - bool IsCurrentSong(const Song &_song) const; - - gcc_pure - bool LockIsCurrentSong(const Song &_song) const { - Lock(); - const bool result = IsCurrentSong(_song); - Unlock(); - return result; - } - -private: - /** - * Wait for the command to be finished by the decoder thread. - * - * To be called from the client thread. Caller must lock the - * object. - */ - void WaitCommandLocked() { - while (command != DecoderCommand::NONE) - WaitForDecoder(); - } - - /** - * Send a command to the decoder thread and synchronously wait - * for it to finish. - * - * To be called from the client thread. Caller must lock the - * object. - */ - void SynchronousCommandLocked(DecoderCommand cmd) { - command = cmd; - Signal(); - WaitCommandLocked(); - } - - /** - * Send a command to the decoder thread and synchronously wait - * for it to finish. - * - * To be called from the client thread. This method locks the - * object. - */ - void LockSynchronousCommand(DecoderCommand cmd) { - Lock(); - ClearError(); - SynchronousCommandLocked(cmd); - Unlock(); - } - - void LockAsynchronousCommand(DecoderCommand cmd) { - Lock(); - command = cmd; - Signal(); - Unlock(); - } - -public: - /** - * Start the decoder. - * - * @param song the song to be decoded; the given instance will be - * owned and freed by the decoder - * @param start_ms see #DecoderControl - * @param end_ms see #DecoderControl - * @param pipe the pipe which receives the decoded chunks (owned by - * the caller) - */ - void Start(Song *song, unsigned start_ms, unsigned end_ms, - MusicBuffer &buffer, MusicPipe &pipe); - - void Stop(); - - bool Seek(double where); - - void Quit(); - - const char *GetMixRampStart() const { - return mix_ramp.GetStart(); - } - - const char *GetMixRampEnd() const { - return mix_ramp.GetEnd(); - } - - const char *GetMixRampPreviousEnd() const { - return previous_mix_ramp.GetEnd(); - } - - void SetMixRamp(MixRampInfo &&new_value) { - mix_ramp = std::move(new_value); - } - - /** - * Move mixramp_end to mixramp_prev_end and clear - * mixramp_start/mixramp_end. - */ - void CycleMixRamp(); -}; - -#endif diff --git a/src/DecoderError.cxx b/src/DecoderError.cxx deleted file mode 100644 index a8d3f548d..000000000 --- a/src/DecoderError.cxx +++ /dev/null @@ -1,23 +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 "DecoderError.hxx" -#include "util/Domain.hxx" - -const Domain decoder_domain("decoder"); diff --git a/src/DecoderError.hxx b/src/DecoderError.hxx deleted file mode 100644 index 9ea74167f..000000000 --- a/src/DecoderError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_ERROR_HXX -#define MPD_DECODER_ERROR_HXX - -extern const class Domain decoder_domain; - -#endif diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx deleted file mode 100644 index d5f40ad48..000000000 --- a/src/DecoderInternal.cxx +++ /dev/null @@ -1,100 +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 "DecoderInternal.hxx" -#include "DecoderControl.hxx" -#include "MusicPipe.hxx" -#include "MusicBuffer.hxx" -#include "MusicChunk.hxx" -#include "tag/Tag.hxx" - -#include <assert.h> - -Decoder::~Decoder() -{ - /* caller must flush the chunk */ - assert(chunk == nullptr); - - delete song_tag; - delete stream_tag; - delete decoder_tag; -} - -/** - * All chunks are full of decoded data; wait for the player to free - * one. - */ -static DecoderCommand -need_chunks(DecoderControl &dc) -{ - if (dc.command == DecoderCommand::NONE) - dc.Wait(); - - return dc.command; -} - -struct music_chunk * -decoder_get_chunk(Decoder &decoder) -{ - DecoderControl &dc = decoder.dc; - DecoderCommand cmd; - - if (decoder.chunk != nullptr) - return decoder.chunk; - - do { - decoder.chunk = dc.buffer->Allocate(); - if (decoder.chunk != nullptr) { - decoder.chunk->replay_gain_serial = - decoder.replay_gain_serial; - if (decoder.replay_gain_serial != 0) - decoder.chunk->replay_gain_info = - decoder.replay_gain_info; - - return decoder.chunk; - } - - dc.Lock(); - cmd = need_chunks(dc); - dc.Unlock(); - } while (cmd == DecoderCommand::NONE); - - return nullptr; -} - -void -decoder_flush_chunk(Decoder &decoder) -{ - DecoderControl &dc = decoder.dc; - - assert(decoder.chunk != nullptr); - - if (decoder.chunk->IsEmpty()) - dc.buffer->Return(decoder.chunk); - else - dc.pipe->Push(decoder.chunk); - - decoder.chunk = nullptr; - - dc.Lock(); - if (dc.client_is_waiting) - dc.client_cond.signal(); - dc.Unlock(); -} diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx deleted file mode 100644 index 46069a561..000000000 --- a/src/DecoderInternal.hxx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_INTERNAL_HXX -#define MPD_DECODER_INTERNAL_HXX - -#include "DecoderCommand.hxx" -#include "pcm/PcmConvert.hxx" -#include "ReplayGainInfo.hxx" - -struct DecoderControl; -struct InputStream; -struct Tag; - -struct Decoder { - DecoderControl &dc; - - PcmConvert conv_state; - - /** - * The time stamp of the next data chunk, in seconds. - */ - double timestamp; - - /** - * Is the initial seek (to the start position of the sub-song) - * pending, or has it been performed already? - */ - bool initial_seek_pending; - - /** - * Is the initial seek currently running? During this time, - * the decoder command is SEEK. This flag is set by - * decoder_get_virtual_command(), when the virtual SEEK - * command is generated for the first time. - */ - bool initial_seek_running; - - /** - * This flag is set by decoder_seek_where(), and checked by - * decoder_command_finished(). It is used to clean up after - * seeking. - */ - bool seeking; - - /** - * The tag from the song object. This is only used for local - * files, because we expect the stream server to send us a new - * tag each time we play it. - */ - Tag *song_tag; - - /** the last tag received from the stream */ - Tag *stream_tag; - - /** the last tag received from the decoder plugin */ - Tag *decoder_tag; - - /** the chunk currently being written to */ - struct music_chunk *chunk; - - ReplayGainInfo replay_gain_info; - - /** - * A positive serial number for checking if replay gain info - * has changed since the last check. - */ - unsigned replay_gain_serial; - - Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag) - :dc(_dc), - timestamp(0), - initial_seek_pending(_initial_seek_pending), - initial_seek_running(false), - seeking(false), - song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr), - chunk(nullptr), - replay_gain_serial(0) { - } - - ~Decoder(); -}; - -/** - * Returns the current chunk the decoder writes to, or allocates a new - * chunk if there is none. - * - * @return the chunk, or NULL if we have received a decoder command - */ -struct music_chunk * -decoder_get_chunk(Decoder &decoder); - -/** - * Flushes the current chunk. - * - * Caller must not lock the #DecoderControl object. - */ -void -decoder_flush_chunk(Decoder &decoder); - -#endif diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx deleted file mode 100644 index 834178260..000000000 --- a/src/DecoderList.cxx +++ /dev/null @@ -1,237 +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 "DecoderList.hxx" -#include "DecoderPlugin.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "decoder/AudiofileDecoderPlugin.hxx" -#include "decoder/PcmDecoderPlugin.hxx" -#include "decoder/DsdiffDecoderPlugin.hxx" -#include "decoder/DsfDecoderPlugin.hxx" -#include "decoder/FlacDecoderPlugin.h" -#include "decoder/OpusDecoderPlugin.h" -#include "decoder/VorbisDecoderPlugin.h" -#include "decoder/AdPlugDecoderPlugin.h" -#include "decoder/WavpackDecoderPlugin.hxx" -#include "decoder/FfmpegDecoderPlugin.hxx" -#include "decoder/GmeDecoderPlugin.hxx" -#include "decoder/FaadDecoderPlugin.hxx" -#include "decoder/MadDecoderPlugin.hxx" -#include "decoder/SndfileDecoderPlugin.hxx" -#include "decoder/Mpg123DecoderPlugin.hxx" -#include "decoder/WildmidiDecoderPlugin.hxx" -#include "decoder/MikmodDecoderPlugin.hxx" -#include "decoder/ModplugDecoderPlugin.hxx" -#include "decoder/MpcdecDecoderPlugin.hxx" -#include "decoder/FluidsynthDecoderPlugin.hxx" -#include "decoder/SidplayDecoderPlugin.hxx" -#include "system/FatalError.hxx" -#include "util/Macros.hxx" - -#include <string.h> - -const struct DecoderPlugin *const decoder_plugins[] = { -#ifdef HAVE_MAD - &mad_decoder_plugin, -#endif -#ifdef HAVE_MPG123 - &mpg123_decoder_plugin, -#endif -#ifdef ENABLE_VORBIS_DECODER - &vorbis_decoder_plugin, -#endif -#if defined(HAVE_FLAC) - &oggflac_decoder_plugin, -#endif -#ifdef HAVE_FLAC - &flac_decoder_plugin, -#endif -#ifdef HAVE_OPUS - &opus_decoder_plugin, -#endif -#ifdef ENABLE_SNDFILE - &sndfile_decoder_plugin, -#endif -#ifdef HAVE_AUDIOFILE - &audiofile_decoder_plugin, -#endif - &dsdiff_decoder_plugin, - &dsf_decoder_plugin, -#ifdef HAVE_FAAD - &faad_decoder_plugin, -#endif -#ifdef HAVE_MPCDEC - &mpcdec_decoder_plugin, -#endif -#ifdef HAVE_WAVPACK - &wavpack_decoder_plugin, -#endif -#ifdef HAVE_MODPLUG - &modplug_decoder_plugin, -#endif -#ifdef ENABLE_MIKMOD_DECODER - &mikmod_decoder_plugin, -#endif -#ifdef ENABLE_SIDPLAY - &sidplay_decoder_plugin, -#endif -#ifdef ENABLE_WILDMIDI - &wildmidi_decoder_plugin, -#endif -#ifdef ENABLE_FLUIDSYNTH - &fluidsynth_decoder_plugin, -#endif -#ifdef HAVE_ADPLUG - &adplug_decoder_plugin, -#endif -#ifdef HAVE_FFMPEG - &ffmpeg_decoder_plugin, -#endif -#ifdef HAVE_GME - &gme_decoder_plugin, -#endif - &pcm_decoder_plugin, - nullptr -}; - -static constexpr unsigned num_decoder_plugins = - ARRAY_SIZE(decoder_plugins) - 1; - -/** which plugins have been initialized successfully? */ -bool decoder_plugins_enabled[num_decoder_plugins]; - -static unsigned -decoder_plugin_index(const struct DecoderPlugin *plugin) -{ - unsigned i = 0; - - while (decoder_plugins[i] != plugin) - ++i; - - return i; -} - -static unsigned -decoder_plugin_next_index(const struct DecoderPlugin *plugin) -{ - return plugin == 0 - ? 0 /* start with first plugin */ - : decoder_plugin_index(plugin) + 1; -} - -const struct DecoderPlugin * -decoder_plugin_from_suffix(const char *suffix, - const struct DecoderPlugin *plugin) -{ - if (suffix == nullptr) - return nullptr; - - for (unsigned i = decoder_plugin_next_index(plugin); - decoder_plugins[i] != nullptr; ++i) { - plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - plugin->SupportsSuffix(suffix)) - return plugin; - } - - return nullptr; -} - -const struct DecoderPlugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) -{ - static unsigned i = num_decoder_plugins; - - if (mimeType == nullptr) - return nullptr; - - if (!next) - i = 0; - for (; decoder_plugins[i] != nullptr; ++i) { - const struct DecoderPlugin *plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - plugin->SupportsMimeType(mimeType)) { - ++i; - return plugin; - } - } - - return nullptr; -} - -const struct DecoderPlugin * -decoder_plugin_from_name(const char *name) -{ - return decoder_plugins_find([=](const DecoderPlugin &plugin){ - return strcmp(plugin.name, name) == 0; - }); -} - -/** - * Find the "decoder" configuration block for the specified plugin. - * - * @param plugin_name the name of the decoder plugin - * @return the configuration block, or nullptr if none was configured - */ -static const struct config_param * -decoder_plugin_config(const char *plugin_name) -{ - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_DECODER, param)) != nullptr) { - const char *name = param->GetBlockValue("plugin"); - if (name == nullptr) - FormatFatalError("decoder configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return nullptr; -} - -void decoder_plugin_init_all(void) -{ - struct config_param empty; - - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) { - const DecoderPlugin &plugin = *decoder_plugins[i]; - const struct config_param *param = - decoder_plugin_config(plugin.name); - - if (param == nullptr) - param = ∅ - else if (!param->GetBlockValue("enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (plugin.Init(*param)) - decoder_plugins_enabled[i] = true; - } -} - -void decoder_plugin_deinit_all(void) -{ - decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){ - plugin.Finish(); - }); -} diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx deleted file mode 100644 index fd4b22c63..000000000 --- a/src/DecoderList.hxx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_LIST_HXX -#define MPD_DECODER_LIST_HXX - -struct DecoderPlugin; - -extern const struct DecoderPlugin *const decoder_plugins[]; -extern bool decoder_plugins_enabled[]; - -/* interface for using plugins */ - -/** - * Find the next enabled decoder plugin which supports the specified suffix. - * - * @param suffix the file name suffix - * @param plugin the previous plugin, or nullptr to find the first plugin - * @return a plugin, or nullptr if none matches - */ -const struct DecoderPlugin * -decoder_plugin_from_suffix(const char *suffix, - const struct DecoderPlugin *plugin); - -const struct DecoderPlugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); - -const struct DecoderPlugin * -decoder_plugin_from_name(const char *name); - -/* this is where we "load" all the "plugins" ;-) */ -void decoder_plugin_init_all(void); - -/* this is where we "unload" all the "plugins" */ -void decoder_plugin_deinit_all(void); - -template<typename F> -static inline const DecoderPlugin * -decoder_plugins_find(F f) -{ - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) - if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) - return decoder_plugins[i]; - - return nullptr; -} - -template<typename F> -static inline bool -decoder_plugins_try(F f) -{ - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) - if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) - return true; - - return false; -} - -template<typename F> -static inline void -decoder_plugins_for_each(F f) -{ - for (auto i = decoder_plugins; *i != nullptr; ++i) - f(**i); -} - -template<typename F> -static inline void -decoder_plugins_for_each_enabled(F f) -{ - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) - if (decoder_plugins_enabled[i]) - f(*decoder_plugins[i]); -} - -#endif diff --git a/src/DecoderPlugin.cxx b/src/DecoderPlugin.cxx deleted file mode 100644 index 77ed90882..000000000 --- a/src/DecoderPlugin.cxx +++ /dev/null @@ -1,42 +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 "DecoderPlugin.hxx" -#include "util/StringUtil.hxx" - -#include <assert.h> - -bool -DecoderPlugin::SupportsSuffix(const char *suffix) const -{ - assert(suffix != nullptr); - - return suffixes != nullptr && string_array_contains(suffixes, suffix); - -} - -bool -DecoderPlugin::SupportsMimeType(const char *mime_type) const -{ - assert(mime_type != nullptr); - - return mime_types != nullptr && - string_array_contains(mime_types, mime_type); -} diff --git a/src/DecoderPlugin.hxx b/src/DecoderPlugin.hxx deleted file mode 100644 index 6b0340123..000000000 --- a/src/DecoderPlugin.hxx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_PLUGIN_HXX -#define MPD_DECODER_PLUGIN_HXX - -#include "Compiler.h" - -struct config_param; -struct InputStream; -struct Tag; -struct tag_handler; - -/** - * Opaque handle which the decoder plugin passes to the functions in - * this header. - */ -struct Decoder; - -struct DecoderPlugin { - const char *name; - - /** - * Initialize the decoder plugin. Optional method. - * - * @param param a configuration block for this plugin, or nullptr - * if none is configured - * @return true if the plugin was initialized successfully, - * false if the plugin is not available - */ - bool (*init)(const config_param ¶m); - - /** - * Deinitialize a decoder plugin which was initialized - * successfully. Optional method. - */ - void (*finish)(void); - - /** - * Decode a stream (data read from an #input_stream object). - * - * Either implement this method or file_decode(). If - * possible, it is recommended to implement this method, - * because it is more versatile. - */ - void (*stream_decode)(Decoder &decoder, InputStream &is); - - /** - * Decode a local file. - * - * Either implement this method or stream_decode(). - */ - void (*file_decode)(Decoder &decoder, const char *path_fs); - - /** - * Scan metadata of a file. - * - * @return false if the operation has failed - */ - bool (*scan_file)(const char *path_fs, - const struct tag_handler *handler, - void *handler_ctx); - - /** - * Scan metadata of a file. - * - * @return false if the operation has failed - */ - bool (*scan_stream)(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx); - - /** - * @brief Return a "virtual" filename for subtracks in - * container formats like flac - * @param const char* pathname full pathname for the file on fs - * @param const unsigned int tnum track number - * - * @return nullptr if there are no multiple files - * a filename for every single track according to tnum (param 2) - * do not include full pathname here, just the "virtual" file - */ - char* (*container_scan)(const char *path_fs, const unsigned int tnum); - - /* last element in these arrays must always be a nullptr: */ - const char *const*suffixes; - const char *const*mime_types; - - /** - * Initialize a decoder plugin. - * - * @param param a configuration block for this plugin, or nullptr if none - * is configured - * @return true if the plugin was initialized successfully, false if - * the plugin is not available - */ - bool Init(const config_param ¶m) const { - return init != nullptr - ? init(param) - : true; - } - - /** - * Deinitialize a decoder plugin which was initialized successfully. - */ - void Finish() const { - if (finish != nullptr) - finish(); - } - - /** - * Decode a stream. - */ - void StreamDecode(Decoder &decoder, InputStream &is) const { - stream_decode(decoder, is); - } - - /** - * Decode a file. - */ - void FileDecode(Decoder &decoder, const char *path_fs) const { - file_decode(decoder, path_fs); - } - - /** - * Read the tag of a file. - */ - bool ScanFile(const char *path_fs, - const tag_handler &handler, void *handler_ctx) const { - return scan_file != nullptr - ? scan_file(path_fs, &handler, handler_ctx) - : false; - } - - /** - * Read the tag of a stream. - */ - bool ScanStream(InputStream &is, - const tag_handler &handler, void *handler_ctx) const { - return scan_stream != nullptr - ? scan_stream(is, &handler, handler_ctx) - : false; - } - - /** - * return "virtual" tracks in a container - */ - char *ContainerScan(const char *path, const unsigned int tnum) const { - return container_scan(path, tnum); - } - - /** - * Does the plugin announce the specified file name suffix? - */ - gcc_pure gcc_nonnull_all - bool SupportsSuffix(const char *suffix) const; - - /** - * Does the plugin announce the specified MIME type? - */ - gcc_pure gcc_nonnull_all - bool SupportsMimeType(const char *mime_type) const; -}; - -#endif diff --git a/src/DecoderPrint.cxx b/src/DecoderPrint.cxx deleted file mode 100644 index 2372272c2..000000000 --- a/src/DecoderPrint.cxx +++ /dev/null @@ -1,55 +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 "DecoderPrint.hxx" -#include "DecoderList.hxx" -#include "DecoderPlugin.hxx" -#include "Client.hxx" - -#include <functional> - -#include <assert.h> - -static void -decoder_plugin_print(Client &client, - const DecoderPlugin &plugin) -{ - const char *const*p; - - assert(plugin.name != nullptr); - - client_printf(client, "plugin: %s\n", plugin.name); - - if (plugin.suffixes != nullptr) - for (p = plugin.suffixes; *p != nullptr; ++p) - client_printf(client, "suffix: %s\n", *p); - - if (plugin.mime_types != nullptr) - for (p = plugin.mime_types; *p != nullptr; ++p) - client_printf(client, "mime_type: %s\n", *p); -} - -void -decoder_list_print(Client &client) -{ - using namespace std::placeholders; - const auto f = std::bind(decoder_plugin_print, std::ref(client), _1); - decoder_plugins_for_each_enabled(f); -} diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx deleted file mode 100644 index 693608746..000000000 --- a/src/DecoderPrint.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_PRINT_HXX -#define MPD_DECODER_PRINT_HXX - -class Client; - -void -decoder_list_print(Client &client); - -#endif diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx deleted file mode 100644 index 72fc3cfb4..000000000 --- a/src/DecoderThread.cxx +++ /dev/null @@ -1,450 +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 "DecoderThread.hxx" -#include "DecoderControl.hxx" -#include "DecoderInternal.hxx" -#include "DecoderError.hxx" -#include "DecoderPlugin.hxx" -#include "Song.hxx" -#include "system/FatalError.hxx" -#include "Mapper.hxx" -#include "fs/Traits.hxx" -#include "fs/AllocatedPath.hxx" -#include "DecoderAPI.hxx" -#include "tag/Tag.hxx" -#include "InputStream.hxx" -#include "DecoderList.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "tag/ApeReplayGain.hxx" -#include "Log.hxx" - -#include <functional> - -static constexpr Domain decoder_thread_domain("decoder_thread"); - -/** - * Marks the current decoder command as "finished" and notifies the - * player thread. - * - * @param dc the #DecoderControl object; must be locked - */ -static void -decoder_command_finished_locked(DecoderControl &dc) -{ - assert(dc.command != DecoderCommand::NONE); - - dc.command = DecoderCommand::NONE; - - dc.client_cond.signal(); -} - -/** - * Opens the input stream with input_stream::Open(), and waits until - * the stream gets ready. If a decoder STOP command is received - * during that, it cancels the operation (but does not close the - * stream). - * - * Unlock the decoder before calling this function. - * - * @return an input_stream on success or if #DecoderCommand::STOP is - * received, nullptr on error - */ -static InputStream * -decoder_input_stream_open(DecoderControl &dc, const char *uri) -{ - Error error; - - InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error); - if (is == nullptr) { - if (error.IsDefined()) - LogError(error); - - return nullptr; - } - - /* wait for the input stream to become ready; its metadata - will be available then */ - - dc.Lock(); - - is->Update(); - while (!is->ready && - dc.command != DecoderCommand::STOP) { - dc.Wait(); - - is->Update(); - } - - if (!is->Check(error)) { - dc.Unlock(); - - LogError(error); - return nullptr; - } - - dc.Unlock(); - - return is; -} - -static bool -decoder_stream_decode(const DecoderPlugin &plugin, - Decoder &decoder, - InputStream &input_stream) -{ - assert(plugin.stream_decode != nullptr); - assert(decoder.stream_tag == nullptr); - assert(decoder.decoder_tag == nullptr); - assert(input_stream.ready); - assert(decoder.dc.state == DecoderState::START); - - FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); - - if (decoder.dc.command == DecoderCommand::STOP) - return true; - - /* rewind the stream, so each plugin gets a fresh start */ - input_stream.Rewind(IgnoreError()); - - decoder.dc.Unlock(); - - plugin.StreamDecode(decoder, input_stream); - - decoder.dc.Lock(); - - assert(decoder.dc.state == DecoderState::START || - decoder.dc.state == DecoderState::DECODE); - - return decoder.dc.state != DecoderState::START; -} - -static bool -decoder_file_decode(const DecoderPlugin &plugin, - Decoder &decoder, const char *path) -{ - assert(plugin.file_decode != nullptr); - assert(decoder.stream_tag == nullptr); - assert(decoder.decoder_tag == nullptr); - assert(path != nullptr); - assert(PathTraits::IsAbsoluteFS(path)); - assert(decoder.dc.state == DecoderState::START); - - FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); - - if (decoder.dc.command == DecoderCommand::STOP) - return true; - - decoder.dc.Unlock(); - - plugin.FileDecode(decoder, path); - - decoder.dc.Lock(); - - assert(decoder.dc.state == DecoderState::START || - decoder.dc.state == DecoderState::DECODE); - - return decoder.dc.state != DecoderState::START; -} - -gcc_pure -static bool -decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is) -{ - assert(plugin.stream_decode != nullptr); - - return !is.mime.empty() && plugin.SupportsMimeType(is.mime.c_str()); -} - -gcc_pure -static bool -decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix) -{ - assert(plugin.stream_decode != nullptr); - - return suffix != nullptr && plugin.SupportsSuffix(suffix); -} - -gcc_pure -static bool -decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, - const char *suffix) -{ - return plugin.stream_decode != nullptr && - (decoder_check_plugin_mime(plugin, is) || - decoder_check_plugin_suffix(plugin, suffix)); -} - -static bool -decoder_run_stream_plugin(Decoder &decoder, InputStream &is, - const char *suffix, - const DecoderPlugin &plugin, - bool &tried_r) -{ - if (!decoder_check_plugin(plugin, is, suffix)) - return false; - - tried_r = true; - return decoder_stream_decode(plugin, decoder, is); -} - -static bool -decoder_run_stream_locked(Decoder &decoder, InputStream &is, - const char *uri, bool &tried_r) -{ - const char *const suffix = uri_get_suffix(uri); - - using namespace std::placeholders; - const auto f = std::bind(decoder_run_stream_plugin, - std::ref(decoder), std::ref(is), suffix, - _1, std::ref(tried_r)); - return decoder_plugins_try(f); -} - -/** - * Try decoding a stream, using the fallback plugin. - */ -static bool -decoder_run_stream_fallback(Decoder &decoder, InputStream &is) -{ - const struct DecoderPlugin *plugin; - - plugin = decoder_plugin_from_name("mad"); - return plugin != nullptr && plugin->stream_decode != nullptr && - decoder_stream_decode(*plugin, decoder, is); -} - -/** - * Try decoding a stream. - */ -static bool -decoder_run_stream(Decoder &decoder, const char *uri) -{ - DecoderControl &dc = decoder.dc; - InputStream *input_stream; - bool success; - - dc.Unlock(); - - input_stream = decoder_input_stream_open(dc, uri); - if (input_stream == nullptr) { - dc.Lock(); - return false; - } - - dc.Lock(); - - bool tried = false; - success = dc.command == DecoderCommand::STOP || - decoder_run_stream_locked(decoder, *input_stream, uri, - tried) || - /* fallback to mp3: this is needed for bastard streams - that don't have a suffix or set the mimeType */ - (!tried && - decoder_run_stream_fallback(decoder, *input_stream)); - - dc.Unlock(); - input_stream->Close(); - dc.Lock(); - - return success; -} - -/** - * Attempt to load replay gain data, and pass it to - * decoder_replay_gain(). - */ -static void -decoder_load_replay_gain(Decoder &decoder, const char *path_fs) -{ - ReplayGainInfo info; - if (replay_gain_ape_read(Path::FromFS(path_fs), info)) - decoder_replay_gain(decoder, &info); -} - -/** - * Try decoding a file. - */ -static bool -decoder_run_file(Decoder &decoder, const char *path_fs) -{ - DecoderControl &dc = decoder.dc; - const char *suffix = uri_get_suffix(path_fs); - const struct DecoderPlugin *plugin = nullptr; - - if (suffix == nullptr) - return false; - - dc.Unlock(); - - decoder_load_replay_gain(decoder, path_fs); - - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != nullptr) { - if (plugin->file_decode != nullptr) { - dc.Lock(); - - if (decoder_file_decode(*plugin, decoder, path_fs)) - return true; - - dc.Unlock(); - } else if (plugin->stream_decode != nullptr) { - InputStream *input_stream; - bool success; - - input_stream = decoder_input_stream_open(dc, path_fs); - if (input_stream == nullptr) - continue; - - dc.Lock(); - - success = decoder_stream_decode(*plugin, decoder, - *input_stream); - - dc.Unlock(); - - input_stream->Close(); - - if (success) { - dc.Lock(); - return true; - } - } - } - - dc.Lock(); - return false; -} - -static void -decoder_run_song(DecoderControl &dc, - const Song *song, const char *uri) -{ - Decoder decoder(dc, dc.start_ms > 0, - song->tag != nullptr && song->IsFile() - ? new Tag(*song->tag) : nullptr); - int ret; - - dc.state = DecoderState::START; - - decoder_command_finished_locked(dc); - - ret = song->IsFile() - ? decoder_run_file(decoder, uri) - : decoder_run_stream(decoder, uri); - - dc.Unlock(); - - /* flush the last chunk */ - - if (decoder.chunk != nullptr) - decoder_flush_chunk(decoder); - - dc.Lock(); - - if (ret) - dc.state = DecoderState::STOP; - else { - dc.state = DecoderState::ERROR; - - const char *error_uri = song->uri; - const std::string allocated = uri_remove_auth(error_uri); - if (!allocated.empty()) - error_uri = allocated.c_str(); - - dc.error.Format(decoder_domain, - "Failed to decode %s", error_uri); - } - - dc.client_cond.signal(); -} - -static void -decoder_run(DecoderControl &dc) -{ - dc.ClearError(); - - const Song *song = dc.song; - assert(song != nullptr); - - const std::string uri = song->IsFile() - ? std::string(map_song_fs(*song).c_str()) - : song->GetURI(); - - if (uri.empty()) { - dc.state = DecoderState::ERROR; - dc.error.Set(decoder_domain, "Failed to map song"); - - decoder_command_finished_locked(dc); - return; - } - - decoder_run_song(dc, song, uri.c_str()); - -} - -static void -decoder_task(void *arg) -{ - DecoderControl &dc = *(DecoderControl *)arg; - - dc.Lock(); - - do { - assert(dc.state == DecoderState::STOP || - dc.state == DecoderState::ERROR); - - switch (dc.command) { - case DecoderCommand::START: - dc.CycleMixRamp(); - dc.replay_gain_prev_db = dc.replay_gain_db; - dc.replay_gain_db = 0; - - /* fall through */ - - case DecoderCommand::SEEK: - decoder_run(dc); - break; - - case DecoderCommand::STOP: - decoder_command_finished_locked(dc); - break; - - case DecoderCommand::NONE: - dc.Wait(); - break; - } - } while (dc.command != DecoderCommand::NONE || !dc.quit); - - dc.Unlock(); -} - -void -decoder_thread_start(DecoderControl &dc) -{ - assert(!dc.thread.IsDefined()); - - dc.quit = false; - - Error error; - if (!dc.thread.Start(decoder_task, &dc, error)) - FatalError(error); -} diff --git a/src/DecoderThread.hxx b/src/DecoderThread.hxx deleted file mode 100644 index a2d533f93..000000000 --- a/src/DecoderThread.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_THREAD_HXX -#define MPD_DECODER_THREAD_HXX - -struct DecoderControl; - -void -decoder_thread_start(DecoderControl &dc); - -#endif diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx deleted file mode 100644 index e91587a7f..000000000 --- a/src/DespotifyUtils.cxx +++ /dev/null @@ -1,154 +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 "DespotifyUtils.hxx" -#include "tag/Tag.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -extern "C" { -#include <despotify.h> -} - -#include <stdio.h> - -const Domain despotify_domain("despotify"); - -static struct despotify_session *g_session; -static void (*registered_callbacks[8])(struct despotify_session *, - int, void *, void *); -static void *registered_callback_data[8]; - -static void -callback(struct despotify_session* ds, int sig, - void *data, gcc_unused void *callback_data) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; - void *cb_data = registered_callback_data[i]; - - if (cb) - cb(ds, sig, data, cb_data); - } -} - -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - - if (!registered_callbacks[i]) { - registered_callbacks[i] = cb; - registered_callback_data[i] = cb_data; - - return true; - } - } - - return false; -} - -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) -{ - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - - if (registered_callbacks[i] == cb) { - registered_callbacks[i] = nullptr; - } - } -} - - -Tag * -mpd_despotify_tag_from_track(struct ds_track *track) -{ - char tracknum[20]; - char comment[80]; - char date[20]; - - Tag *tag = new Tag(); - - if (!track->has_meta_data) - return tag; - - snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); - snprintf(date, sizeof(date), "%d", track->year); - snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", - track->file_bitrate / 1000, - track->geo_restricted ? "" : "not "); - tag->AddItem(TAG_TITLE, track->title); - tag->AddItem(TAG_ARTIST, track->artist->name); - tag->AddItem(TAG_TRACK, tracknum); - tag->AddItem(TAG_ALBUM, track->album); - tag->AddItem(TAG_DATE, date); - tag->AddItem(TAG_COMMENT, comment); - tag->time = track->length / 1000; - - return tag; -} - -struct despotify_session *mpd_despotify_get_session(void) -{ - const char *user; - const char *passwd; - bool high_bitrate; - - if (g_session) - return g_session; - - user = config_get_string(CONF_DESPOTIFY_USER, nullptr); - passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr); - high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); - - if (user == nullptr || passwd == nullptr) { - LogDebug(despotify_domain, - "disabling despotify because account is not configured"); - return nullptr; - } - - if (!despotify_init()) { - LogWarning(despotify_domain, "Can't initialize despotify"); - return nullptr; - } - - g_session = despotify_init_client(callback, nullptr, - high_bitrate, true); - if (!g_session) { - LogWarning(despotify_domain, - "Can't initialize despotify client"); - return nullptr; - } - - if (!despotify_authenticate(g_session, user, passwd)) { - LogWarning(despotify_domain, - "Can't authenticate despotify session"); - despotify_exit(g_session); - return nullptr; - } - - return g_session; -} diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx deleted file mode 100644 index c0d4af47c..000000000 --- a/src/DespotifyUtils.hxx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DESPOTIFY_H -#define MPD_DESPOTIFY_H - -struct Tag; -struct despotify_session; -struct ds_track; - -extern const class Domain despotify_domain; - -/** - * Return the current despotify session. - * - * If the session isn't initialized, this function will initialize - * it and connect to Spotify. - * - * @return a pointer to the despotify session, or nullptr if it can't - * be initialized (e.g., if the configuration isn't supplied) - */ -struct despotify_session *mpd_despotify_get_session(void); - -/** - * Create a MPD tags structure from a spotify track - * - * @param track the track to convert - * - * @return a pointer to the filled in tags structure - */ -Tag * -mpd_despotify_tag_from_track(struct ds_track *track); - -/** - * Register a despotify callback. - * - * Despotify calls this e.g., when a track ends. - * - * @param cb the callback - * @param cb_data the data to pass to the callback - * - * @return true if the callback could be registered - */ -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data); - -/** - * Unregister a despotify callback. - * - * @param cb the callback to unregister. - */ -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); - -#endif - diff --git a/src/DetachedSong.cxx b/src/DetachedSong.cxx new file mode 100644 index 000000000..eb377e591 --- /dev/null +++ b/src/DetachedSong.cxx @@ -0,0 +1,67 @@ +/* + * 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 "DetachedSong.hxx" +#include "db/LightSong.hxx" +#include "util/UriUtil.hxx" +#include "fs/Traits.hxx" + +DetachedSong::DetachedSong(const LightSong &other) + :uri(other.GetURI().c_str()), + real_uri(other.real_uri != nullptr ? other.real_uri : ""), + tag(*other.tag), + mtime(other.mtime), + start_ms(other.start_ms), end_ms(other.end_ms) {} + +DetachedSong::~DetachedSong() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +DetachedSong::IsRemote() const +{ + return uri_has_scheme(GetRealURI()); +} + +bool +DetachedSong::IsAbsoluteFile() const +{ + return PathTraitsUTF8::IsAbsolute(GetRealURI()); +} + +bool +DetachedSong::IsInDatabase() const +{ + /* here, we use GetURI() and not GetRealURI() because + GetRealURI() is never relative */ + + const char *_uri = GetURI(); + return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri); +} + +double +DetachedSong::GetDuration() const +{ + if (end_ms > 0) + return (end_ms - start_ms) / 1000.0; + + return tag.time - start_ms / 1000.0; +} diff --git a/src/DetachedSong.hxx b/src/DetachedSong.hxx new file mode 100644 index 000000000..7ea0bc8d8 --- /dev/null +++ b/src/DetachedSong.hxx @@ -0,0 +1,221 @@ +/* + * 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_DETACHED_SONG_HXX +#define MPD_DETACHED_SONG_HXX + +#include "check.h" +#include "tag/Tag.hxx" +#include "Compiler.h" + +#include <string> +#include <utility> + +#include <time.h> + +struct LightSong; +class Storage; + +class DetachedSong { + friend DetachedSong map_song_detach(const LightSong &song); + friend DetachedSong DatabaseDetachSong(const Storage &db, + const LightSong &song); + + /** + * An UTF-8-encoded URI referring to the song file. This can + * be one of: + * + * - an absolute URL with a scheme + * (e.g. "http://example.com/foo.mp3") + * + * - an absolute file name + * + * - a file name relative to the music directory + */ + std::string uri; + + /** + * The "real" URI, the one to be used for opening the + * resource. If this attribute is empty, then #uri shall be + * used. + * + * This attribute is used for songs from the database which + * have a relative URI. + */ + std::string real_uri; + + Tag tag; + + time_t mtime; + + /** + * Start of this sub-song within the file in milliseconds. + */ + unsigned start_ms; + + /** + * End of this sub-song within the file in milliseconds. + * Unused if zero. + */ + unsigned end_ms; + + explicit DetachedSong(const LightSong &other); + +public: + explicit DetachedSong(const DetachedSong &) = default; + + explicit DetachedSong(const char *_uri) + :uri(_uri), + mtime(0), start_ms(0), end_ms(0) {} + + explicit DetachedSong(const std::string &_uri) + :uri(_uri), + mtime(0), start_ms(0), end_ms(0) {} + + explicit DetachedSong(std::string &&_uri) + :uri(std::move(_uri)), + mtime(0), start_ms(0), end_ms(0) {} + + template<typename U> + DetachedSong(U &&_uri, Tag &&_tag) + :uri(std::forward<U>(_uri)), + tag(std::move(_tag)), + mtime(0), start_ms(0), end_ms(0) {} + + DetachedSong(DetachedSong &&) = default; + + ~DetachedSong(); + + gcc_pure + const char *GetURI() const { + return uri.c_str(); + } + + template<typename T> + void SetURI(T &&_uri) { + uri = std::forward<T>(_uri); + } + + /** + * Does this object have a "real" URI different from the + * displayed URI? + */ + gcc_pure + bool HasRealURI() const { + return !real_uri.empty(); + } + + /** + * Returns "real" URI (#real_uri) and falls back to just + * GetURI(). + */ + gcc_pure + const char *GetRealURI() const { + return (HasRealURI() ? real_uri : uri).c_str(); + } + + template<typename T> + void SetRealURI(T &&_uri) { + real_uri = std::forward<T>(_uri); + } + + /** + * Returns true if both objects refer to the same physical + * song. + */ + gcc_pure + bool IsSame(const DetachedSong &other) const { + return uri == other.uri; + } + + gcc_pure gcc_nonnull_all + bool IsURI(const char *other_uri) const { + return uri == other_uri; + } + + gcc_pure + bool IsRemote() const; + + gcc_pure + bool IsFile() const { + return !IsRemote(); + } + + gcc_pure + bool IsAbsoluteFile() const; + + gcc_pure + bool IsInDatabase() const; + + const Tag &GetTag() const { + return tag; + } + + Tag &WritableTag() { + return tag; + } + + void SetTag(const Tag &_tag) { + tag = Tag(_tag); + } + + void SetTag(Tag &&_tag) { + tag = std::move(_tag); + } + + void MoveTagFrom(DetachedSong &&other) { + tag = std::move(other.tag); + } + + time_t GetLastModified() const { + return mtime; + } + + void SetLastModified(time_t _value) { + mtime = _value; + } + + unsigned GetStartMS() const { + return start_ms; + } + + void SetStartMS(unsigned _value) { + start_ms = _value; + } + + unsigned GetEndMS() const { + return end_ms; + } + + void SetEndMS(unsigned _value) { + end_ms = _value; + } + + gcc_pure + double GetDuration() const; + + /** + * Update the #tag and #mtime. + * + * @return true on success + */ + bool Update(); +}; + +#endif diff --git a/src/Directory.cxx b/src/Directory.cxx deleted file mode 100644 index b2942588e..000000000 --- a/src/Directory.cxx +++ /dev/null @@ -1,331 +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 "Directory.hxx" -#include "SongFilter.hxx" -#include "PlaylistVector.hxx" -#include "DatabaseLock.hxx" -#include "SongSort.hxx" -#include "Song.hxx" -#include "fs/Traits.hxx" -#include "util/Error.hxx" - -extern "C" { -#include "util/list_sort.h" -} - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -inline Directory * -Directory::Allocate(const char *path) -{ - assert(path != nullptr); - - const size_t path_size = strlen(path) + 1; - Directory *directory = - (Directory *)g_malloc0(sizeof(*directory) - - sizeof(directory->path) - + path_size); - new(directory) Directory(path); - - return directory; -} - -Directory::Directory() -{ - INIT_LIST_HEAD(&children); - INIT_LIST_HEAD(&songs); - - path[0] = 0; -} - -Directory::Directory(const char *_path) -{ - INIT_LIST_HEAD(&children); - INIT_LIST_HEAD(&songs); - - strcpy(path, _path); -} - -Directory::~Directory() -{ - Song *song, *ns; - directory_for_each_song_safe(song, ns, *this) - song->Free(); - - Directory *child, *n; - directory_for_each_child_safe(child, n, *this) - child->Free(); -} - -Directory * -Directory::NewGeneric(const char *path, Directory *parent) -{ - assert(path != nullptr); - assert((*path == 0) == (parent == nullptr)); - - Directory *directory = Allocate(path); - - directory->parent = parent; - - return directory; -} - -void -Directory::Free() -{ - this->Directory::~Directory(); - g_free(this); -} - -void -Directory::Delete() -{ - assert(holding_db_lock()); - assert(parent != nullptr); - - list_del(&siblings); - Free(); -} - -const char * -Directory::GetName() const -{ - assert(!IsRoot()); - - return PathTraits::GetBaseUTF8(path); -} - -Directory * -Directory::CreateChild(const char *name_utf8) -{ - assert(holding_db_lock()); - assert(name_utf8 != nullptr); - assert(*name_utf8 != 0); - - char *allocated; - const char *path_utf8; - if (IsRoot()) { - allocated = nullptr; - path_utf8 = name_utf8; - } else { - allocated = g_strconcat(GetPath(), - "/", name_utf8, nullptr); - path_utf8 = allocated; - } - - Directory *child = NewGeneric(path_utf8, this); - g_free(allocated); - - list_add_tail(&child->siblings, &children); - return child; -} - -const Directory * -Directory::FindChild(const char *name) const -{ - assert(holding_db_lock()); - - const Directory *child; - directory_for_each_child(child, *this) - if (strcmp(child->GetName(), name) == 0) - return child; - - return nullptr; -} - -void -Directory::PruneEmpty() -{ - assert(holding_db_lock()); - - Directory *child, *n; - directory_for_each_child_safe(child, n, *this) { - child->PruneEmpty(); - - if (child->IsEmpty()) - child->Delete(); - } -} - -Directory * -Directory::LookupDirectory(const char *uri) -{ - assert(holding_db_lock()); - assert(uri != nullptr); - - if (isRootDirectory(uri)) - return this; - - char *duplicated = g_strdup(uri), *name = duplicated; - - Directory *d = this; - while (1) { - char *slash = strchr(name, '/'); - if (slash == name) { - d = nullptr; - break; - } - - if (slash != nullptr) - *slash = '\0'; - - d = d->FindChild(name); - if (d == nullptr || slash == nullptr) - break; - - name = slash + 1; - } - - g_free(duplicated); - - return d; -} - -void -Directory::AddSong(Song *song) -{ - assert(holding_db_lock()); - assert(song != nullptr); - assert(song->parent == this); - - list_add_tail(&song->siblings, &songs); -} - -void -Directory::RemoveSong(Song *song) -{ - assert(holding_db_lock()); - assert(song != nullptr); - assert(song->parent == this); - - list_del(&song->siblings); -} - -const Song * -Directory::FindSong(const char *name_utf8) const -{ - assert(holding_db_lock()); - assert(name_utf8 != nullptr); - - Song *song; - directory_for_each_song(song, *this) { - assert(song->parent == this); - - if (strcmp(song->uri, name_utf8) == 0) - return song; - } - - return nullptr; -} - -Song * -Directory::LookupSong(const char *uri) -{ - char *duplicated, *base; - - assert(holding_db_lock()); - assert(uri != nullptr); - - duplicated = g_strdup(uri); - base = strrchr(duplicated, '/'); - - Directory *d = this; - if (base != nullptr) { - *base++ = 0; - d = d->LookupDirectory(duplicated); - if (d == nullptr) { - g_free(duplicated); - return nullptr; - } - } else - base = duplicated; - - Song *song = d->FindSong(base); - assert(song == nullptr || song->parent == d); - - g_free(duplicated); - return song; - -} - -static int -directory_cmp(gcc_unused void *priv, - struct list_head *_a, struct list_head *_b) -{ - const Directory *a = (const Directory *)_a; - const Directory *b = (const Directory *)_b; - return g_utf8_collate(a->path, b->path); -} - -void -Directory::Sort() -{ - assert(holding_db_lock()); - - list_sort(nullptr, &children, directory_cmp); - song_list_sort(&songs); - - Directory *child; - directory_for_each_child(child, *this) - child->Sort(); -} - -bool -Directory::Walk(bool recursive, const SongFilter *filter, - VisitDirectory visit_directory, VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const -{ - assert(!error.IsDefined()); - - if (visit_song) { - Song *song; - directory_for_each_song(song, *this) - if ((filter == nullptr || filter->Match(*song)) && - !visit_song(*song, error)) - return false; - } - - if (visit_playlist) { - for (const PlaylistInfo &p : playlists) - if (!visit_playlist(p, *this, error)) - return false; - } - - Directory *child; - directory_for_each_child(child, *this) { - if (visit_directory && - !visit_directory(*child, error)) - return false; - - if (recursive && - !child->Walk(recursive, filter, - visit_directory, visit_song, visit_playlist, - error)) - return false; - } - - return true; -} diff --git a/src/Directory.hxx b/src/Directory.hxx deleted file mode 100644 index 380a6b790..000000000 --- a/src/Directory.hxx +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_HXX -#define MPD_DIRECTORY_HXX - -#include "check.h" -#include "util/list.h" -#include "Compiler.h" -#include "DatabaseVisitor.hxx" -#include "PlaylistVector.hxx" - -#include <sys/types.h> - -#define DEVICE_INARCHIVE (dev_t)(-1) -#define DEVICE_CONTAINER (dev_t)(-2) - -#define directory_for_each_child(pos, directory) \ - list_for_each_entry(pos, &(directory).children, siblings) - -#define directory_for_each_child_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &(directory).children, siblings) - -#define directory_for_each_song(pos, directory) \ - list_for_each_entry(pos, &(directory).songs, siblings) - -#define directory_for_each_song_safe(pos, n, directory) \ - list_for_each_entry_safe(pos, n, &(directory).songs, siblings) - -struct Song; -struct db_visitor; -class SongFilter; -class Error; - -struct Directory { - /** - * Pointers to the siblings of this directory within the - * parent directory. It is unused (undefined) in the root - * directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head siblings; - - /** - * A doubly linked list of child directories. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head children; - - /** - * A doubly linked list of songs within this directory. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head songs; - - PlaylistVector playlists; - - Directory *parent; - time_t mtime; - ino_t inode; - dev_t device; - bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */ - char path[sizeof(long)]; - -protected: - Directory(const char *path); - - gcc_malloc gcc_nonnull_all - static Directory *Allocate(const char *path); - -public: - /** - * Default constructor, needed for #detached_root. - */ - Directory(); - ~Directory(); - - /** - * Generic constructor for #Directory object. - */ - gcc_malloc - static Directory *NewGeneric(const char *path_utf8, Directory *parent); - - /** - * Create a new root #Directory object. - */ - gcc_malloc - static Directory *NewRoot() { - return NewGeneric("", nullptr); - } - - /** - * Free this #Directory object (and the whole object tree within it), - * assuming it was already removed from the parent. - */ - void Free(); - - /** - * Remove this #Directory object from its parent and free it. This - * must not be called with the root Directory. - * - * Caller must lock the #db_mutex. - */ - void Delete(); - - /** - * Create a new #Directory object as a child of the given one. - * - * Caller must lock the #db_mutex. - * - * @param name_utf8 the UTF-8 encoded name of the new sub directory - */ - gcc_malloc - Directory *CreateChild(const char *name_utf8); - - /** - * Caller must lock the #db_mutex. - */ - gcc_pure - const Directory *FindChild(const char *name) const; - - gcc_pure - Directory *FindChild(const char *name) { - const Directory *cthis = this; - return const_cast<Directory *>(cthis->FindChild(name)); - } - - /** - * Look up a sub directory, and create the object if it does not - * exist. - * - * Caller must lock the #db_mutex. - */ - Directory *MakeChild(const char *name_utf8) { - Directory *child = FindChild(name_utf8); - if (child == nullptr) - child = CreateChild(name_utf8); - return child; - } - - /** - * Looks up a directory by its relative URI. - * - * @param uri the relative URI - * @return the Directory, or nullptr if none was found - */ - gcc_pure - Directory *LookupDirectory(const char *uri); - - gcc_pure - bool IsEmpty() const { - return list_empty(&children) && - list_empty(&songs) && - playlists.empty(); - } - - gcc_pure - const char *GetPath() const { - return path; - } - - /** - * Returns the base name of the directory. - */ - gcc_pure - const char *GetName() const; - - /** - * Is this the root directory of the music database? - */ - gcc_pure - bool IsRoot() const { - return parent == nullptr; - } - - /** - * Look up a song in this directory by its name. - * - * Caller must lock the #db_mutex. - */ - gcc_pure - const Song *FindSong(const char *name_utf8) const; - - gcc_pure - Song *FindSong(const char *name_utf8) { - const Directory *cthis = this; - return const_cast<Song *>(cthis->FindSong(name_utf8)); - } - - /** - * Looks up a song by its relative URI. - * - * Caller must lock the #db_mutex. - * - * @param uri the relative URI - * @return the song, or nullptr if none was found - */ - gcc_pure - Song *LookupSong(const char *uri); - - /** - * Add a song object to this directory. Its "parent" attribute must - * be set already. - */ - void AddSong(Song *song); - - /** - * Remove a song object from this directory (which effectively - * invalidates the song object, because the "parent" attribute becomes - * stale), but does not free it. - */ - void RemoveSong(Song *song); - - /** - * Caller must lock the #db_mutex. - */ - void PruneEmpty(); - - /** - * Sort all directory entries recursively. - * - * Caller must lock the #db_mutex. - */ - void Sort(); - - /** - * Caller must lock #db_mutex. - */ - bool Walk(bool recursive, const SongFilter *match, - VisitDirectory visit_directory, VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const; -}; - -static inline bool -isRootDirectory(const char *name) -{ - return name[0] == 0 || (name[0] == '/' && name[1] == 0); -} - -#endif diff --git a/src/DirectorySave.cxx b/src/DirectorySave.cxx deleted file mode 100644 index fa330d126..000000000 --- a/src/DirectorySave.cxx +++ /dev/null @@ -1,171 +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 "DirectorySave.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "SongSave.hxx" -#include "PlaylistDatabase.hxx" -#include "TextFile.hxx" -#include "util/NumberParser.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -#define DIRECTORY_DIR "directory: " -#define DIRECTORY_MTIME "mtime: " -#define DIRECTORY_BEGIN "begin: " -#define DIRECTORY_END "end: " - -static constexpr Domain directory_domain("directory"); - -void -directory_save(FILE *fp, const Directory &directory) -{ - if (!directory.IsRoot()) { - fprintf(fp, DIRECTORY_MTIME "%lu\n", - (unsigned long)directory.mtime); - - fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory.GetPath()); - } - - Directory *cur; - directory_for_each_child(cur, directory) { - fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName()); - - directory_save(fp, *cur); - - if (ferror(fp)) - return; - } - - Song *song; - directory_for_each_song(song, directory) - song_save(fp, *song); - - playlist_vector_save(fp, directory.playlists); - - if (!directory.IsRoot()) - fprintf(fp, DIRECTORY_END "%s\n", directory.GetPath()); -} - -static Directory * -directory_load_subdir(TextFile &file, Directory &parent, const char *name, - Error &error) -{ - bool success; - - if (parent.FindChild(name) != nullptr) { - error.Format(directory_domain, - "Duplicate subdirectory '%s'", name); - return nullptr; - } - - Directory *directory = parent.CreateChild(name); - - const char *line = file.ReadLine(); - if (line == nullptr) { - error.Set(directory_domain, "Unexpected end of file"); - directory->Delete(); - return nullptr; - } - - if (g_str_has_prefix(line, DIRECTORY_MTIME)) { - directory->mtime = - ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1); - - line = file.ReadLine(); - if (line == nullptr) { - error.Set(directory_domain, "Unexpected end of file"); - directory->Delete(); - return nullptr; - } - } - - if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { - error.Format(directory_domain, "Malformed line: %s", line); - directory->Delete(); - return nullptr; - } - - success = directory_load(file, *directory, error); - if (!success) { - directory->Delete(); - return nullptr; - } - - return directory; -} - -bool -directory_load(TextFile &file, Directory &directory, Error &error) -{ - const char *line; - - while ((line = file.ReadLine()) != nullptr && - !g_str_has_prefix(line, DIRECTORY_END)) { - if (g_str_has_prefix(line, DIRECTORY_DIR)) { - Directory *subdir = - directory_load_subdir(file, directory, - line + sizeof(DIRECTORY_DIR) - 1, - error); - if (subdir == nullptr) - return false; - } else if (g_str_has_prefix(line, SONG_BEGIN)) { - const char *name = line + sizeof(SONG_BEGIN) - 1; - Song *song; - - if (directory.FindSong(name) != nullptr) { - error.Format(directory_domain, - "Duplicate song '%s'", name); - return false; - } - - song = song_load(file, &directory, name, error); - if (song == nullptr) - return false; - - directory.AddSong(song); - } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { - /* duplicate the name, because - playlist_metadata_load() will overwrite the - buffer */ - char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1); - - if (!playlist_metadata_load(file, directory.playlists, - name, error)) { - g_free(name); - return false; - } - - g_free(name); - } else { - error.Format(directory_domain, - "Malformed line: %s", line); - return false; - } - } - - return true; -} diff --git a/src/DirectorySave.hxx b/src/DirectorySave.hxx deleted file mode 100644 index 02814490a..000000000 --- a/src/DirectorySave.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DIRECTORY_SAVE_HXX -#define MPD_DIRECTORY_SAVE_HXX - -#include <stdio.h> - -struct Directory; -class TextFile; -class Error; - -void -directory_save(FILE *fp, const Directory &directory); - -bool -directory_load(TextFile &file, Directory &directory, Error &error); - -#endif diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx deleted file mode 100644 index b3397f25c..000000000 --- a/src/EncoderAPI.hxx +++ /dev/null @@ -1,33 +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. - */ - -/* - * This header is included by encoder plugins. - * - */ - -#ifndef MPD_ENCODER_API_HXX -#define MPD_ENCODER_API_HXX - -#include "EncoderPlugin.hxx" -#include "AudioFormat.hxx" -#include "tag/Tag.hxx" -#include "ConfigData.hxx" - -#endif diff --git a/src/EncoderList.cxx b/src/EncoderList.cxx deleted file mode 100644 index 7760a9582..000000000 --- a/src/EncoderList.cxx +++ /dev/null @@ -1,64 +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 "EncoderList.hxx" -#include "EncoderPlugin.hxx" -#include "encoder/NullEncoderPlugin.hxx" -#include "encoder/WaveEncoderPlugin.hxx" -#include "encoder/VorbisEncoderPlugin.hxx" -#include "encoder/OpusEncoderPlugin.hxx" -#include "encoder/FlacEncoderPlugin.hxx" -#include "encoder/LameEncoderPlugin.hxx" -#include "encoder/TwolameEncoderPlugin.hxx" - -#include <string.h> - -const EncoderPlugin *const encoder_plugins[] = { - &null_encoder_plugin, -#ifdef ENABLE_VORBIS_ENCODER - &vorbis_encoder_plugin, -#endif -#ifdef HAVE_OPUS - &opus_encoder_plugin, -#endif -#ifdef ENABLE_LAME_ENCODER - &lame_encoder_plugin, -#endif -#ifdef ENABLE_TWOLAME_ENCODER - &twolame_encoder_plugin, -#endif -#ifdef ENABLE_WAVE_ENCODER - &wave_encoder_plugin, -#endif -#ifdef ENABLE_FLAC_ENCODER - &flac_encoder_plugin, -#endif - nullptr -}; - -const EncoderPlugin * -encoder_plugin_get(const char *name) -{ - encoder_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return nullptr; -} diff --git a/src/EncoderList.hxx b/src/EncoderList.hxx deleted file mode 100644 index 8cbb389a4..000000000 --- a/src/EncoderList.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_LIST_HXX -#define MPD_ENCODER_LIST_HXX - -struct EncoderPlugin; - -extern const EncoderPlugin *const encoder_plugins[]; - -#define encoder_plugins_for_each(plugin) \ - for (const EncoderPlugin *plugin, \ - *const*encoder_plugin_iterator = &encoder_plugins[0]; \ - (plugin = *encoder_plugin_iterator) != nullptr; \ - ++encoder_plugin_iterator) - -/** - * Looks up an encoder plugin by its name. - * - * @param name the encoder name to look for - * @return the encoder plugin with the specified name, or nullptr if none - * was found - */ -const EncoderPlugin * -encoder_plugin_get(const char *name); - -#endif diff --git a/src/EncoderPlugin.hxx b/src/EncoderPlugin.hxx deleted file mode 100644 index 8ad5ea0c6..000000000 --- a/src/EncoderPlugin.hxx +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_PLUGIN_HXX -#define MPD_ENCODER_PLUGIN_HXX - -#include <assert.h> -#include <stdbool.h> -#include <stddef.h> - -struct EncoderPlugin; -struct AudioFormat; -struct config_param; -struct Tag; -class Error; - -struct Encoder { - const EncoderPlugin &plugin; - -#ifndef NDEBUG - bool open, pre_tag, tag, end; -#endif - - explicit Encoder(const EncoderPlugin &_plugin) - :plugin(_plugin) -#ifndef NDEBUG - , open(false) -#endif - {} -}; - -struct EncoderPlugin { - const char *name; - - Encoder *(*init)(const config_param ¶m, - Error &error); - - void (*finish)(Encoder *encoder); - - bool (*open)(Encoder *encoder, - AudioFormat &audio_format, - Error &error); - - void (*close)(Encoder *encoder); - - bool (*end)(Encoder *encoder, Error &error); - - bool (*flush)(Encoder *encoder, Error &error); - - bool (*pre_tag)(Encoder *encoder, Error &error); - - bool (*tag)(Encoder *encoder, const Tag *tag, - Error &error); - - bool (*write)(Encoder *encoder, - const void *data, size_t length, - Error &error); - - size_t (*read)(Encoder *encoder, void *dest, size_t length); - - const char *(*get_mime_type)(Encoder *encoder); -}; - -/** - * Creates a new encoder object. - * - * @param plugin the encoder plugin - * @param param optional configuration - * @param error location to store the error occurring, or nullptr to ignore errors. - * @return an encoder object on success, nullptr on failure - */ -static inline Encoder * -encoder_init(const EncoderPlugin &plugin, const config_param ¶m, - Error &error_r) -{ - return plugin.init(param, error_r); -} - -/** - * Frees an encoder object. - * - * @param encoder the encoder - */ -static inline void -encoder_finish(Encoder *encoder) -{ - assert(!encoder->open); - - encoder->plugin.finish(encoder); -} - -/** - * Opens an encoder object. You must call this prior to using it. - * Before you free it, you must call encoder_close(). You may open - * and close (reuse) one encoder any number of times. - * - * After this function returns successfully and before the first - * encoder_write() call, you should invoke encoder_read() to obtain - * the file header. - * - * @param encoder the encoder - * @param audio_format the encoder's input audio format; the plugin - * may modify the struct to adapt it to its abilities - * @return true on success - */ -static inline bool -encoder_open(Encoder *encoder, AudioFormat &audio_format, - Error &error) -{ - assert(!encoder->open); - - bool success = encoder->plugin.open(encoder, audio_format, error); -#ifndef NDEBUG - encoder->open = success; - encoder->pre_tag = encoder->tag = encoder->end = false; -#endif - return success; -} - -/** - * Closes an encoder object. This disables the encoder, and readies - * it for reusal by calling encoder_open() again. - * - * @param encoder the encoder - */ -static inline void -encoder_close(Encoder *encoder) -{ - assert(encoder->open); - - if (encoder->plugin.close != nullptr) - encoder->plugin.close(encoder); - -#ifndef NDEBUG - encoder->open = false; -#endif -} - -/** - * Ends the stream: flushes the encoder object, generate an - * end-of-stream marker (if applicable), make everything which might - * currently be buffered available by encoder_read(). - * - * After this function has been called, the encoder may not be usable - * for more data, and only encoder_read() and encoder_close() can be - * called. - * - * @param encoder the encoder - * @return true on success - */ -static inline bool -encoder_end(Encoder *encoder, Error &error) -{ - assert(encoder->open); - assert(!encoder->end); - -#ifndef NDEBUG - encoder->end = true; -#endif - - /* this method is optional */ - return encoder->plugin.end != nullptr - ? encoder->plugin.end(encoder, error) - : true; -} - -/** - * Flushes an encoder object, make everything which might currently be - * buffered available by encoder_read(). - * - * @param encoder the encoder - * @return true on success - */ -static inline bool -encoder_flush(Encoder *encoder, Error &error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(!encoder->tag); - assert(!encoder->end); - - /* this method is optional */ - return encoder->plugin.flush != nullptr - ? encoder->plugin.flush(encoder, error) - : true; -} - -/** - * Prepare for sending a tag to the encoder. This is used by some - * encoders to flush the previous sub-stream, in preparation to begin - * a new one. - * - * @param encoder the encoder - * @param tag the tag object - * @return true on success - */ -static inline bool -encoder_pre_tag(Encoder *encoder, Error &error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(!encoder->tag); - assert(!encoder->end); - - /* this method is optional */ - bool success = encoder->plugin.pre_tag != nullptr - ? encoder->plugin.pre_tag(encoder, error) - : true; - -#ifndef NDEBUG - encoder->pre_tag = success; -#endif - return success; -} - -/** - * Sends a tag to the encoder. - * - * Instructions: call encoder_pre_tag(); then obtain flushed data with - * encoder_read(); finally call encoder_tag(). - * - * @param encoder the encoder - * @param tag the tag object - * @return true on success - */ -static inline bool -encoder_tag(Encoder *encoder, const Tag *tag, Error &error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(encoder->tag); - assert(!encoder->end); - -#ifndef NDEBUG - encoder->tag = false; -#endif - - /* this method is optional */ - return encoder->plugin.tag != nullptr - ? encoder->plugin.tag(encoder, tag, error) - : true; -} - -/** - * Writes raw PCM data to the encoder. - * - * @param encoder the encoder - * @param data the buffer containing PCM samples - * @param length the length of the buffer in bytes - * @return true on success - */ -static inline bool -encoder_write(Encoder *encoder, const void *data, size_t length, - Error &error) -{ - assert(encoder->open); - assert(!encoder->pre_tag); - assert(!encoder->tag); - assert(!encoder->end); - - return encoder->plugin.write(encoder, data, length, error); -} - -/** - * Reads encoded data from the encoder. - * - * Call this repeatedly until no more data is returned. - * - * @param encoder the encoder - * @param dest the destination buffer to copy to - * @param length the maximum length of the destination buffer - * @return the number of bytes written to #dest - */ -static inline size_t -encoder_read(Encoder *encoder, void *dest, size_t length) -{ - assert(encoder->open); - assert(!encoder->pre_tag || !encoder->tag); - -#ifndef NDEBUG - if (encoder->pre_tag) { - encoder->pre_tag = false; - encoder->tag = true; - } -#endif - - return encoder->plugin.read(encoder, dest, length); -} - -/** - * Get mime type of encoded content. - * - * @param plugin the encoder plugin - * @return an constant string, nullptr on failure - */ -static inline const char * -encoder_get_mime_type(Encoder *encoder) -{ - /* this method is optional */ - return encoder->plugin.get_mime_type != nullptr - ? encoder->plugin.get_mime_type(encoder) - : nullptr; -} - -#endif diff --git a/src/ExcludeList.cxx b/src/ExcludeList.cxx deleted file mode 100644 index d34afd897..000000000 --- a/src/ExcludeList.cxx +++ /dev/null @@ -1,83 +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. - */ - -/* - * The .mpdignore backend code. - * - */ - -#include "config.h" -#include "ExcludeList.hxx" -#include "fs/Path.hxx" -#include "fs/FileSystem.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> -#include <errno.h> - -static constexpr Domain exclude_list_domain("exclude_list"); - -bool -ExcludeList::LoadFile(Path path_fs) -{ - FILE *file = FOpen(path_fs, FOpenMode::ReadText); - if (file == nullptr) { - const int e = errno; - if (e != ENOENT) { - const auto path_utf8 = path_fs.ToUTF8(); - FormatErrno(exclude_list_domain, - "Failed to open %s", - path_utf8.c_str()); - } - - return false; - } - - char line[1024]; - while (fgets(line, sizeof(line), file) != nullptr) { - char *p = strchr(line, '#'); - if (p != nullptr) - *p = 0; - - p = g_strstrip(line); - if (*p != 0) - patterns.emplace_front(p); - } - - fclose(file); - - return true; -} - -bool -ExcludeList::Check(Path name_fs) const -{ - assert(!name_fs.IsNull()); - - /* XXX include full path name in check */ - - for (const auto &i : patterns) - if (i.Check(name_fs.c_str())) - return true; - - return false; -} diff --git a/src/ExcludeList.hxx b/src/ExcludeList.hxx deleted file mode 100644 index e15f902f9..000000000 --- a/src/ExcludeList.hxx +++ /dev/null @@ -1,80 +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. - */ - -/* - * The .mpdignore backend code. - * - */ - -#ifndef MPD_EXCLUDE_H -#define MPD_EXCLUDE_H - -#include "Compiler.h" - -#include <forward_list> - -#include <glib.h> - -class Path; - -class ExcludeList { - class Pattern { - GPatternSpec *pattern; - - public: - Pattern(const char *_pattern) - :pattern(g_pattern_spec_new(_pattern)) {} - - Pattern(Pattern &&other) - :pattern(other.pattern) { - other.pattern = nullptr; - } - - ~Pattern() { - g_pattern_spec_free(pattern); - } - - gcc_pure - bool Check(const char *name_fs) const { - return g_pattern_match_string(pattern, name_fs); - } - }; - - std::forward_list<Pattern> patterns; - -public: - gcc_pure - bool IsEmpty() const { - return patterns.empty(); - } - - /** - * Loads and parses a .mpdignore file. - */ - bool LoadFile(Path path_fs); - - /** - * Checks whether one of the patterns in the .mpdignore file matches - * the specified file name. - */ - bool Check(Path name_fs) const; -}; - - -#endif diff --git a/src/FilterConfig.cxx b/src/FilterConfig.cxx deleted file mode 100644 index cfac1c756..000000000 --- a/src/FilterConfig.cxx +++ /dev/null @@ -1,110 +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 "FilterConfig.hxx" -#include "filter/ChainFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "ConfigData.hxx" -#include "ConfigOption.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" - -#include <algorithm> - -#include <string.h> - -/** - * Find the "filter" configuration block for the specified name. - * - * @param filter_template_name the name of the filter template - * @param error space to return an error description - * @return the configuration block, or nullptr if none was configured - */ -static const struct config_param * -filter_plugin_config(const char *filter_template_name, Error &error) -{ - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != nullptr) { - const char *name = param->GetBlockValue("name"); - if (name == nullptr) { - error.Format(config_domain, - "filter configuration without 'name' name in line %d", - param->line); - return nullptr; - } - - if (strcmp(name, filter_template_name) == 0) - return param; - } - - error.Format(config_domain, - "filter template not found: %s", - filter_template_name); - return nullptr; -} - -static bool -filter_chain_append_new(Filter &chain, const char *template_name, Error &error) -{ - const struct config_param *cfg = - filter_plugin_config(template_name, error); - if (cfg == nullptr) - // The error has already been set, just stop. - return false; - - // Instantiate one of those filter plugins with the template name as a hint - Filter *f = filter_configured_new(*cfg, error); - if (f == nullptr) - // The error has already been set, just stop. - return false; - - const char *plugin_name = cfg->GetBlockValue("plugin", - "unknown"); - filter_chain_append(chain, plugin_name, f); - - return true; -} - -bool -filter_chain_parse(Filter &chain, const char *spec, Error &error) -{ - const char *const end = spec + strlen(spec); - - while (true) { - const char *comma = std::find(spec, end, ','); - if (comma > spec) { - const std::string name(spec, comma); - if (!filter_chain_append_new(chain, name.c_str(), - error)) - return false; - } - - if (comma == end) - break; - - spec = comma + 1; - } - - return true; -} diff --git a/src/FilterConfig.hxx b/src/FilterConfig.hxx deleted file mode 100644 index 6b074b930..000000000 --- a/src/FilterConfig.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Utility functions for filter configuration - */ - -#ifndef MPD_FILTER_CONFIG_HXX -#define MPD_FILTER_CONFIG_HXX - -class Filter; -class Error; - -/** - * Builds a filter chain from a configuration string on the form - * "name1, name2, name3, ..." by looking up each name among the - * configured filter sections. - * @param chain the chain to append filters on - * @param spec the filter chain specification - * @param error_r space to return an error description - * @return true on success - */ -bool -filter_chain_parse(Filter &chain, const char *spec, Error &error); - -#endif diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx deleted file mode 100644 index e2884e82f..000000000 --- a/src/FilterInternal.hxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal stuff for the filter core and filter plugins. - */ - -#ifndef MPD_FILTER_INTERNAL_HXX -#define MPD_FILTER_INTERNAL_HXX - -#include <stddef.h> - -struct AudioFormat; -class Error; - -class Filter { -public: - virtual ~Filter() {} - - /** - * Opens the filter, preparing it for FilterPCM(). - * - * @param filter the filter object - * @param audio_format the audio format of incoming data; the - * plugin may modify the object to enforce another input - * format - * @param error location to store the error occurring, or nullptr - * to ignore errors. - * @return the format of outgoing data or - * AudioFormat::Undefined() on error - */ - virtual AudioFormat Open(AudioFormat &af, Error &error) = 0; - - /** - * Closes the filter. After that, you may call Open() again. - */ - virtual void Close() = 0; - - /** - * Filters a block of PCM data. - * - * @param filter the filter object - * @param src the input buffer - * @param src_size the size of #src_buffer in bytes - * @param dest_size_r the size of the returned buffer - * @param error location to store the error occurring, or nullptr - * to ignore errors. - * @return the destination buffer on success (will be - * invalidated by filter_close() or filter_filter()), nullptr on - * error - */ - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, - Error &error) = 0; -}; - -#endif diff --git a/src/FilterPlugin.cxx b/src/FilterPlugin.cxx deleted file mode 100644 index 608542f92..000000000 --- a/src/FilterPlugin.cxx +++ /dev/null @@ -1,59 +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 "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "ConfigData.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" - -#include <assert.h> - -Filter * -filter_new(const struct filter_plugin *plugin, - const config_param ¶m, Error &error) -{ - assert(plugin != nullptr); - assert(!error.IsDefined()); - - return plugin->init(param, error); -} - -Filter * -filter_configured_new(const config_param ¶m, Error &error) -{ - assert(!error.IsDefined()); - - const char *plugin_name = param.GetBlockValue("plugin"); - if (plugin_name == nullptr) { - error.Set(config_domain, "No filter plugin specified"); - return nullptr; - } - - const filter_plugin *plugin = filter_plugin_by_name(plugin_name); - if (plugin == nullptr) { - error.Format(config_domain, - "No such filter plugin: %s", plugin_name); - return nullptr; - } - - return filter_new(plugin, param, error); -} diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx deleted file mode 100644 index 5ff4637b3..000000000 --- a/src/FilterPlugin.hxx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the filter_plugin class. It describes a - * plugin API for objects which filter raw PCM data. - */ - -#ifndef MPD_FILTER_PLUGIN_HXX -#define MPD_FILTER_PLUGIN_HXX - -struct config_param; -class Filter; -class Error; - -struct filter_plugin { - const char *name; - - /** - * Allocates and configures a filter. - */ - Filter *(*init)(const config_param ¶m, Error &error); -}; - -/** - * Creates a new instance of the specified filter plugin. - * - * @param plugin the filter plugin - * @param param optional configuration section - * @param error location to store the error occurring, or nullptr to - * ignore errors. - * @return a new filter object, or nullptr on error - */ -Filter * -filter_new(const struct filter_plugin *plugin, - const config_param ¶m, Error &error); - -/** - * Creates a new filter, loads configuration and the plugin name from - * the specified configuration section. - * - * @param param the configuration section - * @param error location to store the error occurring, or nullptr to - * ignore errors. - * @return a new filter object, or nullptr on error - */ -Filter * -filter_configured_new(const config_param ¶m, Error &error); - -#endif diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx deleted file mode 100644 index b3b08505e..000000000 --- a/src/FilterRegistry.cxx +++ /dev/null @@ -1,44 +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 "FilterRegistry.hxx" -#include "FilterPlugin.hxx" - -#include <stddef.h> -#include <string.h> - -const struct filter_plugin *const filter_plugins[] = { - &null_filter_plugin, - &route_filter_plugin, - &normalize_filter_plugin, - &volume_filter_plugin, - &replay_gain_filter_plugin, - nullptr, -}; - -const struct filter_plugin * -filter_plugin_by_name(const char *name) -{ - for (unsigned i = 0; filter_plugins[i] != nullptr; ++i) - if (strcmp(filter_plugins[i]->name, name) == 0) - return filter_plugins[i]; - - return nullptr; -} diff --git a/src/FilterRegistry.hxx b/src/FilterRegistry.hxx deleted file mode 100644 index 286420f92..000000000 --- a/src/FilterRegistry.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This library manages all filter plugins which are enabled at - * compile time. - */ - -#ifndef MPD_FILTER_REGISTRY_HXX -#define MPD_FILTER_REGISTRY_HXX - -#include "Compiler.h" - -extern const struct filter_plugin null_filter_plugin; -extern const struct filter_plugin chain_filter_plugin; -extern const struct filter_plugin convert_filter_plugin; -extern const struct filter_plugin route_filter_plugin; -extern const struct filter_plugin normalize_filter_plugin; -extern const struct filter_plugin volume_filter_plugin; -extern const struct filter_plugin replay_gain_filter_plugin; - -gcc_pure -const struct filter_plugin * -filter_plugin_by_name(const char *name); - -#endif diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx index 86bfb3e2a..9c60f6357 100644 --- a/src/GlobalEvents.cxx +++ b/src/GlobalEvents.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 @@ -21,7 +21,6 @@ #include "GlobalEvents.hxx" #include "util/Manual.hxx" #include "event/DeferredMonitor.hxx" -#include "Compiler.h" #include <atomic> diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx index 47ec6d070..a9df03724 100644 --- a/src/GlobalEvents.hxx +++ b/src/GlobalEvents.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 @@ -33,12 +33,6 @@ class EventLoop; namespace GlobalEvents { enum Event { - /** database update was finished */ - UPDATE, - - /** during database update, a song was deleted */ - DELETE, - /** an idle event was emitted */ IDLE, @@ -48,9 +42,6 @@ namespace GlobalEvents { /** the current song's tag has changed */ TAG, - /** a hardware mixer plugin has detected a change */ - MIXER, - #ifdef WIN32 /** shutdown requested */ SHUTDOWN, diff --git a/src/IOThread.cxx b/src/IOThread.cxx index a14f12eb1..e21ede4f3 100644 --- a/src/IOThread.cxx +++ b/src/IOThread.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,6 +22,7 @@ #include "thread/Mutex.hxx" #include "thread/Cond.hxx" #include "thread/Thread.hxx" +#include "thread/Name.hxx" #include "event/Loop.hxx" #include "system/FatalError.hxx" #include "util/Error.hxx" @@ -48,6 +49,8 @@ io_thread_run(void) static void io_thread_func(gcc_unused void *arg) { + SetThreadName("io"); + /* lock+unlock to synchronize with io_thread_start(), to be sure that io.thread is set */ io.mutex.lock(); diff --git a/src/IOThread.hxx b/src/IOThread.hxx index 3384a09e0..f6f5dffec 100644 --- a/src/IOThread.hxx +++ b/src/IOThread.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 @@ -49,7 +49,7 @@ io_thread_quit(void); void io_thread_deinit(void); -gcc_pure +gcc_const EventLoop & io_thread_get(); diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx index bfa2e8558..4c13c2c2c 100644 --- a/src/IcyMetaDataParser.cxx +++ b/src/IcyMetaDataParser.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 @@ -20,11 +20,10 @@ #include "config.h" #include "IcyMetaDataParser.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> @@ -37,7 +36,7 @@ IcyMetaDataParser::Reset() return; if (data_rest == 0 && meta_size > 0) - g_free(meta_data); + delete[] meta_data; delete tag; @@ -66,7 +65,7 @@ IcyMetaDataParser::Data(size_t length) } static void -icy_add_item(Tag &tag, TagType type, const char *value) +icy_add_item(TagBuilder &tag, TagType type, const char *value) { size_t length = strlen(value); @@ -81,7 +80,7 @@ icy_add_item(Tag &tag, TagType type, const char *value) } static void -icy_parse_tag_item(Tag &tag, const char *name, const char *value) +icy_parse_tag_item(TagBuilder &tag, const char *name, const char *value) { if (strcmp(name, "StreamTitle") == 0) icy_add_item(tag, TAG_TITLE, value); @@ -122,7 +121,7 @@ icy_parse_tag(char *p, char *const end) assert(end != nullptr); assert(p <= end); - Tag *tag = new Tag(); + TagBuilder tag; while (p != end) { const char *const name = p; @@ -153,7 +152,7 @@ icy_parse_tag(char *p, char *const end) *quote = 0; p = quote + 1; - icy_parse_tag_item(*tag, name, value); + icy_parse_tag_item(tag, name, value); char *semicolon = std::find(p, end, ';'); if (semicolon == end) @@ -161,7 +160,7 @@ icy_parse_tag(char *p, char *const end) p = semicolon + 1; } - return tag; + return tag.CommitNew(); } size_t @@ -190,7 +189,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length) /* initialize metadata reader, allocate enough memory (+1 for the null terminator) */ meta_position = 0; - meta_data = (char *)g_malloc(meta_size + 1); + meta_data = new char[meta_size + 1]; } assert(meta_position < meta_size); @@ -211,7 +210,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length) delete tag; tag = icy_parse_tag(meta_data, meta_data + meta_size); - g_free(meta_data); + delete[] meta_data; /* change back to normal data mode */ @@ -221,3 +220,32 @@ IcyMetaDataParser::Meta(const void *data, size_t length) return length; } + +size_t +IcyMetaDataParser::ParseInPlace(void *data, size_t length) +{ + uint8_t *const dest0 = (uint8_t *)data; + uint8_t *dest = dest0; + const uint8_t *src = dest0; + + while (length > 0) { + size_t chunk = Data(length); + if (chunk > 0) { + memmove(dest, src, chunk); + dest += chunk; + src += chunk; + length -= chunk; + + if (length == 0) + break; + } + + chunk = Meta(src, length); + if (chunk > 0) { + src += chunk; + length -= chunk; + } + } + + return dest - dest0; +} diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx index 6bcb09668..3075485b2 100644 --- a/src/IcyMetaDataParser.hxx +++ b/src/IcyMetaDataParser.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 @@ -75,6 +75,13 @@ public: */ size_t Meta(const void *data, size_t length); + /** + * Parse data and eliminate metadata. + * + * @return the number of data bytes remaining in the buffer + */ + size_t ParseInPlace(void *data, size_t length); + Tag *ReadTag() { Tag *result = tag; tag = nullptr; diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx deleted file mode 100644 index f5e981c2f..000000000 --- a/src/IcyMetaDataServer.cxx +++ /dev/null @@ -1,135 +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 "IcyMetaDataServer.hxx" -#include "Page.hxx" -#include "tag/Tag.hxx" -#include "util/FormatString.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -char* -icy_server_metadata_header(const char *name, - const char *genre, const char *url, - const char *content_type, int metaint) -{ - return FormatNew("ICY 200 OK\r\n" - "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ - "icy-notice2:MPD - The music player daemon<BR>\r\n" - "icy-name: %s\r\n" /* TODO */ - "icy-genre: %s\r\n" /* TODO */ - "icy-url: %s\r\n" /* TODO */ - "icy-pub:1\r\n" - "icy-metaint:%d\r\n" - /* TODO "icy-br:%d\r\n" */ - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - name, - genre, - url, - metaint, - /* bitrate, */ - content_type); -} - -static char * -icy_server_metadata_string(const char *stream_title, const char* stream_url) -{ - gchar *icy_metadata; - guint meta_length; - - // The leading n is a placeholder for the length information - icy_metadata = FormatNew("nStreamTitle='%s';" - "StreamUrl='%s';", - stream_title, - stream_url); - - meta_length = strlen(icy_metadata); - - meta_length--; // subtract placeholder - - meta_length = ((int)meta_length / 16) + 1; - - icy_metadata[0] = meta_length; - - if (meta_length > 255) { - delete[] icy_metadata; - return nullptr; - } - - return icy_metadata; -} - -Page * -icy_server_metadata_page(const Tag &tag, const TagType *types) -{ - const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; - gint last_item, item; - guint position; - gchar *icy_string; - gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - - // "StreamTitle='';StreamUrl='';" - // = 4081 - 28 - stream_title[0] = '\0'; - - last_item = -1; - - while (*types != TAG_NUM_OF_ITEM_TYPES) { - const gchar *tag_item = tag.GetValue(*types++); - if (tag_item) - tag_items[++last_item] = tag_item; - } - - position = item = 0; - while (position < sizeof(stream_title) && item <= last_item) { - gint length = 0; - - length = g_strlcpy(stream_title + position, - tag_items[item++], - sizeof(stream_title) - position); - - position += length; - - if (item <= last_item) { - length = g_strlcpy(stream_title + position, - " - ", - sizeof(stream_title) - position); - - position += length; - } - } - - icy_string = icy_server_metadata_string(stream_title, ""); - - if (icy_string == nullptr) - return nullptr; - - Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1); - - delete[] icy_string; - - return icy_metadata; -} diff --git a/src/IcyMetaDataServer.hxx b/src/IcyMetaDataServer.hxx deleted file mode 100644 index 20964ce74..000000000 --- a/src/IcyMetaDataServer.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ICY_META_DATA_SERVER_HXX -#define MPD_ICY_META_DATA_SERVER_HXX - -#include "tag/TagType.h" - -struct Tag; -class Page; - -/** - * Free the return value with delete[]. - */ -char* -icy_server_metadata_header(const char *name, - const char *genre, const char *url, - const char *content_type, int metaint); - -Page * -icy_server_metadata_page(const Tag &tag, const TagType *types); - -#endif diff --git a/src/IdTable.hxx b/src/IdTable.hxx deleted file mode 100644 index ab021e48f..000000000 --- a/src/IdTable.hxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ID_TABLE_HXX -#define MPD_ID_TABLE_HXX - -#include "Compiler.h" - -#include <algorithm> - -#include <assert.h> - -/** - * A table that maps id numbers to position numbers. - */ -class IdTable { - unsigned size; - - unsigned next; - - int *data; - -public: - IdTable(unsigned _size):size(_size), next(1), data(new int[size]) { - std::fill_n(data, size, -1); - } - - ~IdTable() { - delete[] data; - } - - int IdToPosition(unsigned id) const { - return id < size - ? data[id] - : -1; - } - - unsigned GenerateId() { - assert(next > 0); - assert(next < size); - - while (true) { - unsigned id = next; - - ++next; - if (next == size) - next = 1; - - if (data[id] < 0) - return id; - } - } - - unsigned Insert(unsigned position) { - unsigned id = GenerateId(); - data[id] = position; - return id; - } - - void Move(unsigned id, unsigned position) { - assert(id < size); - assert(data[id] >= 0); - - data[id] = position; - } - - void Erase(unsigned id) { - assert(id < size); - assert(data[id] >= 0); - - data[id] = -1; - } -}; - -#endif diff --git a/src/Idle.cxx b/src/Idle.cxx index 840a5d4e5..8fe672200 100644 --- a/src/Idle.cxx +++ b/src/Idle.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 @@ -25,6 +25,7 @@ #include "config.h" #include "Idle.hxx" #include "GlobalEvents.hxx" +#include "util/ASCII.hxx" #include <atomic> @@ -44,6 +45,8 @@ static const char *const idle_names[] = { "update", "subscription", "message", + "neighbor", + "mount", nullptr }; @@ -69,3 +72,15 @@ idle_get_names(void) { return idle_names; } + +unsigned +idle_parse_name(const char *name) +{ + assert(name != nullptr); + + for (unsigned i = 0; idle_names[i] != nullptr; ++i) + if (StringEqualsCaseASCII(name, idle_names[i])) + return 1 << i; + + return 0; +} diff --git a/src/Idle.hxx b/src/Idle.hxx index e5a39f403..fb7150f98 100644 --- a/src/Idle.hxx +++ b/src/Idle.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 @@ -25,6 +25,8 @@ #ifndef MPD_IDLE_HXX #define MPD_IDLE_HXX +#include "Compiler.h" + /** song database has been updated*/ static constexpr unsigned IDLE_DATABASE = 0x1; @@ -59,6 +61,12 @@ static constexpr unsigned IDLE_SUBSCRIPTION = 0x200; /** a message on the subscribed channel was received */ static constexpr unsigned IDLE_MESSAGE = 0x400; +/** a neighbor was found or lost */ +static constexpr unsigned IDLE_NEIGHBOR = 0x800; + +/** the mount list has changed */ +static constexpr unsigned IDLE_MOUNT = 0x1000; + /** * Adds idle flag (with bitwise "or") and queues notifications to all * clients. @@ -78,4 +86,12 @@ idle_get(void); const char*const* idle_get_names(void); +/** + * Parse an idle name and return its mask. Returns 0 if the given + * name is unknown. + */ +gcc_nonnull_all gcc_pure +unsigned +idle_parse_name(const char *name); + #endif diff --git a/src/InotifyDomain.cxx b/src/InotifyDomain.cxx deleted file mode 100644 index 1b8e62d38..000000000 --- a/src/InotifyDomain.cxx +++ /dev/null @@ -1,23 +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 "InotifyDomain.hxx" -#include "util/Domain.hxx" - -const Domain inotify_domain("inotify"); diff --git a/src/InotifyDomain.hxx b/src/InotifyDomain.hxx deleted file mode 100644 index 005487804..000000000 --- a/src/InotifyDomain.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_DOMAIN_HXX -#define MPD_INOTIFY_DOMAIN_HXX - -extern const class Domain inotify_domain; - -#endif diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx deleted file mode 100644 index c24816241..000000000 --- a/src/InotifyQueue.cxx +++ /dev/null @@ -1,90 +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 "InotifyQueue.hxx" -#include "InotifyDomain.hxx" -#include "UpdateGlue.hxx" -#include "event/Loop.hxx" -#include "Log.hxx" - -#include <string.h> - -/** - * Wait this long after the last change before calling - * update_enqueue(). This increases the probability that updates can - * be bundled. - */ -static constexpr unsigned INOTIFY_UPDATE_DELAY_S = 5; - -void -InotifyQueue::OnTimeout() -{ - unsigned id; - - while (!queue.empty()) { - const char *uri_utf8 = queue.front().c_str(); - - id = update_enqueue(uri_utf8, false); - if (id == 0) { - /* retry later */ - ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); - return; - } - - FormatDebug(inotify_domain, "updating '%s' job=%u", - uri_utf8, id); - - queue.pop_front(); - } -} - -static bool -path_in(const char *path, const char *possible_parent) -{ - size_t length = strlen(possible_parent); - - return path[0] == 0 || - (memcmp(possible_parent, path, length) == 0 && - (path[length] == 0 || path[length] == '/')); -} - -void -InotifyQueue::Enqueue(const char *uri_utf8) -{ - ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); - - for (auto i = queue.begin(), end = queue.end(); i != end;) { - const char *current_uri = i->c_str(); - - if (path_in(uri_utf8, current_uri)) - /* already enqueued */ - return; - - if (path_in(current_uri, uri_utf8)) - /* existing path is a sub-path of the new - path; we can dequeue the existing path and - update the new path instead */ - i = queue.erase(i); - else - ++i; - } - - queue.emplace_back(uri_utf8); -} diff --git a/src/InotifyQueue.hxx b/src/InotifyQueue.hxx deleted file mode 100644 index ce79748a0..000000000 --- a/src/InotifyQueue.hxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_QUEUE_HXX -#define MPD_INOTIFY_QUEUE_HXX - -#include "event/TimeoutMonitor.hxx" -#include "Compiler.h" - -#include <list> -#include <string> - -class InotifyQueue final : private TimeoutMonitor { - std::list<std::string> queue; - -public: - InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {} - - void Enqueue(const char *uri_utf8); - -private: - virtual void OnTimeout() override; -}; - -#endif diff --git a/src/InotifySource.cxx b/src/InotifySource.cxx deleted file mode 100644 index ccf5d3e14..000000000 --- a/src/InotifySource.cxx +++ /dev/null @@ -1,114 +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 "InotifySource.hxx" -#include "InotifyDomain.hxx" -#include "util/Error.hxx" -#include "system/fd_util.h" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <sys/inotify.h> -#include <unistd.h> -#include <errno.h> - -bool -InotifySource::OnSocketReady(gcc_unused unsigned flags) -{ - const auto dest = buffer.Write(); - if (dest.IsEmpty()) - FatalError("buffer full"); - - ssize_t nbytes = read(Get(), dest.data, dest.size); - if (nbytes < 0) - FatalSystemError("Failed to read from inotify"); - if (nbytes == 0) - FatalError("end of file from inotify"); - - buffer.Append(nbytes); - - while (true) { - const char *name; - - auto range = buffer.Read(); - const struct inotify_event *event = - (const struct inotify_event *) - range.data; - if (range.size < sizeof(*event) || - range.size < sizeof(*event) + event->len) - break; - - if (event->len > 0 && event->name[event->len - 1] == 0) - name = event->name; - else - name = nullptr; - - callback(event->wd, event->mask, name, callback_ctx); - buffer.Consume(sizeof(*event) + event->len); - } - - return true; -} - -inline -InotifySource::InotifySource(EventLoop &_loop, - mpd_inotify_callback_t _callback, void *_ctx, - int _fd) - :SocketMonitor(_fd, _loop), - callback(_callback), callback_ctx(_ctx) -{ - ScheduleRead(); - -} - -InotifySource * -InotifySource::Create(EventLoop &loop, - mpd_inotify_callback_t callback, void *callback_ctx, - Error &error) -{ - int fd = inotify_init_cloexec(); - if (fd < 0) { - error.SetErrno("inotify_init() has failed"); - return nullptr; - } - - return new InotifySource(loop, callback, callback_ctx, fd); -} - -int -InotifySource::Add(const char *path_fs, unsigned mask, Error &error) -{ - int wd = inotify_add_watch(Get(), path_fs, mask); - if (wd < 0) - error.SetErrno("inotify_add_watch() has failed"); - - return wd; -} - -void -InotifySource::Remove(unsigned wd) -{ - int ret = inotify_rm_watch(Get(), wd); - if (ret < 0 && errno != EINVAL) - LogErrno(inotify_domain, "inotify_rm_watch() has failed"); - - /* EINVAL may happen here when the file has been deleted; the - kernel seems to auto-unregister deleted files */ -} diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx deleted file mode 100644 index f6ddea966..000000000 --- a/src/InotifySource.hxx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_SOURCE_HXX -#define MPD_INOTIFY_SOURCE_HXX - -#include "event/SocketMonitor.hxx" -#include "util/FifoBuffer.hxx" -#include "Compiler.h" - -class Error; - -typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, - const char *name, void *ctx); - -class InotifySource final : private SocketMonitor { - mpd_inotify_callback_t callback; - void *callback_ctx; - - FifoBuffer<uint8_t, 4096> buffer; - - InotifySource(EventLoop &_loop, - mpd_inotify_callback_t callback, void *ctx, int fd); - -public: - /** - * Creates a new inotify source and registers it in the GLib main - * loop. - * - * @param a callback invoked for events received from the kernel - */ - static InotifySource *Create(EventLoop &_loop, - mpd_inotify_callback_t callback, - void *ctx, - Error &error); - - /** - * Adds a path to the notify list. - * - * @return a watch descriptor or -1 on error - */ - int Add(const char *path_fs, unsigned mask, Error &error); - - /** - * Removes a path from the notify list. - * - * @param wd the watch descriptor returned by mpd_inotify_source_add() - */ - void Remove(unsigned wd); - -private: - virtual bool OnSocketReady(unsigned flags) override; -}; - -#endif diff --git a/src/InotifyUpdate.cxx b/src/InotifyUpdate.cxx deleted file mode 100644 index 5a015508a..000000000 --- a/src/InotifyUpdate.cxx +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "InotifyUpdate.hxx" -#include "InotifySource.hxx" -#include "InotifyQueue.hxx" -#include "InotifyDomain.hxx" -#include "Mapper.hxx" -#include "Main.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <string> -#include <map> -#include <forward_list> - -#include <assert.h> -#include <sys/inotify.h> -#include <sys/stat.h> -#include <string.h> -#include <dirent.h> - -static constexpr unsigned IN_MASK = -#ifdef IN_ONLYDIR - IN_ONLYDIR| -#endif - IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF - |IN_MOVE|IN_MOVE_SELF; - -struct WatchDirectory { - WatchDirectory *parent; - - AllocatedPath name; - - int descriptor; - - std::forward_list<WatchDirectory> children; - - template<typename N> - WatchDirectory(WatchDirectory *_parent, N &&_name, - int _descriptor) - :parent(_parent), name(std::forward<N>(_name)), - descriptor(_descriptor) {} - - WatchDirectory(const WatchDirectory &) = delete; - WatchDirectory &operator=(const WatchDirectory &) = delete; -}; - -static InotifySource *inotify_source; -static InotifyQueue *inotify_queue; - -static unsigned inotify_max_depth; -static WatchDirectory *inotify_root; -static std::map<int, WatchDirectory *> inotify_directories; - -static void -tree_add_watch_directory(WatchDirectory *directory) -{ - inotify_directories.insert(std::make_pair(directory->descriptor, - directory)); -} - -static void -tree_remove_watch_directory(WatchDirectory *directory) -{ - auto i = inotify_directories.find(directory->descriptor); - assert(i != inotify_directories.end()); - inotify_directories.erase(i); -} - -static WatchDirectory * -tree_find_watch_directory(int wd) -{ - auto i = inotify_directories.find(wd); - if (i == inotify_directories.end()) - return nullptr; - - return i->second; -} - -static void -disable_watch_directory(WatchDirectory &directory) -{ - tree_remove_watch_directory(&directory); - - for (WatchDirectory &child : directory.children) - disable_watch_directory(child); - - inotify_source->Remove(directory.descriptor); -} - -static void -remove_watch_directory(WatchDirectory *directory) -{ - assert(directory != nullptr); - - if (directory->parent == nullptr) { - LogWarning(inotify_domain, - "music directory was removed - " - "cannot continue to watch it"); - return; - } - - disable_watch_directory(*directory); - - /* remove it from the parent, which effectively deletes it */ - directory->parent->children.remove_if([directory](const WatchDirectory &child){ - return &child == directory; - }); -} - -static AllocatedPath -watch_directory_get_uri_fs(const WatchDirectory *directory) -{ - if (directory->parent == nullptr) - return AllocatedPath::Null(); - - const auto uri = watch_directory_get_uri_fs(directory->parent); - if (uri.IsNull()) - return directory->name; - - return AllocatedPath::Build(uri, directory->name); -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != nullptr; -} - -static void -recursive_watch_subdirectories(WatchDirectory *directory, - const AllocatedPath &path_fs, unsigned depth) -{ - Error error; - DIR *dir; - struct dirent *ent; - - assert(directory != nullptr); - assert(depth <= inotify_max_depth); - assert(!path_fs.IsNull()); - - ++depth; - - if (depth > inotify_max_depth) - return; - - dir = opendir(path_fs.c_str()); - if (dir == nullptr) { - FormatErrno(inotify_domain, - "Failed to open directory %s", path_fs.c_str()); - return; - } - - while ((ent = readdir(dir))) { - struct stat st; - int ret; - - if (skip_path(ent->d_name)) - continue; - - const auto child_path_fs = - AllocatedPath::Build(path_fs, ent->d_name); - ret = StatFile(child_path_fs, st); - if (ret < 0) { - FormatErrno(inotify_domain, - "Failed to stat %s", - child_path_fs.c_str()); - continue; - } - - if (!S_ISDIR(st.st_mode)) - continue; - - ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK, - error); - if (ret < 0) { - FormatError(error, - "Failed to register %s", - child_path_fs.c_str()); - error.Clear(); - continue; - } - - WatchDirectory *child = tree_find_watch_directory(ret); - if (child != nullptr) - /* already being watched */ - continue; - - directory->children.emplace_front(directory, - AllocatedPath::FromFS(ent->d_name), - ret); - child = &directory->children.front(); - - tree_add_watch_directory(child); - - recursive_watch_subdirectories(child, child_path_fs, depth); - } - - closedir(dir); -} - -gcc_pure -static unsigned -watch_directory_depth(const WatchDirectory *d) -{ - assert(d != nullptr); - - unsigned depth = 0; - while ((d = d->parent) != nullptr) - ++depth; - - return depth; -} - -static void -mpd_inotify_callback(int wd, unsigned mask, - gcc_unused const char *name, gcc_unused void *ctx) -{ - WatchDirectory *directory; - - /*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/ - - directory = tree_find_watch_directory(wd); - if (directory == nullptr) - return; - - const auto uri_fs = watch_directory_get_uri_fs(directory); - - if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { - remove_watch_directory(directory); - return; - } - - if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 && - (mask & IN_ISDIR) != 0) { - /* a sub directory was changed: register those in - inotify */ - const auto &root = mapper_get_music_directory_fs(); - - const auto path_fs = uri_fs.IsNull() - ? root - : AllocatedPath::Build(root, uri_fs.c_str()); - - recursive_watch_subdirectories(directory, path_fs, - watch_directory_depth(directory)); - } - - if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || - /* at the maximum depth, we watch out for newly created - directories */ - (watch_directory_depth(directory) == inotify_max_depth && - (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { - /* a file was changed, or a directory was - moved/deleted: queue a database update */ - - if (!uri_fs.IsNull()) { - const std::string uri_utf8 = uri_fs.ToUTF8(); - if (!uri_utf8.empty()) - inotify_queue->Enqueue(uri_utf8.c_str()); - } - else - inotify_queue->Enqueue(""); - } -} - -void -mpd_inotify_init(unsigned max_depth) -{ - LogDebug(inotify_domain, "initializing inotify"); - - const auto &path = mapper_get_music_directory_fs(); - if (path.IsNull()) { - LogDebug(inotify_domain, "no music directory configured"); - return; - } - - Error error; - inotify_source = InotifySource::Create(*main_loop, - mpd_inotify_callback, nullptr, - error); - if (inotify_source == nullptr) { - LogError(error); - return; - } - - inotify_max_depth = max_depth; - - int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error); - if (descriptor < 0) { - LogError(error); - delete inotify_source; - inotify_source = nullptr; - return; - } - - inotify_root = new WatchDirectory(nullptr, path, descriptor); - - tree_add_watch_directory(inotify_root); - - recursive_watch_subdirectories(inotify_root, path, 0); - - inotify_queue = new InotifyQueue(*main_loop); - - LogDebug(inotify_domain, "watching music directory"); -} - -void -mpd_inotify_finish(void) -{ - if (inotify_source == nullptr) - return; - - delete inotify_queue; - delete inotify_source; - delete inotify_root; - inotify_directories.clear(); -} diff --git a/src/InotifyUpdate.hxx b/src/InotifyUpdate.hxx deleted file mode 100644 index 44d0b10b7..000000000 --- a/src/InotifyUpdate.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INOTIFY_UPDATE_HXX -#define MPD_INOTIFY_UPDATE_HXX - -#include "check.h" - -#ifdef HAVE_INOTIFY_INIT - -void -mpd_inotify_init(unsigned max_depth); - -void -mpd_inotify_finish(void); - -#else /* !HAVE_INOTIFY_INIT */ - -static inline void -mpd_inotify_init(gcc_unused unsigned max_depth) -{ -} - -static inline void -mpd_inotify_finish(void) -{ -} - -#endif /* !HAVE_INOTIFY_INIT */ - -#endif diff --git a/src/InputInit.cxx b/src/InputInit.cxx deleted file mode 100644 index 3af1e1686..000000000 --- a/src/InputInit.cxx +++ /dev/null @@ -1,102 +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 "InputInit.hxx" -#include "InputRegistry.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "ConfigData.hxx" - -#include <assert.h> -#include <string.h> - -extern constexpr Domain input_domain("input"); - -/** - * Find the "input" configuration block for the specified plugin. - * - * @param plugin_name the name of the input plugin - * @return the configuration block, or nullptr if none was configured - */ -static const struct config_param * -input_plugin_config(const char *plugin_name, Error &error) -{ - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_INPUT, param)) != nullptr) { - const char *name = param->GetBlockValue("plugin"); - if (name == nullptr) { - error.Format(input_domain, - "input configuration without 'plugin' name in line %d", - param->line); - return nullptr; - } - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return nullptr; -} - -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 = - input_plugin_config(plugin->name, error); - if (param == nullptr) { - if (error.IsDefined()) - return false; - - 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/InputInit.hxx b/src/InputInit.hxx deleted file mode 100644 index 6afea1351..000000000 --- a/src/InputInit.hxx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_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/InputPlugin.hxx b/src/InputPlugin.hxx deleted file mode 100644 index 00226d195..000000000 --- a/src/InputPlugin.hxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_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/InputRegistry.cxx b/src/InputRegistry.cxx deleted file mode 100644 index aa6c06ed1..000000000 --- a/src/InputRegistry.cxx +++ /dev/null @@ -1,72 +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 "InputRegistry.hxx" -#include "util/Macros.hxx" -#include "input/FileInputPlugin.hxx" - -#ifdef ENABLE_ARCHIVE -#include "input/ArchiveInputPlugin.hxx" -#endif - -#ifdef ENABLE_CURL -#include "input/CurlInputPlugin.hxx" -#endif - -#ifdef HAVE_FFMPEG -#include "input/FfmpegInputPlugin.hxx" -#endif - -#ifdef ENABLE_MMS -#include "input/MmsInputPlugin.hxx" -#endif - -#ifdef ENABLE_CDIO_PARANOIA -#include "input/CdioParanoiaInputPlugin.hxx" -#endif - -#ifdef ENABLE_DESPOTIFY -#include "input/DespotifyInputPlugin.hxx" -#endif - -const InputPlugin *const input_plugins[] = { - &input_plugin_file, -#ifdef ENABLE_ARCHIVE - &input_plugin_archive, -#endif -#ifdef ENABLE_CURL - &input_plugin_curl, -#endif -#ifdef HAVE_FFMPEG - &input_plugin_ffmpeg, -#endif -#ifdef ENABLE_MMS - &input_plugin_mms, -#endif -#ifdef ENABLE_CDIO_PARANOIA - &input_plugin_cdio_paranoia, -#endif -#ifdef ENABLE_DESPOTIFY - &input_plugin_despotify, -#endif - nullptr -}; - -bool input_plugins_enabled[ARRAY_SIZE(input_plugins) - 1]; diff --git a/src/InputRegistry.hxx b/src/InputRegistry.hxx deleted file mode 100644 index 8bc927a7a..000000000 --- a/src/InputRegistry.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_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/InputStream.cxx b/src/InputStream.cxx deleted file mode 100644 index 28a0aad1a..000000000 --- a/src/InputStream.cxx +++ /dev/null @@ -1,192 +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 "InputStream.hxx" -#include "InputRegistry.hxx" -#include "InputPlugin.hxx" -#include "input/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; -} - -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/InputStream.hxx b/src/InputStream.hxx deleted file mode 100644 index b1bc9c4ab..000000000 --- a/src/InputStream.hxx +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_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); - - /** - * 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/Instance.cxx b/src/Instance.cxx index daad94212..232cd21df 100644 --- a/src/Instance.cxx +++ b/src/Instance.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 @@ -21,28 +21,82 @@ #include "Instance.hxx" #include "Partition.hxx" #include "Idle.hxx" +#include "Stats.hxx" + +#ifdef ENABLE_DATABASE +#include "db/DatabaseError.hxx" +#include "db/LightSong.hxx" + +#ifdef ENABLE_SQLITE +#include "sticker/StickerDatabase.hxx" +#include "sticker/SongSticker.hxx" +#endif + +Database * +Instance::GetDatabase(Error &error) +{ + if (database == nullptr) + error.Set(db_domain, DB_DISABLED, "No database"); + return database; +} + +#endif void -Instance::DeleteSong(const Song &song) +Instance::TagModified() { - partition->DeleteSong(song); + partition->TagModified(); } void -Instance::DatabaseModified() +Instance::SyncWithPlayer() +{ + partition->SyncWithPlayer(); +} + +#ifdef ENABLE_DATABASE + +void +Instance::OnDatabaseModified() { - partition->DatabaseModified(); + assert(database != nullptr); + + /* propagate the change to all subsystems */ + + stats_invalidate(); + partition->DatabaseModified(*database); idle_add(IDLE_DATABASE); } void -Instance::TagModified() +Instance::OnDatabaseSongRemoved(const LightSong &song) { - partition->TagModified(); + assert(database != nullptr); + +#ifdef ENABLE_SQLITE + /* if the song has a sticker, remove it */ + if (sticker_enabled()) + sticker_song_delete(song); +#endif + + const auto uri = song.GetURI(); + partition->DeleteSong(uri.c_str()); } +#endif + +#ifdef ENABLE_NEIGHBOR_PLUGINS + void -Instance::SyncWithPlayer() +Instance::FoundNeighbor(gcc_unused const NeighborInfo &info) { - partition->SyncWithPlayer(); + idle_add(IDLE_NEIGHBOR); } + +void +Instance::LostNeighbor(gcc_unused const NeighborInfo &info) +{ + idle_add(IDLE_NEIGHBOR); +} + +#endif diff --git a/src/Instance.hxx b/src/Instance.hxx index a0dfd1b94..fa7711ab9 100644 --- a/src/Instance.hxx +++ b/src/Instance.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 @@ -21,23 +21,76 @@ #define MPD_INSTANCE_HXX #include "check.h" +#include "Compiler.h" +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Listener.hxx" +class NeighborGlue; +#endif + +#ifdef ENABLE_DATABASE +#include "db/DatabaseListener.hxx" +class Database; +class Storage; +class UpdateService; +#endif + +class EventLoop; +class Error; class ClientList; struct Partition; -struct Song; -struct Instance { +struct Instance final +#if defined(ENABLE_DATABASE) || defined(ENABLE_NEIGHBOR_PLUGINS) + : +#endif +#ifdef ENABLE_DATABASE + public DatabaseListener +#ifdef ENABLE_NEIGHBOR_PLUGINS + , +#endif +#endif +#ifdef ENABLE_NEIGHBOR_PLUGINS + public NeighborListener +#endif +{ + EventLoop *event_loop; + +#ifdef ENABLE_NEIGHBOR_PLUGINS + NeighborGlue *neighbors; +#endif + +#ifdef ENABLE_DATABASE + Database *database; + + /** + * This is really a #CompositeStorage. To avoid heavy include + * dependencies, we declare it as just #Storage. + */ + Storage *storage; + + UpdateService *update; +#endif + ClientList *client_list; Partition *partition; - void DeleteSong(const Song &song); + Instance() { +#ifdef ENABLE_DATABASE + storage = nullptr; + update = nullptr; +#endif + } +#ifdef ENABLE_DATABASE /** - * The database has been modified. Propagate the change to - * all subsystems. + * Returns the global #Database instance. May return nullptr + * if this MPD configuration has no database (no + * music_directory was configured). */ - void DatabaseModified(); + Database *GetDatabase(Error &error); +#endif /** * A tag in the play queue has been modified by the player @@ -49,6 +102,18 @@ struct Instance { * Synchronize the player with the play queue. */ void SyncWithPlayer(); + +private: +#ifdef ENABLE_DATABASE + virtual void OnDatabaseModified() override; + virtual void OnDatabaseSongRemoved(const LightSong &song) override; +#endif + +#ifdef ENABLE_NEIGHBOR_PLUGINS + /* virtual methods from class NeighborListener */ + virtual void FoundNeighbor(const NeighborInfo &info) override; + virtual void LostNeighbor(const NeighborInfo &info) override; +#endif }; #endif diff --git a/src/Listen.cxx b/src/Listen.cxx index d5c132545..d48d795d1 100644 --- a/src/Listen.cxx +++ b/src/Listen.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,12 +19,10 @@ #include "config.h" #include "Listen.hxx" -#include "Main.hxx" -#include "Instance.hxx" -#include "Client.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "client/Client.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "event/ServerSocket.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -43,13 +41,16 @@ static constexpr Domain listen_domain("listen"); #define DEFAULT_PORT 6600 class ClientListener final : public ServerSocket { + Partition &partition; + public: - ClientListener():ServerSocket(*main_loop) {} + ClientListener(EventLoop &_loop, Partition &_partition) + :ServerSocket(_loop), partition(_partition) {} private: virtual void OnAccept(int fd, const sockaddr &address, size_t address_length, int uid) { - client_new(*main_loop, *instance->partition, + client_new(GetEventLoop(), partition, fd, &address, address_length, uid); } }; @@ -76,10 +77,11 @@ listen_add_config_param(unsigned int port, } } +#ifdef ENABLE_SYSTEMD_DAEMON + static bool listen_systemd_activation(Error &error_r) { -#ifdef ENABLE_SYSTEMD_DAEMON int n = sd_listen_fds(true); if (n <= 0) { if (n < 0) @@ -94,29 +96,26 @@ listen_systemd_activation(Error &error_r) return false; return true; -#else - (void)error_r; - return false; -#endif } +#endif + bool -listen_global_init(Error &error) +listen_global_init(EventLoop &loop, Partition &partition, Error &error) { - assert(main_loop != nullptr); - int port = config_get_positive(CONF_PORT, DEFAULT_PORT); const struct config_param *param = - config_get_next_param(CONF_BIND_TO_ADDRESS, nullptr); - bool success; + config_get_param(CONF_BIND_TO_ADDRESS); - listen_socket = new ClientListener(); + listen_socket = new ClientListener(loop, partition); +#ifdef ENABLE_SYSTEMD_DAEMON if (listen_systemd_activation(error)) return true; if (error.IsDefined()) return false; +#endif if (param != nullptr) { /* "bind_to_address" is configured, create listeners @@ -130,16 +129,12 @@ listen_global_init(Error &error) param->line); return false; } - - param = config_get_next_param(CONF_BIND_TO_ADDRESS, - param); - } while (param != nullptr); + } while ((param = param->next) != nullptr); } else { /* no "bind_to_address" configured, bind the configured port on all interfaces */ - success = listen_socket->AddPort(port, error); - if (!success) { + if (!listen_socket->AddPort(port, error)) { delete listen_socket; error.FormatPrefix("Failed to listen on *:%d: ", port); return false; diff --git a/src/Listen.hxx b/src/Listen.hxx index a6fdb2f1c..d74c1d233 100644 --- a/src/Listen.hxx +++ b/src/Listen.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 @@ -20,12 +20,14 @@ #ifndef MPD_LISTEN_HXX #define MPD_LISTEN_HXX +class EventLoop; class Error; +struct Partition; extern int listen_port; bool -listen_global_init(Error &error); +listen_global_init(EventLoop &loop, Partition &partition, Error &error); void listen_global_finish(void); diff --git a/src/Log.cxx b/src/Log.cxx index a46c0ced8..ba691581b 100644 --- a/src/Log.cxx +++ b/src/Log.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,62 +19,19 @@ #include "config.h" #include "LogV.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "system/fd_util.h" -#include "system/FatalError.hxx" -#include "fs/Path.hxx" -#include "fs/FileSystem.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/FatalError.hxx" - -#include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <string.h> -#include <fcntl.h> #include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include <unistd.h> +#include <string.h> #include <errno.h> -static GLogLevelFlags -ToGLib(LogLevel level) -{ - switch (level) { - case LogLevel::DEBUG: - return G_LOG_LEVEL_DEBUG; - - case LogLevel::INFO: - return G_LOG_LEVEL_INFO; - - case LogLevel::DEFAULT: - return G_LOG_LEVEL_MESSAGE; - - case LogLevel::WARNING: - case LogLevel::ERROR: - return G_LOG_LEVEL_WARNING; - } - - assert(false); - gcc_unreachable(); -} - -void -Log(const Domain &domain, LogLevel level, const char *msg) -{ - g_log(domain.GetName(), ToGLib(level), "%s", msg); -} - void LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap) { - g_logv(domain.GetName(), ToGLib(level), fmt, ap); + char msg[1024]; + vsnprintf(msg, sizeof(msg), fmt, ap); + Log(domain, level, msg); } void @@ -159,7 +116,7 @@ FormatError(const Error &error, const char *fmt, ...) void LogErrno(const Domain &domain, int e, const char *msg) { - LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, g_strerror(e)); + LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e)); } void diff --git a/src/Log.hxx b/src/Log.hxx index 920a8d8a3..15077e374 100644 --- a/src/Log.hxx +++ b/src/Log.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 @@ -20,47 +20,12 @@ #ifndef MPD_LOG_HXX #define MPD_LOG_HXX +#include "LogLevel.hxx" #include "Compiler.h" -#ifdef WIN32 -#include <windows.h> -/* damn you, windows.h! */ -#ifdef ERROR -#undef ERROR -#endif -#endif - class Error; class Domain; -enum class LogLevel { - /** - * Debug message for developers. - */ - DEBUG, - - /** - * Unimportant informational message. - */ - INFO, - - /** - * Interesting informational message. - */ - DEFAULT, - - /** - * Warning: something may be wrong. - */ - WARNING, - - /** - * An error has occurred, an operation could not finish - * successfully. - */ - ERROR, -}; - void Log(const Domain &domain, LogLevel level, const char *msg); diff --git a/src/LogBackend.cxx b/src/LogBackend.cxx new file mode 100644 index 000000000..6591fef2d --- /dev/null +++ b/src/LogBackend.cxx @@ -0,0 +1,224 @@ +/* + * 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 "LogBackend.hxx" +#include "Log.hxx" +#include "util/Domain.hxx" +#include "util/StringUtil.hxx" + +#ifdef HAVE_GLIB +#include <glib.h> +#endif + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +#ifdef ANDROID +#include <android/log.h> + +static int +ToAndroidLogLevel(LogLevel log_level) +{ + switch (log_level) { + case LogLevel::DEBUG: + return ANDROID_LOG_DEBUG; + + case LogLevel::INFO: + case LogLevel::DEFAULT: + return ANDROID_LOG_INFO; + + case LogLevel::WARNING: + return ANDROID_LOG_WARN; + + case LogLevel::ERROR: + return ANDROID_LOG_ERROR; + } + + assert(false); + gcc_unreachable(); +} + +#else + +static LogLevel log_threshold = LogLevel::INFO; + +#ifdef HAVE_GLIB +static const char *log_charset; +#endif + +static bool enable_timestamp; + +#ifdef HAVE_SYSLOG +static bool enable_syslog; +#endif + +void +SetLogThreshold(LogLevel _threshold) +{ + log_threshold = _threshold; +} + +#ifdef HAVE_GLIB + +void +SetLogCharset(const char *_charset) +{ + log_charset = _charset; +} + +#endif + +void +EnableLogTimestamp() +{ +#ifdef HAVE_SYSLOG + assert(!enable_syslog); +#endif + assert(!enable_timestamp); + + enable_timestamp = true; +} + +static const char *log_date(void) +{ + static constexpr size_t LOG_DATE_BUF_SIZE = 16; + static char buf[LOG_DATE_BUF_SIZE]; + time_t t = time(nullptr); + strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); + return buf; +} + +/** + * Determines the length of the string excluding trailing whitespace + * characters. + */ +static int +chomp_length(const char *p) +{ + size_t length = strlen(p); + return StripRight(p, length); +} + +#ifdef HAVE_SYSLOG + +static int +ToSysLogLevel(LogLevel log_level) +{ + switch (log_level) { + case LogLevel::DEBUG: + return LOG_DEBUG; + + case LogLevel::INFO: + return LOG_INFO; + + case LogLevel::DEFAULT: + return LOG_NOTICE; + + case LogLevel::WARNING: + return LOG_WARNING; + + case LogLevel::ERROR: + return LOG_ERR; + } + + assert(false); + gcc_unreachable(); +} + +static void +SysLog(const Domain &domain, LogLevel log_level, const char *message) +{ + syslog(ToSysLogLevel(log_level), "%s: %.*s", + domain.GetName(), + chomp_length(message), message); +} + +void +LogInitSysLog() +{ + openlog(PACKAGE, 0, LOG_DAEMON); + enable_syslog = true; +} + +void +LogFinishSysLog() +{ + if (enable_syslog) + closelog(); +} + +#endif + +static void +FileLog(const Domain &domain, const char *message) +{ +#ifdef HAVE_GLIB + char *converted; + + if (log_charset != nullptr) { + converted = g_convert_with_fallback(message, -1, + log_charset, "utf-8", + nullptr, nullptr, + nullptr, nullptr); + if (converted != nullptr) + message = converted; + } else + converted = nullptr; +#endif + + fprintf(stderr, "%s%s: %.*s\n", + enable_timestamp ? log_date() : "", + domain.GetName(), + chomp_length(message), message); + +#ifdef HAVE_GLIB + g_free(converted); +#endif +} + +#endif /* !ANDROID */ + +void +Log(const Domain &domain, LogLevel level, const char *msg) +{ +#ifdef ANDROID + __android_log_print(ToAndroidLogLevel(level), "MPD", + "%s: %s", domain.GetName(), msg); +#else + + if (level < log_threshold) + return; + +#ifdef HAVE_SYSLOG + if (enable_syslog) { + SysLog(domain, level, msg); + return; + } +#endif + + FileLog(domain, msg); +#endif /* !ANDROID */ +} diff --git a/src/LogBackend.hxx b/src/LogBackend.hxx new file mode 100644 index 000000000..23df2037e --- /dev/null +++ b/src/LogBackend.hxx @@ -0,0 +1,45 @@ +/* + * 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_LOG_BACKEND_HXX +#define MPD_LOG_BACKEND_HXX + +#include "check.h" +#include "LogLevel.hxx" + +void +SetLogThreshold(LogLevel _threshold); + +#ifdef HAVE_GLIB + +void +SetLogCharset(const char *_charset); + +#endif + +void +EnableLogTimestamp(); + +void +LogInitSysLog(); + +void +LogFinishSysLog(); + +#endif /* LOG_H */ diff --git a/src/LogInit.cxx b/src/LogInit.cxx index 41d13a5e8..accd1d4d8 100644 --- a/src/LogInit.cxx +++ b/src/LogInit.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,48 +19,38 @@ #include "config.h" #include "LogInit.hxx" +#include "LogBackend.hxx" #include "Log.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "system/fd_util.h" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "system/FatalError.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include "util/CharUtil.hxx" #include "system/FatalError.hxx" +#ifdef HAVE_GLIB +#include <glib.h> +#endif + #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> #include <string.h> -#include <stdarg.h> #include <fcntl.h> #include <stdio.h> -#include <stdlib.h> #include <time.h> #include <unistd.h> -#include <errno.h> -#include <glib.h> - -#ifdef HAVE_SYSLOG -#include <syslog.h> -#endif -#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO +#define LOG_LEVEL_SECURE LogLevel::INFO #define LOG_DATE_BUF_SIZE 16 #define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) static constexpr Domain log_domain("log"); -static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE; - -static const char *log_charset; +#ifndef ANDROID -static bool stdout_mode = true; static int out_fd; static AllocatedPath out_path = AllocatedPath::Null(); @@ -73,66 +63,6 @@ static void redirect_logs(int fd) FatalSystemError("Failed to dup2 stderr"); } -static const char *log_date(void) -{ - static char buf[LOG_DATE_BUF_SIZE]; - time_t t = time(nullptr); - strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); - return buf; -} - -/** - * Determines the length of the string excluding trailing whitespace - * characters. - */ -static int -chomp_length(const char *p) -{ - size_t length = strlen(p); - - while (length > 0 && IsWhitespaceOrNull(p[length - 1])) - --length; - - return (int)length; -} - -static void -file_log_func(const gchar *domain, - GLogLevelFlags log_level, - const gchar *message, gcc_unused gpointer user_data) -{ - char *converted; - - if (log_level > log_threshold) - return; - - if (log_charset != nullptr) { - converted = g_convert_with_fallback(message, -1, - log_charset, "utf-8", - nullptr, nullptr, - nullptr, nullptr); - if (converted != nullptr) - message = converted; - } else - converted = nullptr; - - if (domain == nullptr) - domain = ""; - - fprintf(stderr, "%s%s%s%.*s\n", - stdout_mode ? "" : log_date(), - domain, *domain == 0 ? "" : ": ", - chomp_length(message), message); - - g_free(converted); -} - -static void -log_init_stdout(void) -{ - g_log_set_default_handler(file_log_func, nullptr); -} - static int open_log_file(void) { @@ -154,112 +84,63 @@ log_init_file(unsigned line, Error &error) return false; } - g_log_set_default_handler(file_log_func, nullptr); + EnableLogTimestamp(); return true; } -#ifdef HAVE_SYSLOG - -static int -glib_to_syslog_level(GLogLevelFlags log_level) -{ - switch (log_level & G_LOG_LEVEL_MASK) { - case G_LOG_LEVEL_ERROR: - case G_LOG_LEVEL_CRITICAL: - return LOG_ERR; - - case G_LOG_LEVEL_WARNING: - return LOG_WARNING; - - case G_LOG_LEVEL_MESSAGE: - return LOG_NOTICE; - - case G_LOG_LEVEL_INFO: - return LOG_INFO; - - case G_LOG_LEVEL_DEBUG: - return LOG_DEBUG; - - default: - return LOG_NOTICE; - } -} - -static void -syslog_log_func(const gchar *domain, - GLogLevelFlags log_level, const gchar *message, - gcc_unused gpointer user_data) -{ - if (stdout_mode) { - /* fall back to the file log function during - startup */ - file_log_func(domain, log_level, - message, user_data); - return; - } - - if (log_level > log_threshold) - return; - - if (domain == nullptr) - domain = ""; - - syslog(glib_to_syslog_level(log_level), "%s%s%.*s", - domain, *domain == 0 ? "" : ": ", - chomp_length(message), message); -} - -static void -log_init_syslog(void) -{ - assert(out_path.IsNull()); - - openlog(PACKAGE, 0, LOG_DAEMON); - g_log_set_default_handler(syslog_log_func, nullptr); -} - -#endif - -static inline GLogLevelFlags +static inline LogLevel parse_log_level(const char *value, unsigned line) { if (0 == strcmp(value, "default")) - return G_LOG_LEVEL_MESSAGE; + return LogLevel::DEFAULT; if (0 == strcmp(value, "secure")) return LOG_LEVEL_SECURE; else if (0 == strcmp(value, "verbose")) - return G_LOG_LEVEL_DEBUG; + return LogLevel::DEBUG; else { FormatFatalError("unknown log level \"%s\" at line %u", value, line); - return G_LOG_LEVEL_MESSAGE; } } +#endif + void log_early_init(bool verbose) { +#ifdef ANDROID + (void)verbose; +#else if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; - - log_init_stdout(); + SetLogThreshold(LogLevel::DEBUG); +#endif } bool log_init(bool verbose, bool use_stdout, Error &error) { +#ifdef ANDROID + (void)verbose; + (void)use_stdout; + (void)error; + + return true; +#else const struct config_param *param; - g_get_charset(&log_charset); +#ifdef HAVE_GLIB + const char *charset; + g_get_charset(&charset); + SetLogCharset(charset); +#endif if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; + SetLogThreshold(LogLevel::DEBUG); else if ((param = config_get_param(CONF_LOG_LEVEL)) != nullptr) - log_threshold = parse_log_level(param->value.c_str(), - param->line); + SetLogThreshold(parse_log_level(param->value.c_str(), + param->line)); if (use_stdout) { - log_init_stdout(); return true; } else { param = config_get_param(CONF_LOG_FILE); @@ -267,7 +148,7 @@ log_init(bool verbose, bool use_stdout, Error &error) #ifdef HAVE_SYSLOG /* no configuration: default to syslog (if available) */ - log_init_syslog(); + LogInitSysLog(); return true; #else error.Set(log_domain, @@ -276,7 +157,7 @@ log_init(bool verbose, bool use_stdout, Error &error) #endif #ifdef HAVE_SYSLOG } else if (strcmp(param->value.c_str(), "syslog") == 0) { - log_init_syslog(); + LogInitSysLog(); return true; #endif } else { @@ -285,56 +166,70 @@ log_init(bool verbose, bool use_stdout, Error &error) log_init_file(param->line, error); } } +#endif } +#ifndef ANDROID + static void close_log_files(void) { - if (stdout_mode) - return; - #ifdef HAVE_SYSLOG - if (out_path.IsNull()) - closelog(); + LogFinishSysLog(); #endif } +#endif + void log_deinit(void) { +#ifndef ANDROID close_log_files(); out_path = AllocatedPath::Null(); +#endif } - void setup_log_output(bool use_stdout) { +#ifdef ANDROID + (void)use_stdout; +#else + if (use_stdout) + return; + fflush(nullptr); - if (!use_stdout) { -#ifndef WIN32 - if (out_path.IsNull()) - out_fd = open("/dev/null", O_WRONLY); + + if (out_fd < 0) { +#ifdef WIN32 + return; +#else + out_fd = open("/dev/null", O_WRONLY); + if (out_fd < 0) + return; #endif + } - if (out_fd >= 0) { - redirect_logs(out_fd); - close(out_fd); - } + redirect_logs(out_fd); + close(out_fd); + out_fd = -1; - stdout_mode = false; - log_charset = nullptr; - } +#ifdef HAVE_GLIB + SetLogCharset(nullptr); +#endif +#endif } int cycle_log_files(void) { +#ifdef ANDROID + return 0; +#else int fd; - if (stdout_mode || out_path.IsNull()) + if (out_path.IsNull()) return 0; - assert(!out_path.IsNull()); - FormatDebug(log_domain, "Cycling log files"); close_log_files(); @@ -348,6 +243,9 @@ int cycle_log_files(void) } redirect_logs(fd); + close(fd); + FormatDebug(log_domain, "Done cycling log files"); return 0; +#endif } diff --git a/src/LogInit.hxx b/src/LogInit.hxx index 2369fcdca..d0bcb40f2 100644 --- a/src/LogInit.hxx +++ b/src/LogInit.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/LogLevel.hxx b/src/LogLevel.hxx new file mode 100644 index 000000000..2614a67d1 --- /dev/null +++ b/src/LogLevel.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LOG_LEVEL_HXX +#define MPD_LOG_LEVEL_HXX + +#ifdef WIN32 +#include <windows.h> +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +enum class LogLevel { + /** + * Debug message for developers. + */ + DEBUG, + + /** + * Unimportant informational message. + */ + INFO, + + /** + * Interesting informational message. + */ + DEFAULT, + + /** + * Warning: something may be wrong. + */ + WARNING, + + /** + * An error has occurred, an operation could not finish + * successfully. + */ + ERROR, +}; + +#endif diff --git a/src/LogV.hxx b/src/LogV.hxx index 4bd4f801d..6b16f82b4 100644 --- a/src/LogV.hxx +++ b/src/LogV.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 @@ -20,7 +20,7 @@ #ifndef MPD_LOGV_HXX #define MPD_LOGV_HXX -#include "Log.hxx" +#include "Log.hxx" // IWYU pragma: export #include <stdarg.h> diff --git a/src/Main.cxx b/src/Main.cxx index b45e2c3ae..2ce81bc3e 100644 --- a/src/Main.cxx +++ b/src/Main.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,67 +23,87 @@ #include "CommandLine.hxx" #include "PlaylistFile.hxx" #include "PlaylistGlobal.hxx" -#include "UpdateGlue.hxx" #include "MusicChunk.hxx" #include "StateFile.hxx" #include "PlayerThread.hxx" #include "Mapper.hxx" -#include "DatabaseGlue.hxx" -#include "DatabaseSimple.hxx" #include "Permission.hxx" #include "Listen.hxx" -#include "Client.hxx" -#include "ClientList.hxx" +#include "client/Client.hxx" +#include "client/ClientList.hxx" #include "command/AllCommands.hxx" #include "Partition.hxx" -#include "Volume.hxx" -#include "OutputAll.hxx" #include "tag/TagConfig.hxx" #include "ReplayGainConfig.hxx" #include "Idle.hxx" -#include "SignalHandlers.hxx" #include "Log.hxx" #include "LogInit.hxx" #include "GlobalEvents.hxx" -#include "InputInit.hxx" +#include "input/Init.hxx" #include "event/Loop.hxx" #include "IOThread.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Config.hxx" -#include "PlaylistRegistry.hxx" -#include "ZeroconfGlue.hxx" -#include "DecoderList.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "zeroconf/ZeroconfGlue.hxx" +#include "decoder/DecoderList.hxx" #include "AudioConfig.hxx" -#include "pcm/PcmResample.hxx" -#include "Daemon.hxx" +#include "pcm/PcmConvert.hxx" +#include "unix/SignalHandlers.hxx" +#include "unix/Daemon.hxx" #include "system/FatalError.hxx" +#include "util/UriUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "thread/Id.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "ConfigDefaults.hxx" -#include "ConfigOption.hxx" +#include "thread/Slack.hxx" +#include "lib/icu/Init.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigDefaults.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigError.hxx" #include "Stats.hxx" +#ifdef ENABLE_DATABASE +#include "db/update/Service.hxx" +#include "db/Configured.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "storage/Configured.hxx" +#include "storage/CompositeStorage.hxx" #ifdef ENABLE_INOTIFY -#include "InotifyUpdate.hxx" +#include "db/update/InotifyUpdate.hxx" +#endif +#endif + +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Glue.hxx" #endif #ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" +#include "sticker/StickerDatabase.hxx" #endif #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" +#include "archive/ArchiveList.hxx" +#endif + +#ifdef ANDROID +#include "java/Global.hxx" +#include "java/File.hxx" +#include "android/Environment.hxx" +#include "android/Context.hxx" +#include "fs/StandardDirectory.hxx" +#include "fs/FileSystem.hxx" +#include "org_musicpd_Bridge.h" #endif +#ifdef HAVE_GLIB #include <glib.h> +#endif -#include <unistd.h> #include <stdlib.h> -#include <errno.h> -#include <string.h> #ifdef HAVE_LOCALE_H #include <locale.h> @@ -94,18 +114,23 @@ #include <ws2tcpip.h> #endif +#include <limits.h> + static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096; static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10; static constexpr Domain main_domain("main"); -ThreadId main_thread; -EventLoop *main_loop; +#ifdef ANDROID +Context *context; +#endif Instance *instance; static StateFile *state_file; +#ifndef ANDROID + static bool glue_daemonize_init(const struct options *options, Error &error) { @@ -123,28 +148,33 @@ glue_daemonize_init(const struct options *options, Error &error) return true; } +#endif + static bool glue_mapper_init(Error &error) { - auto music_dir = config_get_path(CONF_MUSIC_DIR, error); - if (music_dir.IsNull() && error.IsDefined()) - return false; - auto playlist_dir = config_get_path(CONF_PLAYLIST_DIR, error); if (playlist_dir.IsNull() && error.IsDefined()) return false; - if (music_dir.IsNull()) { - const char *path = - g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); - if (path != nullptr) { - music_dir = AllocatedPath::FromUTF8(path, error); - if (music_dir.IsNull()) - return false; - } - } + mapper_init(std::move(playlist_dir)); + return true; +} + +#ifdef ENABLE_DATABASE + +static bool +InitStorage(Error &error) +{ + Storage *storage = CreateConfiguredStorage(error); + if (storage == nullptr) + return !error.IsDefined(); + + assert(!error.IsDefined()); - mapper_init(std::move(music_dir), std::move(playlist_dir)); + CompositeStorage *composite = new CompositeStorage(); + instance->storage = composite; + composite->Mount("", storage); return true; } @@ -156,50 +186,60 @@ glue_mapper_init(Error &error) static bool glue_db_init_and_load(void) { - const struct config_param *param = config_get_param(CONF_DATABASE); - const struct config_param *path = config_get_param(CONF_DB_FILE); + Error error; + instance->database = + CreateConfiguredDatabase(*instance->event_loop, *instance, + error); + if (instance->database == nullptr) { + if (error.IsDefined()) + FatalError(error); + else + return true; + } - if (param != nullptr && path != nullptr) - LogWarning(main_domain, - "Found both 'database' and 'db_file' setting - ignoring the latter"); + if (instance->database->GetPlugin().flags & DatabasePlugin::FLAG_REQUIRE_STORAGE) { + if (!InitStorage(error)) + FatalError(error); - if (!mapper_has_music_directory()) { - if (param != nullptr) - LogDefault(main_domain, + if (instance->storage == nullptr) { + delete instance->database; + instance->database = nullptr; + LogDefault(config_domain, "Found database setting without " "music_directory - disabling database"); - if (path != nullptr) - LogDefault(main_domain, - "Found db_file setting without " - "music_directory - disabling database"); - return true; + return true; + } + } else { + if (IsStorageConfigured()) + LogDefault(config_domain, + "Ignoring the storage configuration " + "because the database does not need it"); } - struct config_param *allocated = nullptr; - - if (param == nullptr && path != nullptr) { - allocated = new config_param("database", path->line); - allocated->AddBlockParam("path", path->value.c_str(), - path->line); - param = allocated; - } + if (!instance->database->Open(error)) + FatalError(error); - if (param == nullptr) + if (!instance->database->IsPlugin(simple_db_plugin)) return true; - Error error; - if (!DatabaseGlobalInit(*param, error)) - FatalError(error); - - delete allocated; - - if (!DatabaseGlobalOpen(error)) - FatalError(error); + SimpleDatabase &db = *(SimpleDatabase *)instance->database; + instance->update = new UpdateService(*instance->event_loop, db, + static_cast<CompositeStorage &>(*instance->storage), + *instance); /* run database update after daemonization? */ - return !db_is_simple() || db_exists(); + return db.FileExists(); } +static bool +InitDatabaseAndStorage() +{ + const bool create_db = !glue_db_init_and_load(); + return create_db; +} + +#endif + /** * Configure and initialize the sticker subsystem. */ @@ -224,11 +264,24 @@ static bool glue_state_file_init(Error &error) { auto path_fs = config_get_path(CONF_STATE_FILE, error); - if (path_fs.IsNull()) - return !error.IsDefined(); + if (path_fs.IsNull()) { + if (error.IsDefined()) + return false; + +#ifdef ANDROID + const auto cache_dir = GetUserCacheDir(); + if (cache_dir.IsNull()) + return true; + + path_fs = AllocatedPath::Build(cache_dir, "state"); +#else + return true; +#endif + } state_file = new StateFile(std::move(path_fs), - *instance->partition, *main_loop); + *instance->partition, + *instance->event_loop); state_file->Read(); return true; } @@ -240,9 +293,8 @@ static void winsock_init(void) { #ifdef WIN32 WSADATA sockinfo; - int retval; - retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); + int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); if(retval != 0) FormatFatalError("Attempt to open Winsock2 failed; error code %d", retval); @@ -260,14 +312,11 @@ static void initialize_decoder_and_player(void) { const struct config_param *param; - char *test; - size_t buffer_size; - float perc; - unsigned buffered_chunks; - unsigned buffered_before_play; + size_t buffer_size; param = config_get_param(CONF_AUDIO_BUFFER_SIZE); if (param != nullptr) { + char *test; long tmp = strtol(param->value.c_str(), &test, 10); if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) FormatFatalError("buffer size \"%s\" is not a " @@ -279,14 +328,16 @@ initialize_decoder_and_player(void) buffer_size *= 1024; - buffered_chunks = buffer_size / CHUNK_SIZE; + const unsigned buffered_chunks = buffer_size / CHUNK_SIZE; if (buffered_chunks >= 1 << 15) FormatFatalError("buffer size \"%lu\" is too big", (unsigned long)buffer_size); + float perc; param = config_get_param(CONF_BUFFER_BEFORE_PLAY); if (param != nullptr) { + char *test; perc = strtod(param->value.c_str(), &test); if (*test != '%' || perc < 0 || perc > 100) { FormatFatalError("buffered before play \"%s\" is not " @@ -297,7 +348,7 @@ initialize_decoder_and_player(void) } else perc = DEFAULT_BUFFER_BEFORE_PLAY; - buffered_before_play = (perc / 100) * buffered_chunks; + unsigned buffered_before_play = (perc / 100) * buffered_chunks; if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; @@ -336,11 +387,13 @@ idle_event_emitted(void) static void shutdown_event_emitted(void) { - main_loop->Break(); + instance->event_loop->Break(); } #endif +#ifndef ANDROID + int main(int argc, char *argv[]) { #ifdef WIN32 @@ -350,14 +403,17 @@ int main(int argc, char *argv[]) #endif } +#endif + +#ifdef ANDROID +static inline +#endif int mpd_main(int argc, char *argv[]) { struct options options; - clock_t start; - bool create_db; Error error; - bool success; +#ifndef ANDROID daemonize_close_stdin(); #ifdef HAVE_LOCALE_H @@ -365,19 +421,43 @@ int mpd_main(int argc, char *argv[]) setlocale(LC_CTYPE,""); #endif +#ifdef HAVE_GLIB g_set_application_name("Music Player Daemon"); #if !GLIB_CHECK_VERSION(2,32,0) /* enable GLib's thread safety code */ g_thread_init(nullptr); #endif +#endif +#endif + + if (!IcuInit(error)) { + LogError(error); + return EXIT_FAILURE; + } - io_thread_init(); winsock_init(); + io_thread_init(); config_global_init(); - success = parse_cmdline(argc, argv, &options, error); - if (!success) { +#ifdef ANDROID + (void)argc; + (void)argv; + + { + const auto sdcard = Environment::getExternalStorageDirectory(); + if (!sdcard.IsNull()) { + const auto config_path = + AllocatedPath::Build(sdcard, "mpd.conf"); + if (FileExists(config_path) && + !ReadConfigFile(config_path, error)) { + LogError(error); + return EXIT_FAILURE; + } + } + } +#else + if (!parse_cmdline(argc, argv, &options, error)) { LogError(error); return EXIT_FAILURE; } @@ -386,6 +466,7 @@ int mpd_main(int argc, char *argv[]) LogError(error); return EXIT_FAILURE; } +#endif stats_global_init(); TagLoadConfig(); @@ -395,23 +476,39 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } - main_thread = ThreadId::GetCurrent(); - main_loop = new EventLoop(EventLoop::Default()); - instance = new Instance(); + instance->event_loop = new EventLoop(); + +#ifdef ENABLE_NEIGHBOR_PLUGINS + instance->neighbors = new NeighborGlue(); + if (!instance->neighbors->Init(io_thread_get(), *instance, error)) { + LogError(error); + return EXIT_FAILURE; + } + + if (instance->neighbors->IsEmpty()) { + delete instance->neighbors; + instance->neighbors = nullptr; + } +#endif const unsigned max_clients = config_get_positive(CONF_MAX_CONN, 10); instance->client_list = new ClientList(max_clients); - success = listen_global_init(error); - if (!success) { + initialize_decoder_and_player(); + + if (!listen_global_init(*instance->event_loop, *instance->partition, + error)) { LogError(error); return EXIT_FAILURE; } +#ifndef ANDROID daemonize_set_user(); + daemonize_begin(options.daemon); +#endif - GlobalEvents::Initialize(*main_loop); + GlobalEvents::Initialize(*instance->event_loop); GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); #ifdef WIN32 GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); @@ -431,23 +528,23 @@ int mpd_main(int argc, char *argv[]) archive_plugin_init_all(); #endif - if (!pcm_resample_global_init(error)) { + if (!pcm_convert_global_init(error)) { LogError(error); return EXIT_FAILURE; } decoder_plugin_init_all(); - update_global_init(); - create_db = !glue_db_init_and_load(); +#ifdef ENABLE_DATABASE + const bool create_db = InitDatabaseAndStorage(); +#endif glue_sticker_init(); command_init(); - initialize_decoder_and_player(); - volume_init(); initAudioConfig(); - audio_output_all_init(instance->partition->pc); + instance->partition->outputs.Configure(*instance->event_loop, + instance->partition->pc); client_manager_init(); replay_gain_global_init(); @@ -458,43 +555,59 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_init(); - daemonize(options.daemon); +#ifndef ANDROID + daemonize_commit(); setup_log_output(options.log_stderr); - SignalHandlersInit(*main_loop); + SignalHandlersInit(*instance->event_loop); +#endif io_thread_start(); - ZeroconfInit(*main_loop); +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (instance->neighbors != nullptr && + !instance->neighbors->Open(error)) + FatalError(error); +#endif + + ZeroconfInit(*instance->event_loop); player_create(instance->partition->pc); +#ifdef ENABLE_DATABASE if (create_db) { /* the database failed to load: recreate the database */ - unsigned job = update_enqueue("", true); + unsigned job = instance->update->Enqueue("", true); if (job == 0) FatalError("directory update failed"); } +#endif if (!glue_state_file_init(error)) { - g_printerr("%s\n", error.GetMessage()); + LogError(error); return EXIT_FAILURE; } - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); + instance->partition->outputs.SetReplayGainMode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); - success = config_get_bool(CONF_AUTO_UPDATE, false); +#ifdef ENABLE_DATABASE + if (config_get_bool(CONF_AUTO_UPDATE, false)) { #ifdef ENABLE_INOTIFY - if (success && mapper_has_music_directory()) - mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, - G_MAXUINT)); + if (instance->storage != nullptr && + instance->update != nullptr) + mpd_inotify_init(*instance->event_loop, + *instance->storage, + *instance->update, + config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, + INT_MAX)); #else - if (success) FormatWarning(main_domain, "inotify: auto_update was disabled. enable during compilation phase"); #endif + } +#endif config_global_check(); @@ -506,8 +619,12 @@ int mpd_main(int argc, char *argv[]) win32_app_started(); #endif + /* the MPD frontend does not care about timer slack; set it to + a huge value to allow the kernel to reduce CPU wakeups */ + SetThreadTimerSlackMS(100); + /* run the main loop */ - main_loop->Run(); + instance->event_loop->Run(); #ifdef WIN32 win32_app_stopping(); @@ -515,8 +632,11 @@ int mpd_main(int argc, char *argv[]) /* cleanup */ -#ifdef ENABLE_INOTIFY +#if defined(ENABLE_DATABASE) && defined(ENABLE_INOTIFY) mpd_inotify_finish(); + + if (instance->update != nullptr) + instance->update->CancelAllAsync(); #endif if (state_file != nullptr) { @@ -529,11 +649,23 @@ int mpd_main(int argc, char *argv[]) listen_global_finish(); delete instance->client_list; - start = clock(); - DatabaseGlobalDeinit(); - FormatDebug(main_domain, - "db_finish took %f seconds", - ((float)(clock()-start))/CLOCKS_PER_SEC); +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (instance->neighbors != nullptr) { + instance->neighbors->Close(); + delete instance->neighbors; + } +#endif + +#ifdef ENABLE_DATABASE + delete instance->update; + + if (instance->database != nullptr) { + instance->database->Close(); + delete instance->database; + } + + delete instance->storage; +#endif #ifdef ENABLE_SQLITE sticker_global_finish(); @@ -543,27 +675,53 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_finish(); input_stream_global_finish(); - audio_output_all_finish(); - volume_finish(); + +#ifdef ENABLE_DATABASE mapper_finish(); +#endif + delete instance->partition; command_finish(); - update_global_finish(); decoder_plugin_deinit_all(); #ifdef ENABLE_ARCHIVE archive_plugin_deinit_all(); #endif config_global_finish(); - stats_global_finish(); io_thread_deinit(); +#ifndef ANDROID SignalHandlersFinish(); +#endif + delete instance->event_loop; delete instance; - delete main_loop; +#ifndef ANDROID daemonize_finish(); +#endif #ifdef WIN32 WSACleanup(); #endif + IcuFinish(); + log_deinit(); return EXIT_SUCCESS; } + +#ifdef ANDROID + +gcc_visibility_default +JNIEXPORT void JNICALL +Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context) +{ + Java::Init(env); + Java::File::Initialise(env); + Environment::Initialise(env); + + context = new Context(env, _context); + + mpd_main(0, nullptr); + + delete context; + Environment::Deinitialise(env); +} + +#endif diff --git a/src/Main.hxx b/src/Main.hxx index a8ec184c8..7e3fecd0b 100644 --- a/src/Main.hxx +++ b/src/Main.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 @@ -20,16 +20,18 @@ #ifndef MPD_MAIN_HXX #define MPD_MAIN_HXX -class ThreadId; class EventLoop; +class Context; struct Instance; -extern ThreadId main_thread; - -extern EventLoop *main_loop; +#ifdef ANDROID +extern Context *context; +#endif extern Instance *instance; +#ifndef ANDROID + /** * A entry point for application. * On non-Windows platforms this is called directly from main() @@ -38,6 +40,8 @@ extern Instance *instance; */ int mpd_main(int argc, char *argv[]); +#endif + #ifdef WIN32 /** diff --git a/src/Mapper.cxx b/src/Mapper.cxx index cbe45daa0..7baad9459 100644 --- a/src/Mapper.cxx +++ b/src/Mapper.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,36 +23,18 @@ #include "config.h" #include "Mapper.hxx" -#include "Directory.hxx" -#include "Song.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/Charset.hxx" -#include "fs/FileSystem.hxx" -#include "fs/DirectoryReader.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" +#include "fs/CheckFile.hxx" -#include <assert.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <dirent.h> - -static constexpr Domain mapper_domain("mapper"); - -/** - * The absolute path of the music directory encoded in UTF-8. - */ -static std::string music_dir_utf8; -static size_t music_dir_utf8_length; +#ifdef ENABLE_DATABASE +#include "storage/StorageInterface.hxx" +#include "Instance.hxx" +#include "Main.hxx" +#endif -/** - * The absolute path of the music directory encoded in the filesystem - * character set. - */ -static AllocatedPath music_dir_fs = AllocatedPath::Null(); +#include <assert.h> /** * The absolute path of the playlist directory encoded in the @@ -61,67 +43,18 @@ static AllocatedPath music_dir_fs = AllocatedPath::Null(); static AllocatedPath playlist_dir_fs = AllocatedPath::Null(); static void -check_directory(const char *path_utf8, const AllocatedPath &path_fs) -{ - struct stat st; - if (!StatFile(path_fs, st)) { - FormatErrno(mapper_domain, - "Failed to stat directory \"%s\"", - path_utf8); - return; - } - - if (!S_ISDIR(st.st_mode)) { - FormatError(mapper_domain, - "Not a directory: %s", path_utf8); - return; - } - -#ifndef WIN32 - const auto x = AllocatedPath::Build(path_fs, "."); - if (!StatFile(x, st) && errno == EACCES) - FormatError(mapper_domain, - "No permission to traverse (\"execute\") directory: %s", - path_utf8); -#endif - - const DirectoryReader reader(path_fs); - if (reader.HasFailed() && errno == EACCES) - FormatError(mapper_domain, - "No permission to read directory: %s", path_utf8); -} - -static void -mapper_set_music_dir(AllocatedPath &&path) -{ - assert(!path.IsNull()); - - music_dir_fs = std::move(path); - music_dir_fs.ChopSeparators(); - - music_dir_utf8 = music_dir_fs.ToUTF8(); - music_dir_utf8_length = music_dir_utf8.length(); - - check_directory(music_dir_utf8.c_str(), music_dir_fs); -} - -static void mapper_set_playlist_dir(AllocatedPath &&path) { assert(!path.IsNull()); playlist_dir_fs = std::move(path); - const auto utf8 = playlist_dir_fs.ToUTF8(); - check_directory(utf8.c_str(), playlist_dir_fs); + CheckDirectoryReadable(playlist_dir_fs); } void -mapper_init(AllocatedPath &&_music_dir, AllocatedPath &&_playlist_dir) +mapper_init(AllocatedPath &&_playlist_dir) { - if (!_music_dir.IsNull()) - mapper_set_music_dir(std::move(_music_dir)); - if (!_playlist_dir.IsNull()) mapper_set_playlist_dir(std::move(_playlist_dir)); } @@ -130,30 +63,7 @@ void mapper_finish(void) { } -const char * -mapper_get_music_directory_utf8(void) -{ - return music_dir_utf8.empty() - ? nullptr - : music_dir_utf8.c_str(); -} - -const AllocatedPath & -mapper_get_music_directory_fs(void) -{ - return music_dir_fs; -} - -const char * -map_to_relative_path(const char *path_utf8) -{ - return !music_dir_utf8.empty() && - memcmp(path_utf8, music_dir_utf8.c_str(), - music_dir_utf8_length) == 0 && - PathTraits::IsSeparatorUTF8(path_utf8[music_dir_utf8_length]) - ? path_utf8 + music_dir_utf8_length + 1 - : path_utf8; -} +#ifdef ENABLE_DATABASE AllocatedPath map_uri_fs(const char *uri) @@ -161,6 +71,10 @@ map_uri_fs(const char *uri) assert(uri != nullptr); assert(*uri != '/'); + if (instance->storage == nullptr) + return AllocatedPath::Null(); + + const auto music_dir_fs = instance->storage->MapFS(""); if (music_dir_fs.IsNull()) return AllocatedPath::Null(); @@ -171,70 +85,17 @@ map_uri_fs(const char *uri) return AllocatedPath::Build(music_dir_fs, uri_fs); } -AllocatedPath -map_directory_fs(const Directory &directory) -{ - assert(!music_dir_fs.IsNull()); - - if (directory.IsRoot()) - return music_dir_fs; - - return map_uri_fs(directory.GetPath()); -} - -AllocatedPath -map_directory_child_fs(const Directory &directory, const char *name) -{ - assert(!music_dir_fs.IsNull()); - - /* check for invalid or unauthorized base names */ - if (*name == 0 || strchr(name, '/') != nullptr || - strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - return AllocatedPath::Null(); - - const auto parent_fs = map_directory_fs(directory); - if (parent_fs.IsNull()) - return AllocatedPath::Null(); - - const auto name_fs = AllocatedPath::FromUTF8(name); - if (name_fs.IsNull()) - return AllocatedPath::Null(); - - return AllocatedPath::Build(parent_fs, name_fs); -} - -/** - * Map a song object that was created by song_dup_detached(). It does - * not have a real parent directory, only the dummy object - * #detached_root. - */ -static AllocatedPath -map_detached_song_fs(const char *uri_utf8) -{ - auto uri_fs = AllocatedPath::FromUTF8(uri_utf8); - if (uri_fs.IsNull()) - return uri_fs; - - return AllocatedPath::Build(music_dir_fs, uri_fs); -} - -AllocatedPath -map_song_fs(const Song &song) -{ - assert(song.IsFile()); - - if (song.IsInDatabase()) - return song.IsDetached() - ? map_detached_song_fs(song.uri) - : map_directory_child_fs(*song.parent, song.uri); - else - return AllocatedPath::FromUTF8(song.uri); -} - std::string map_fs_to_utf8(const char *path_fs) { - if (PathTraits::IsSeparatorFS(path_fs[0])) { + if (PathTraitsFS::IsSeparator(path_fs[0])) { + if (instance->storage == nullptr) + return std::string(); + + const auto music_dir_fs = instance->storage->MapFS(""); + if (music_dir_fs.IsNull()) + return std::string(); + path_fs = music_dir_fs.RelativeFS(path_fs); if (path_fs == nullptr || *path_fs == 0) return std::string(); @@ -243,6 +104,8 @@ map_fs_to_utf8(const char *path_fs) return PathToUTF8(path_fs); } +#endif + const AllocatedPath & map_spl_path(void) { diff --git a/src/Mapper.hxx b/src/Mapper.hxx index 947fd2822..7ff41f239 100644 --- a/src/Mapper.hxx +++ b/src/Mapper.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 @@ -30,50 +30,14 @@ #define PLAYLIST_FILE_SUFFIX ".m3u" -class Path; class AllocatedPath; -struct Directory; -struct Song; void -mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir); +mapper_init(AllocatedPath &&playlist_dir); void mapper_finish(void); -/** - * Return the absolute path of the music directory encoded in UTF-8 or - * nullptr if no music directory was configured. - */ -gcc_const -const char * -mapper_get_music_directory_utf8(void); - -/** - * Return the absolute path of the music directory encoded in the - * filesystem character set. - */ -gcc_const -const AllocatedPath & -mapper_get_music_directory_fs(void); - -/** - * Returns true if a music directory was configured. - */ -gcc_const -static inline bool -mapper_has_music_directory(void) -{ - return mapper_get_music_directory_utf8() != nullptr; -} - -/** - * If the specified absolute path points inside the music directory, - * this function converts it to a relative path. If not, it returns - * the unmodified string pointer. - */ -gcc_pure -const char * -map_to_relative_path(const char *path_utf8); +#ifdef ENABLE_DATABASE /** * Determines the absolute file system path of a relative URI. This @@ -85,39 +49,6 @@ AllocatedPath map_uri_fs(const char *uri); /** - * Determines the file system path of a directory object. - * - * @param directory the directory object - * @return the path in file system encoding, or nullptr if mapping failed - */ -gcc_pure -AllocatedPath -map_directory_fs(const Directory &directory); - -/** - * Determines the file system path of a directory's child (may be a - * sub directory or a song). - * - * @param directory the parent directory object - * @param name the child's name in UTF-8 - * @return the path in file system encoding, or nullptr if mapping failed - */ -gcc_pure -AllocatedPath -map_directory_child_fs(const Directory &directory, const char *name); - -/** - * Determines the file system path of a song. This must not be a - * remote song. - * - * @param song the song object - * @return the path in file system encoding, or nullptr if mapping failed - */ -gcc_pure -AllocatedPath -map_song_fs(const Song &song); - -/** * Maps a file system path (relative to the music directory or * absolute) to a relative path in UTF-8 encoding. * @@ -129,6 +60,8 @@ gcc_pure std::string map_fs_to_utf8(const char *path_fs); +#endif + /** * Returns the playlist directory. */ @@ -138,8 +71,7 @@ map_spl_path(void); /** * Maps a playlist name (without the ".m3u" suffix) to a file system - * path. The return value is allocated on the heap and must be freed - * with g_free(). + * path. * * @return the path in file system encoding, or nullptr if mapping failed */ diff --git a/src/MemorySongEnumerator.cxx b/src/MemorySongEnumerator.cxx deleted file mode 100644 index 7c9d05daa..000000000 --- a/src/MemorySongEnumerator.cxx +++ /dev/null @@ -1,32 +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 "MemorySongEnumerator.hxx" - -Song * -MemorySongEnumerator::NextSong() -{ - if (songs.empty()) - return nullptr; - - auto result = songs.front().Steal(); - songs.pop_front(); - return result; -} diff --git a/src/MemorySongEnumerator.hxx b/src/MemorySongEnumerator.hxx deleted file mode 100644 index 46086a064..000000000 --- a/src/MemorySongEnumerator.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX -#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX - -#include "SongEnumerator.hxx" -#include "SongPointer.hxx" - -#include <forward_list> - -class MemorySongEnumerator final : public SongEnumerator { - std::forward_list<SongPointer> songs; - -public: - MemorySongEnumerator(std::forward_list<SongPointer> &&_songs) - :songs(std::move(_songs)) {} - - virtual Song *NextSong() override; -}; - -#endif diff --git a/src/MixRampInfo.hxx b/src/MixRampInfo.hxx index 9af41b77d..90c2c984a 100644 --- a/src/MixRampInfo.hxx +++ b/src/MixRampInfo.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/MixerAll.cxx b/src/MixerAll.cxx deleted file mode 100644 index 37225fd25..000000000 --- a/src/MixerAll.cxx +++ /dev/null @@ -1,175 +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 "MixerAll.hxx" -#include "MixerControl.hxx" -#include "MixerInternal.hxx" -#include "MixerList.hxx" -#include "OutputAll.hxx" -#include "pcm/PcmVolume.hxx" -#include "OutputInternal.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> - -static constexpr Domain mixer_domain("mixer"); - -static int -output_mixer_get_volume(unsigned i) -{ - struct audio_output *output; - int volume; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) - return -1; - - Mixer *mixer = output->mixer; - if (mixer == nullptr) - return -1; - - Error error; - volume = mixer_get_volume(mixer, error); - if (volume < 0 && error.IsDefined()) - FormatError(error, - "Failed to read mixer for '%s'", - output->name); - - return volume; -} - -int -mixer_all_get_volume(void) -{ - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; - - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_volume(i); - if (volume >= 0) { - total += volume; - ++ok; - } - } - - if (ok == 0) - return -1; - - return total / ok; -} - -static bool -output_mixer_set_volume(unsigned i, unsigned volume) -{ - struct audio_output *output; - bool success; - - assert(i < audio_output_count()); - assert(volume <= 100); - - output = audio_output_get(i); - if (!output->enabled) - return false; - - Mixer *mixer = output->mixer; - if (mixer == nullptr) - return false; - - Error error; - success = mixer_set_volume(mixer, volume, error); - if (!success && error.IsDefined()) - FormatError(error, - "Failed to set mixer for '%s'", - output->name); - - return success; -} - -bool -mixer_all_set_volume(unsigned volume) -{ - bool success = false; - unsigned count = audio_output_count(); - - assert(volume <= 100); - - for (unsigned i = 0; i < count; i++) - success = output_mixer_set_volume(i, volume) - || success; - - return success; -} - -static int -output_mixer_get_software_volume(unsigned i) -{ - struct audio_output *output; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) - return -1; - - Mixer *mixer = output->mixer; - if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin)) - return -1; - - return mixer_get_volume(mixer, IgnoreError()); -} - -int -mixer_all_get_software_volume(void) -{ - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; - - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_software_volume(i); - if (volume >= 0) { - total += volume; - ++ok; - } - } - - if (ok == 0) - return -1; - - return total / ok; -} - -void -mixer_all_set_software_volume(unsigned volume) -{ - unsigned count = audio_output_count(); - - assert(volume <= PCM_VOLUME_1); - - for (unsigned i = 0; i < count; i++) { - struct audio_output *output = audio_output_get(i); - if (output->mixer != nullptr && - output->mixer->plugin == &software_mixer_plugin) - mixer_set_volume(output->mixer, volume, IgnoreError()); - } -} diff --git a/src/MixerAll.hxx b/src/MixerAll.hxx deleted file mode 100644 index fa7c89801..000000000 --- a/src/MixerAll.hxx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Functions which affect the mixers of all audio outputs. - */ - -#ifndef MPD_MIXER_ALL_HXX -#define MPD_MIXER_ALL_HXX - -#include "Compiler.h" - -/** - * Returns the average volume of all available mixers (range 0..100). - * Returns -1 if no mixer can be queried. - */ -gcc_pure -int -mixer_all_get_volume(void); - -/** - * Sets the volume on all available mixers. - * - * @param volume the volume (range 0..100) - * @return true on success, false on failure - */ -bool -mixer_all_set_volume(unsigned volume); - -/** - * Similar to mixer_all_get_volume(), but gets the volume only for - * software mixers. See #software_mixer_plugin. This function fails - * if no software mixer is configured. - */ -gcc_pure -int -mixer_all_get_software_volume(void); - -/** - * Similar to mixer_all_set_volume(), but sets the volume only for - * software mixers. See #software_mixer_plugin. This function cannot - * fail, because the underlying software mixers cannot fail either. - */ -void -mixer_all_set_software_volume(unsigned volume); - -#endif diff --git a/src/MixerControl.cxx b/src/MixerControl.cxx deleted file mode 100644 index dd4f03543..000000000 --- a/src/MixerControl.cxx +++ /dev/null @@ -1,162 +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 "MixerControl.hxx" -#include "MixerInternal.hxx" -#include "util/Error.hxx" - -#include <assert.h> -#include <stddef.h> - -Mixer * -mixer_new(const struct mixer_plugin *plugin, void *ao, - const config_param ¶m, - Error &error) -{ - Mixer *mixer; - - assert(plugin != nullptr); - - mixer = plugin->init(ao, param, error); - - assert(mixer == nullptr || mixer->IsPlugin(*plugin)); - - return mixer; -} - -void -mixer_free(Mixer *mixer) -{ - assert(mixer != nullptr); - assert(mixer->plugin != nullptr); - - /* mixers with the "global" flag set might still be open at - this point (see mixer_auto_close()) */ - mixer_close(mixer); - - mixer->plugin->finish(mixer); -} - -bool -mixer_open(Mixer *mixer, Error &error) -{ - bool success; - - assert(mixer != nullptr); - assert(mixer->plugin != nullptr); - - const ScopeLock protect(mixer->mutex); - - if (mixer->open) - success = true; - else if (mixer->plugin->open == nullptr) - success = mixer->open = true; - else - success = mixer->open = mixer->plugin->open(mixer, error); - - mixer->failed = !success; - - return success; -} - -static void -mixer_close_internal(Mixer *mixer) -{ - assert(mixer != nullptr); - assert(mixer->plugin != nullptr); - assert(mixer->open); - - if (mixer->plugin->close != nullptr) - mixer->plugin->close(mixer); - - mixer->open = false; -} - -void -mixer_close(Mixer *mixer) -{ - assert(mixer != nullptr); - assert(mixer->plugin != nullptr); - - const ScopeLock protect(mixer->mutex); - - if (mixer->open) - mixer_close_internal(mixer); -} - -void -mixer_auto_close(Mixer *mixer) -{ - if (!mixer->plugin->global) - mixer_close(mixer); -} - -/* - * Close the mixer due to failure. The mutex must be locked before - * calling this function. - */ -static void -mixer_failed(Mixer *mixer) -{ - assert(mixer->open); - - mixer_close_internal(mixer); - - mixer->failed = true; -} - -int -mixer_get_volume(Mixer *mixer, Error &error) -{ - int volume; - - assert(mixer != nullptr); - - if (mixer->plugin->global && !mixer->failed && - !mixer_open(mixer, error)) - return -1; - - const ScopeLock protect(mixer->mutex); - - if (mixer->open) { - volume = mixer->plugin->get_volume(mixer, error); - if (volume < 0 && error.IsDefined()) - mixer_failed(mixer); - } else - volume = -1; - - return volume; -} - -bool -mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - assert(mixer != nullptr); - assert(volume <= 100); - - if (mixer->plugin->global && !mixer->failed && - !mixer_open(mixer, error)) - return false; - - const ScopeLock protect(mixer->mutex); - - return mixer->open && - mixer->plugin->set_volume(mixer, volume, error); -} diff --git a/src/MixerControl.hxx b/src/MixerControl.hxx deleted file mode 100644 index c1ee01eec..000000000 --- a/src/MixerControl.hxx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Functions which manipulate a #mixer object. - */ - -#ifndef MPD_MIXER_CONTROL_HXX -#define MPD_MIXER_CONTROL_HXX - -class Error; -class Mixer; -struct mixer_plugin; -struct config_param; - -Mixer * -mixer_new(const struct mixer_plugin *plugin, void *ao, - const config_param ¶m, - Error &error); - -void -mixer_free(Mixer *mixer); - -bool -mixer_open(Mixer *mixer, Error &error); - -void -mixer_close(Mixer *mixer); - -/** - * Close the mixer unless the plugin's "global" flag is set. This is - * called when the #audio_output is closed. - */ -void -mixer_auto_close(Mixer *mixer); - -int -mixer_get_volume(Mixer *mixer, Error &error); - -bool -mixer_set_volume(Mixer *mixer, unsigned volume, Error &error); - -#endif diff --git a/src/MixerInternal.hxx b/src/MixerInternal.hxx deleted file mode 100644 index e421a34b4..000000000 --- a/src/MixerInternal.hxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MIXER_INTERNAL_HXX -#define MPD_MIXER_INTERNAL_HXX - -#include "MixerPlugin.hxx" -#include "MixerList.hxx" -#include "thread/Mutex.hxx" - -class Mixer { -public: - const struct mixer_plugin *plugin; - - /** - * This mutex protects all of the mixer struct, including its - * implementation, so plugins don't have to deal with that. - */ - Mutex mutex; - - /** - * Is the mixer device currently open? - */ - bool open; - - /** - * Has this mixer failed, and should not be reopened - * automatically? - */ - bool failed; - -public: - Mixer(const mixer_plugin &_plugin) - :plugin(&_plugin), - open(false), - failed(false) {} - - bool IsPlugin(const mixer_plugin &other) const { - return plugin == &other; - } -}; - -#endif diff --git a/src/MixerList.hxx b/src/MixerList.hxx deleted file mode 100644 index 440f442ba..000000000 --- a/src/MixerList.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header provides "extern" declarations for all mixer plugins. - */ - -#ifndef MPD_MIXER_LIST_HXX -#define MPD_MIXER_LIST_HXX - -extern const struct mixer_plugin software_mixer_plugin; -extern const struct mixer_plugin alsa_mixer_plugin; -extern const struct mixer_plugin oss_mixer_plugin; -extern const struct mixer_plugin roar_mixer_plugin; -extern const struct mixer_plugin pulse_mixer_plugin; -extern const struct mixer_plugin winmm_mixer_plugin; - -#endif diff --git a/src/MixerPlugin.hxx b/src/MixerPlugin.hxx deleted file mode 100644 index db6fb2471..000000000 --- a/src/MixerPlugin.hxx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This header declares the mixer_plugin class. It should not be - * included directly; use MixerInternal.hxx instead in mixer - * implementations. - */ - -#ifndef MPD_MIXER_PLUGIN_HXX -#define MPD_MIXER_PLUGIN_HXX - -struct config_param; -class Mixer; -class Error; - -struct mixer_plugin { - /** - * Alocates and configures a mixer device. - * - * @param ao the pointer returned by audio_output_plugin.init - * @param param the configuration section - * @param error_r location to store the error occurring, or - * nullptr to ignore errors - * @return a mixer object, or nullptr on error - */ - Mixer *(*init)(void *ao, const config_param ¶m, - Error &error); - - /** - * Finish and free mixer data - */ - void (*finish)(Mixer *data); - - /** - * Open mixer device - * - * @param error_r location to store the error occurring, or - * nullptr to ignore errors - * @return true on success, false on error - */ - bool (*open)(Mixer *data, Error &error); - - /** - * Close mixer device - */ - void (*close)(Mixer *data); - - /** - * Reads the current volume. - * - * @param error_r location to store the error occurring, or - * nullptr to ignore errors - * @return the current volume (0..100 including) or -1 if - * unavailable or on error (error set, mixer will be closed) - */ - int (*get_volume)(Mixer *mixer, Error &error); - - /** - * Sets the volume. - * - * @param error_r location to store the error occurring, or - * nullptr to ignore errors - * @param volume the new volume (0..100 including) - * @return true on success, false on error - */ - bool (*set_volume)(Mixer *mixer, unsigned volume, - Error &error); - - /** - * If true, then the mixer is automatically opened, even if - * its audio output is not open. If false, then the mixer is - * disabled as long as its audio output is closed. - */ - bool global; -}; - -#endif diff --git a/src/MixerType.cxx b/src/MixerType.cxx deleted file mode 100644 index 435079790..000000000 --- a/src/MixerType.cxx +++ /dev/null @@ -1,39 +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 "MixerType.hxx" - -#include <assert.h> -#include <string.h> - -enum mixer_type -mixer_type_parse(const char *input) -{ - assert(input != NULL); - - if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0) - return MIXER_TYPE_NONE; - else if (strcmp(input, "hardware") == 0) - return MIXER_TYPE_HARDWARE; - else if (strcmp(input, "software") == 0) - return MIXER_TYPE_SOFTWARE; - else - return MIXER_TYPE_UNKNOWN; -} diff --git a/src/MixerType.hxx b/src/MixerType.hxx deleted file mode 100644 index 320a36c04..000000000 --- a/src/MixerType.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_MIXER_TYPE_HXX -#define MPD_MIXER_TYPE_HXX - -enum mixer_type { - /** parser error */ - MIXER_TYPE_UNKNOWN, - - /** mixer disabled */ - MIXER_TYPE_NONE, - - /** software mixer with pcm_volume() */ - MIXER_TYPE_SOFTWARE, - - /** hardware mixer (output's plugin) */ - MIXER_TYPE_HARDWARE, -}; - -/** - * Parses a "mixer_type" setting from the configuration file. - * - * @param input the configured string value; must not be NULL - * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could - * not be parsed - */ -enum mixer_type -mixer_type_parse(const char *input); - -#endif diff --git a/src/MusicBuffer.cxx b/src/MusicBuffer.cxx index c811d8627..709b40413 100644 --- a/src/MusicBuffer.cxx +++ b/src/MusicBuffer.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 @@ -30,7 +30,7 @@ MusicBuffer::MusicBuffer(unsigned num_chunks) FatalError("Failed to allocate buffer"); } -music_chunk * +MusicChunk * MusicBuffer::Allocate() { const ScopeLock protect(mutex); @@ -38,7 +38,7 @@ MusicBuffer::Allocate() } void -MusicBuffer::Return(music_chunk *chunk) +MusicBuffer::Return(MusicChunk *chunk) { assert(chunk != nullptr); diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx index d2b23d43a..cf7c90f91 100644 --- a/src/MusicBuffer.hxx +++ b/src/MusicBuffer.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 @@ -23,22 +23,22 @@ #include "util/SliceBuffer.hxx" #include "thread/Mutex.hxx" -struct music_chunk; +struct MusicChunk; /** - * An allocator for #music_chunk objects. + * An allocator for #MusicChunk objects. */ class MusicBuffer { /** a mutex which protects #buffer */ Mutex mutex; - SliceBuffer<music_chunk> buffer; + SliceBuffer<MusicChunk> buffer; public: /** * Creates a new #MusicBuffer object. * - * @param num_chunks the number of #music_chunk reserved in + * @param num_chunks the number of #MusicChunk reserved in * this buffer */ MusicBuffer(unsigned num_chunks); @@ -71,13 +71,13 @@ public: * @return an empty chunk or nullptr if there are no chunks * available */ - music_chunk *Allocate(); + MusicChunk *Allocate(); /** * Returns a chunk to the buffer. It can be reused by * Allocate() then. */ - void Return(music_chunk *chunk); + void Return(MusicChunk *chunk); }; #endif diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx index 2d20ac7ac..91856709e 100644 --- a/src/MusicChunk.cxx +++ b/src/MusicChunk.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 @@ -24,14 +24,14 @@ #include <assert.h> -music_chunk::~music_chunk() +MusicChunk::~MusicChunk() { delete tag; } #ifndef NDEBUG bool -music_chunk::CheckFormat(const AudioFormat other_format) const +MusicChunk::CheckFormat(const AudioFormat other_format) const { assert(other_format.IsValid()); @@ -40,8 +40,8 @@ music_chunk::CheckFormat(const AudioFormat other_format) const #endif WritableBuffer<void> -music_chunk::Write(const AudioFormat af, - float data_time, uint16_t _bit_rate) +MusicChunk::Write(const AudioFormat af, + float data_time, uint16_t _bit_rate) { assert(CheckFormat(af)); assert(length == 0 || audio_format.IsValid()); @@ -67,7 +67,7 @@ music_chunk::Write(const AudioFormat af, } bool -music_chunk::Expand(const AudioFormat af, size_t _length) +MusicChunk::Expand(const AudioFormat af, size_t _length) { const size_t frame_size = af.GetFrameSize(); diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx index ecd57090b..835221233 100644 --- a/src/MusicChunk.hxx +++ b/src/MusicChunk.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 @@ -39,15 +39,15 @@ struct Tag; * A chunk of music data. Its format is defined by the * MusicPipe::Push() caller. */ -struct music_chunk { +struct MusicChunk { /** the next chunk in a linked list */ - struct music_chunk *next; + MusicChunk *next; /** * An optional chunk which should be mixed into this chunk. * This is used for cross-fading. */ - struct music_chunk *other; + MusicChunk *other; /** * The current mix ratio for cross-fading: 1.0 means play 100% @@ -92,13 +92,13 @@ struct music_chunk { AudioFormat audio_format; #endif - music_chunk() + MusicChunk() :other(nullptr), length(0), tag(nullptr), replay_gain_serial(0) {} - ~music_chunk(); + ~MusicChunk(); bool IsEmpty() const { return length == 0 && tag == nullptr; @@ -116,9 +116,9 @@ struct music_chunk { /** * Prepares appending to the music chunk. Returns a buffer * where you may write into. After you are finished, call - * music_chunk_expand(). + * Expand(). * - * @param chunk the music_chunk object + * @param chunk the MusicChunk object * @param audio_format the audio format for the appended data; * must stay the same for the life cycle of this chunk * @param data_time the time within the song @@ -132,9 +132,9 @@ struct music_chunk { /** * Increases the length of the chunk after the caller has written to - * the buffer returned by music_chunk_write(). + * the buffer returned by Write(). * - * @param chunk the music_chunk object + * @param chunk the MusicChunk object * @param audio_format the audio format for the appended data; must * stay the same for the life cycle of this chunk * @param length the number of bytes which were appended diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx index a5bbe590e..43ce2dbb2 100644 --- a/src/MusicPipe.cxx +++ b/src/MusicPipe.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 @@ -25,11 +25,11 @@ #ifndef NDEBUG bool -MusicPipe::Contains(const music_chunk *chunk) const +MusicPipe::Contains(const MusicChunk *chunk) const { const ScopeLock protect(mutex); - for (const struct music_chunk *i = head; i != nullptr; i = i->next) + for (const MusicChunk *i = head; i != nullptr; i = i->next) if (i == chunk) return true; @@ -38,12 +38,12 @@ MusicPipe::Contains(const music_chunk *chunk) const #endif -music_chunk * +MusicChunk * MusicPipe::Shift() { const ScopeLock protect(mutex); - music_chunk *chunk = head; + MusicChunk *chunk = head; if (chunk != nullptr) { assert(!chunk->IsEmpty()); @@ -62,7 +62,7 @@ MusicPipe::Shift() #ifndef NDEBUG /* poison the "next" reference */ - chunk->next = (music_chunk *)(void *)0x01010101; + chunk->next = (MusicChunk *)(void *)0x01010101; if (size == 0) audio_format.Clear(); @@ -75,14 +75,14 @@ MusicPipe::Shift() void MusicPipe::Clear(MusicBuffer &buffer) { - music_chunk *chunk; + MusicChunk *chunk; while ((chunk = Shift()) != nullptr) buffer.Return(chunk); } void -MusicPipe::Push(music_chunk *chunk) +MusicPipe::Push(MusicChunk *chunk) { assert(!chunk->IsEmpty()); assert(chunk->length == 0 || chunk->audio_format.IsValid()); diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx index f2db33cc5..4f29d0728 100644 --- a/src/MusicPipe.hxx +++ b/src/MusicPipe.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 @@ -29,19 +29,19 @@ #include <assert.h> -struct music_chunk; +struct MusicChunk; class MusicBuffer; /** - * A queue of #music_chunk objects. One party appends chunks at the + * A queue of #MusicChunk objects. One party appends chunks at the * tail, and the other consumes them from the head. */ class MusicPipe { /** the first chunk */ - music_chunk *head; + MusicChunk *head; /** a pointer to the tail of the chunk */ - music_chunk **tail_r; + MusicChunk **tail_r; /** the current number of chunks */ unsigned size; @@ -87,22 +87,22 @@ public: * Checks if the specified chunk is enqueued in the music pipe. */ gcc_pure - bool Contains(const music_chunk *chunk) const; + bool Contains(const MusicChunk *chunk) const; #endif /** - * Returns the first #music_chunk from the pipe. Returns + * Returns the first #MusicChunk from the pipe. Returns * nullptr if the pipe is empty. */ gcc_pure - const music_chunk *Peek() const { + const MusicChunk *Peek() const { return head; } /** * Removes the first chunk from the head, and returns it. */ - music_chunk *Shift(); + MusicChunk *Shift(); /** * Clears the whole pipe and returns the chunks to the buffer. @@ -114,7 +114,7 @@ public: /** * Pushes a chunk to the tail of the pipe. */ - void Push(music_chunk *chunk); + void Push(MusicChunk *chunk); /** * Returns the number of chunks currently in this pipe. diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx deleted file mode 100644 index e905fd9db..000000000 --- a/src/OutputAPI.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_API_HXX -#define MPD_OUTPUT_API_HXX - -#include "OutputPlugin.hxx" -#include "OutputInternal.hxx" -#include "AudioFormat.hxx" -#include "tag/Tag.hxx" -#include "ConfigData.hxx" - -#endif diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx deleted file mode 100644 index 36d41184a..000000000 --- a/src/OutputAll.cxx +++ /dev/null @@ -1,594 +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 "OutputAll.hxx" -#include "PlayerControl.hxx" -#include "OutputInternal.hxx" -#include "OutputControl.hxx" -#include "OutputError.hxx" -#include "MusicBuffer.hxx" -#include "MusicPipe.hxx" -#include "MusicChunk.hxx" -#include "system/FatalError.hxx" -#include "util/Error.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "notify.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static AudioFormat input_audio_format; - -static struct audio_output **audio_outputs; -static unsigned int num_audio_outputs; - -/** - * The #MusicBuffer object where consumed chunks are returned. - */ -static MusicBuffer *g_music_buffer; - -/** - * The #MusicPipe object which feeds all audio outputs. It is filled - * by audio_output_all_play(). - */ -static MusicPipe *g_mp; - -/** - * The "elapsed_time" stamp of the most recently finished chunk. - */ -static float audio_output_all_elapsed_time = -1.0; - -unsigned int audio_output_count(void) -{ - return num_audio_outputs; -} - -struct audio_output * -audio_output_get(unsigned i) -{ - assert(i < num_audio_outputs); - - assert(audio_outputs[i] != nullptr); - - return audio_outputs[i]; -} - -struct audio_output * -audio_output_find(const char *name) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_output_get(i); - - if (strcmp(ao->name, name) == 0) - return ao; - } - - /* name not found */ - return nullptr; -} - -gcc_const -static unsigned -audio_output_config_count(void) -{ - unsigned int nr = 0; - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) - nr++; - if (!nr) - nr = 1; /* we'll always have at least one device */ - return nr; -} - -void -audio_output_all_init(PlayerControl &pc) -{ - const struct config_param *param = nullptr; - unsigned int i; - Error error; - - num_audio_outputs = audio_output_config_count(); - audio_outputs = g_new(struct audio_output *, num_audio_outputs); - - const config_param empty; - - for (i = 0; i < num_audio_outputs; i++) - { - unsigned int j; - - param = config_get_next_param(CONF_AUDIO_OUTPUT, param); - if (param == nullptr) { - /* only allow param to be nullptr if there - just one audio output */ - assert(i == 0); - assert(num_audio_outputs == 1); - - param = ∅ - } - - audio_output *output = audio_output_new(*param, pc, error); - if (output == nullptr) { - if (param != nullptr) - FormatFatalError("line %i: %s", - param->line, - error.GetMessage()); - else - FatalError(error); - } - - audio_outputs[i] = output; - - /* require output names to be unique: */ - for (j = 0; j < i; j++) { - if (!strcmp(output->name, audio_outputs[j]->name)) { - FormatFatalError("output devices with identical " - "names: %s", output->name); - } - } - } -} - -void -audio_output_all_finish(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; i++) { - audio_output_disable(audio_outputs[i]); - audio_output_finish(audio_outputs[i]); - } - - g_free(audio_outputs); - audio_outputs = nullptr; - num_audio_outputs = 0; -} - -void -audio_output_all_enable_disable(void) -{ - for (unsigned i = 0; i < num_audio_outputs; i++) { - struct audio_output *ao = audio_outputs[i]; - bool enabled; - - ao->mutex.lock(); - enabled = ao->really_enabled; - ao->mutex.unlock(); - - if (ao->enabled != enabled) { - if (ao->enabled) - audio_output_enable(ao); - else - audio_output_disable(ao); - } - } -} - -/** - * Determine if all (active) outputs have finished the current - * command. - */ -static bool -audio_output_all_finished(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - const ScopeLock protect(ao->mutex); - if (audio_output_is_open(ao) && - !audio_output_command_is_finished(ao)) - return false; - } - - return true; -} - -static void audio_output_wait_all(void) -{ - while (!audio_output_all_finished()) - audio_output_client_notify.Wait(); -} - -/** - * Signals all audio outputs which are open. - */ -static void -audio_output_allow_play_all(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_allow_play(audio_outputs[i]); -} - -static void -audio_output_reset_reopen(struct audio_output *ao) -{ - const ScopeLock protect(ao->mutex); - - if (!ao->open && ao->fail_timer != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } -} - -/** - * Resets the "reopen" flag on all audio devices. MPD should - * immediately retry to open the device instead of waiting for the - * timeout when the user wants to start playback. - */ -static void -audio_output_all_reset_reopen(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - audio_output_reset_reopen(ao); - } -} - -/** - * Opens all output devices which are enabled, but closed. - * - * @return true if there is at least open output device which is open - */ -static bool -audio_output_all_update(void) -{ - unsigned int i; - bool ret = false; - - if (!input_audio_format.IsDefined()) - return false; - - for (i = 0; i < num_audio_outputs; ++i) - ret = audio_output_update(audio_outputs[i], - input_audio_format, *g_mp) || ret; - - return ret; -} - -void -audio_output_all_set_replay_gain_mode(ReplayGainMode mode) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_set_replay_gain_mode(audio_outputs[i], mode); -} - -bool -audio_output_all_play(struct music_chunk *chunk, Error &error) -{ - bool ret; - unsigned int i; - - assert(g_music_buffer != nullptr); - assert(g_mp != nullptr); - assert(chunk != nullptr); - assert(chunk->CheckFormat(input_audio_format)); - - ret = audio_output_all_update(); - if (!ret) { - /* TODO: obtain real error */ - error.Set(output_domain, "Failed to open audio output"); - return false; - } - - g_mp->Push(chunk); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_play(audio_outputs[i]); - - return true; -} - -bool -audio_output_all_open(const AudioFormat audio_format, - MusicBuffer &buffer, - Error &error) -{ - bool ret = false, enabled = false; - unsigned int i; - - assert(g_music_buffer == nullptr || g_music_buffer == &buffer); - assert((g_mp == nullptr) == (g_music_buffer == nullptr)); - - g_music_buffer = &buffer; - - /* the audio format must be the same as existing chunks in the - pipe */ - assert(g_mp == nullptr || g_mp->CheckFormat(audio_format)); - - if (g_mp == nullptr) - g_mp = new MusicPipe(); - else - /* if the pipe hasn't been cleared, the the audio - format must not have changed */ - assert(g_mp->IsEmpty() || audio_format == input_audio_format); - - input_audio_format = audio_format; - - audio_output_all_reset_reopen(); - audio_output_all_enable_disable(); - audio_output_all_update(); - - for (i = 0; i < num_audio_outputs; ++i) { - if (audio_outputs[i]->enabled) - enabled = true; - - if (audio_outputs[i]->open) - ret = true; - } - - if (!enabled) - error.Set(output_domain, "All audio outputs are disabled"); - else if (!ret) - /* TODO: obtain real error */ - error.Set(output_domain, "Failed to open audio output"); - - if (!ret) - /* close all devices if there was an error */ - audio_output_all_close(); - - return ret; -} - -/** - * Has the specified audio output already consumed this chunk? - */ -static bool -chunk_is_consumed_in(const struct audio_output *ao, - const struct music_chunk *chunk) -{ - if (!ao->open) - return true; - - if (ao->chunk == nullptr) - return false; - - assert(chunk == ao->chunk || g_mp->Contains(ao->chunk)); - - if (chunk != ao->chunk) { - assert(chunk->next != nullptr); - return true; - } - - return ao->chunk_finished && chunk->next == nullptr; -} - -/** - * Has this chunk been consumed by all audio outputs? - */ -static bool -chunk_is_consumed(const struct music_chunk *chunk) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - const ScopeLock protect(ao->mutex); - if (!chunk_is_consumed_in(ao, chunk)) - return false; - } - - return true; -} - -/** - * There's only one chunk left in the pipe (#g_mp), and all audio - * outputs have consumed it already. Clear the reference. - */ -static void -clear_tail_chunk(gcc_unused const struct music_chunk *chunk, bool *locked) -{ - assert(chunk->next == nullptr); - assert(g_mp->Contains(chunk)); - - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - /* this mutex will be unlocked by the caller when it's - ready */ - ao->mutex.lock(); - locked[i] = ao->open; - - if (!locked[i]) { - ao->mutex.unlock(); - continue; - } - - assert(ao->chunk == chunk); - assert(ao->chunk_finished); - ao->chunk = nullptr; - } -} - -unsigned -audio_output_all_check(void) -{ - const struct music_chunk *chunk; - bool is_tail; - struct music_chunk *shifted; - bool locked[num_audio_outputs]; - - assert(g_music_buffer != nullptr); - assert(g_mp != nullptr); - - while ((chunk = g_mp->Peek()) != nullptr) { - assert(!g_mp->IsEmpty()); - - if (!chunk_is_consumed(chunk)) - /* at least one output is not finished playing - this chunk */ - return g_mp->GetSize(); - - if (chunk->length > 0 && chunk->times >= 0.0) - /* only update elapsed_time if the chunk - provides a defined value */ - audio_output_all_elapsed_time = chunk->times; - - is_tail = chunk->next == nullptr; - if (is_tail) - /* this is the tail of the pipe - clear the - chunk reference in all outputs */ - clear_tail_chunk(chunk, locked); - - /* remove the chunk from the pipe */ - shifted = g_mp->Shift(); - assert(shifted == chunk); - - if (is_tail) - /* unlock all audio outputs which were locked - by clear_tail_chunk() */ - for (unsigned i = 0; i < num_audio_outputs; ++i) - if (locked[i]) - audio_outputs[i]->mutex.unlock(); - - /* return the chunk to the buffer */ - g_music_buffer->Return(shifted); - } - - return 0; -} - -bool -audio_output_all_wait(PlayerControl &pc, unsigned threshold) -{ - pc.Lock(); - - if (audio_output_all_check() < threshold) { - pc.Unlock(); - return true; - } - - pc.Wait(); - pc.Unlock(); - - return audio_output_all_check() < threshold; -} - -void -audio_output_all_pause(void) -{ - unsigned int i; - - audio_output_all_update(); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_pause(audio_outputs[i]); - - audio_output_wait_all(); -} - -void -audio_output_all_drain(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_drain_async(audio_outputs[i]); - - audio_output_wait_all(); -} - -void -audio_output_all_cancel(void) -{ - unsigned int i; - - /* send the cancel() command to all audio outputs */ - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_cancel(audio_outputs[i]); - - audio_output_wait_all(); - - /* clear the music pipe and return all chunks to the buffer */ - - if (g_mp != nullptr) - g_mp->Clear(*g_music_buffer); - - /* the audio outputs are now waiting for a signal, to - synchronize the cleared music pipe */ - - audio_output_allow_play_all(); - - /* invalidate elapsed_time */ - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_close(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_close(audio_outputs[i]); - - if (g_mp != nullptr) { - assert(g_music_buffer != nullptr); - - g_mp->Clear(*g_music_buffer); - delete g_mp; - g_mp = nullptr; - } - - g_music_buffer = nullptr; - - input_audio_format.Clear(); - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_release(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_release(audio_outputs[i]); - - if (g_mp != nullptr) { - assert(g_music_buffer != nullptr); - - g_mp->Clear(*g_music_buffer); - delete g_mp; - g_mp = nullptr; - } - - g_music_buffer = nullptr; - - input_audio_format.Clear(); - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_song_border(void) -{ - /* clear the elapsed_time pointer at the beginning of a new - song */ - audio_output_all_elapsed_time = 0.0; -} - -float -audio_output_all_get_elapsed_time(void) -{ - return audio_output_all_elapsed_time; -} diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx deleted file mode 100644 index 98061c345..000000000 --- a/src/OutputAll.hxx +++ /dev/null @@ -1,174 +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. - */ - -/* - * Functions for dealing with all configured (enabled) audion outputs - * at once. - * - */ - -#ifndef OUTPUT_ALL_H -#define OUTPUT_ALL_H - -#include "ReplayGainInfo.hxx" -#include "Compiler.h" - -struct AudioFormat; -class MusicBuffer; -struct music_chunk; -struct PlayerControl; -class Error; - -/** - * Global initialization: load audio outputs from the configuration - * file and initialize them. - */ -void -audio_output_all_init(PlayerControl &pc); - -/** - * Global finalization: free memory occupied by audio outputs. All - */ -void -audio_output_all_finish(void); - -/** - * Returns the total number of audio output devices, including those - * who are disabled right now. - */ -gcc_const -unsigned int audio_output_count(void); - -/** - * Returns the "i"th audio output device. - */ -gcc_const -struct audio_output * -audio_output_get(unsigned i); - -/** - * Returns the audio output device with the specified name. Returns - * NULL if the name does not exist. - */ -gcc_pure -struct audio_output * -audio_output_find(const char *name); - -/** - * Checks the "enabled" flag of all audio outputs, and if one has - * changed, commit the change. - */ -void -audio_output_all_enable_disable(void); - -/** - * Opens all audio outputs which are not disabled. - * - * @param audio_format the preferred audio format - * @param buffer the #music_buffer where consumed #music_chunk objects - * should be returned - * @return true on success, false on failure - */ -bool -audio_output_all_open(AudioFormat audio_format, - MusicBuffer &buffer, - Error &error); - -/** - * Closes all audio outputs. - */ -void -audio_output_all_close(void); - -/** - * Closes all audio outputs. Outputs with the "always_on" flag are - * put into pause mode. - */ -void -audio_output_all_release(void); - -void -audio_output_all_set_replay_gain_mode(ReplayGainMode mode); - -/** - * Enqueue a #music_chunk object for playing, i.e. pushes it to a - * #MusicPipe. - * - * @param chunk the #music_chunk object to be played - * @return true on success, false if no audio output was able to play - * (all closed then) - */ -bool -audio_output_all_play(struct music_chunk *chunk, Error &error); - -/** - * Checks if the output devices have drained their music pipe, and - * returns the consumed music chunks to the #music_buffer. - * - * @return the number of chunks to play left in the #MusicPipe - */ -unsigned -audio_output_all_check(void); - -/** - * Checks if the size of the #MusicPipe is below the #threshold. If - * not, it attempts to synchronize with all output threads, and waits - * until another #music_chunk is finished. - * - * @param threshold the maximum number of chunks in the pipe - * @return true if there are less than #threshold chunks in the pipe - */ -bool -audio_output_all_wait(PlayerControl &pc, unsigned threshold); - -/** - * Puts all audio outputs into pause mode. Most implementations will - * simply close it then. - */ -void -audio_output_all_pause(void); - -/** - * Drain all audio outputs. - */ -void -audio_output_all_drain(void); - -/** - * Try to cancel data which may still be in the device's buffers. - */ -void -audio_output_all_cancel(void); - -/** - * Indicate that a new song will begin now. - */ -void -audio_output_all_song_border(void); - -/** - * Returns the "elapsed_time" stamp of the most recently finished - * chunk. A negative value is returned when no chunk has been - * finished yet. - */ -gcc_pure -float -audio_output_all_get_elapsed_time(void); - -#endif diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx deleted file mode 100644 index 10b5bb322..000000000 --- a/src/OutputCommand.cxx +++ /dev/null @@ -1,113 +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. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#include "config.h" -#include "OutputCommand.hxx" -#include "OutputAll.hxx" -#include "OutputInternal.hxx" -#include "OutputPlugin.hxx" -#include "PlayerControl.hxx" -#include "MixerControl.hxx" -#include "Idle.hxx" - -extern unsigned audio_output_state_version; - -bool -audio_output_enable_index(unsigned idx) -{ - struct audio_output *ao; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (ao->enabled) - return true; - - ao->enabled = true; - idle_add(IDLE_OUTPUT); - - ao->player_control->UpdateAudio(); - - ++audio_output_state_version; - - return true; -} - -bool -audio_output_disable_index(unsigned idx) -{ - struct audio_output *ao; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (!ao->enabled) - return true; - - ao->enabled = false; - idle_add(IDLE_OUTPUT); - - Mixer *mixer = ao->mixer; - if (mixer != nullptr) { - mixer_close(mixer); - idle_add(IDLE_MIXER); - } - - ao->player_control->UpdateAudio(); - - ++audio_output_state_version; - - return true; -} - -bool -audio_output_toggle_index(unsigned idx) -{ - struct audio_output *ao; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - const bool enabled = ao->enabled = !ao->enabled; - idle_add(IDLE_OUTPUT); - - if (!enabled) { - Mixer *mixer = ao->mixer; - if (mixer != nullptr) { - mixer_close(mixer); - idle_add(IDLE_MIXER); - } - } - - ao->player_control->UpdateAudio(); - - ++audio_output_state_version; - - return true; -} diff --git a/src/OutputCommand.hxx b/src/OutputCommand.hxx deleted file mode 100644 index 46fab92c5..000000000 --- a/src/OutputCommand.hxx +++ /dev/null @@ -1,51 +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. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#ifndef MPD_OUTPUT_COMMAND_HXX -#define MPD_OUTPUT_COMMAND_HXX - -/** - * Enables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_enable_index(unsigned idx); - -/** - * Disables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_disable_index(unsigned idx); - -/** - * Toggles an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_toggle_index(unsigned idx); - -#endif diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx deleted file mode 100644 index 27f280231..000000000 --- a/src/OutputControl.cxx +++ /dev/null @@ -1,336 +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 "OutputControl.hxx" -#include "OutputThread.hxx" -#include "OutputInternal.hxx" -#include "OutputPlugin.hxx" -#include "OutputError.hxx" -#include "MixerPlugin.hxx" -#include "MixerControl.hxx" -#include "notify.hxx" -#include "filter/ReplayGainFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -/** after a failure, wait this number of seconds before - automatically reopening the device */ -static constexpr unsigned REOPEN_AFTER = 10; - -struct notify audio_output_client_notify; - -/** - * Waits for command completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_wait(struct audio_output *ao) -{ - while (ao->command != AO_COMMAND_NONE) { - ao->mutex.unlock(); - audio_output_client_notify.Wait(); - ao->mutex.lock(); - } -} - -/** - * Sends a command to the #audio_output object, but does not wait for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) -{ - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - ao->cond.signal(); -} - -/** - * Sends a command to the #audio_output object and waits for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void -ao_command(struct audio_output *ao, enum audio_output_command cmd) -{ - ao_command_async(ao, cmd); - ao_command_wait(ao); -} - -/** - * Lock the #audio_output object and execute the command - * synchronously. - */ -static void -ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) -{ - const ScopeLock protect(ao->mutex); - ao_command(ao, cmd); -} - -void -audio_output_set_replay_gain_mode(struct audio_output *ao, - ReplayGainMode mode) -{ - if (ao->replay_gain_filter != nullptr) - replay_gain_filter_set_mode(ao->replay_gain_filter, mode); - if (ao->other_replay_gain_filter != nullptr) - replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode); -} - -void -audio_output_enable(struct audio_output *ao) -{ - if (!ao->thread.IsDefined()) { - if (ao->plugin->enable == nullptr) { - /* don't bother to start the thread now if the - device doesn't even have a enable() method; - just assign the variable and we're done */ - ao->really_enabled = true; - return; - } - - audio_output_thread_start(ao); - } - - ao_lock_command(ao, AO_COMMAND_ENABLE); -} - -void -audio_output_disable(struct audio_output *ao) -{ - if (!ao->thread.IsDefined()) { - if (ao->plugin->disable == nullptr) - ao->really_enabled = false; - else - /* if there's no thread yet, the device cannot - be enabled */ - assert(!ao->really_enabled); - - return; - } - - ao_lock_command(ao, AO_COMMAND_DISABLE); -} - -/** - * Object must be locked (and unlocked) by the caller. - */ -static bool -audio_output_open(struct audio_output *ao, - const AudioFormat audio_format, - const MusicPipe &mp) -{ - bool open; - - assert(ao != nullptr); - assert(ao->allow_play); - assert(audio_format.IsValid()); - - if (ao->fail_timer != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } - - if (ao->open && audio_format == ao->in_audio_format) { - assert(ao->pipe == &mp || - (ao->always_on && ao->pause)); - - if (ao->pause) { - ao->chunk = nullptr; - ao->pipe = ∓ - - /* unpause with the CANCEL command; this is a - hack, but suits well for forcing the thread - to leave the ao_pause() thread, and we need - to flush the device buffer anyway */ - - /* we're not using audio_output_cancel() here, - because that function is asynchronous */ - ao_command(ao, AO_COMMAND_CANCEL); - } - - return true; - } - - ao->in_audio_format = audio_format; - ao->chunk = nullptr; - - ao->pipe = ∓ - - if (!ao->thread.IsDefined()) - audio_output_thread_start(ao); - - ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); - open = ao->open; - - if (open && ao->mixer != nullptr) { - Error error; - if (!mixer_open(ao->mixer, error)) - FormatWarning(output_domain, - "Failed to open mixer for '%s'", - ao->name); - } - - return open; -} - -/** - * Same as audio_output_close(), but expects the lock to be held by - * the caller. - */ -static void -audio_output_close_locked(struct audio_output *ao) -{ - assert(ao != nullptr); - assert(ao->allow_play); - - if (ao->mixer != nullptr) - mixer_auto_close(ao->mixer); - - assert(!ao->open || ao->fail_timer == nullptr); - - if (ao->open) - ao_command(ao, AO_COMMAND_CLOSE); - else if (ao->fail_timer != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } -} - -bool -audio_output_update(struct audio_output *ao, - const AudioFormat audio_format, - const MusicPipe &mp) -{ - const ScopeLock protect(ao->mutex); - - if (ao->enabled && ao->really_enabled) { - if (ao->fail_timer == nullptr || - g_timer_elapsed(ao->fail_timer, nullptr) > REOPEN_AFTER) { - return audio_output_open(ao, audio_format, mp); - } - } else if (audio_output_is_open(ao)) - audio_output_close_locked(ao); - - return false; -} - -void -audio_output_play(struct audio_output *ao) -{ - const ScopeLock protect(ao->mutex); - - assert(ao->allow_play); - - if (audio_output_is_open(ao) && !ao->in_playback_loop && - !ao->woken_for_play) { - ao->woken_for_play = true; - ao->cond.signal(); - } -} - -void audio_output_pause(struct audio_output *ao) -{ - if (ao->mixer != nullptr && ao->plugin->pause == nullptr) - /* the device has no pause mode: close the mixer, - unless its "global" flag is set (checked by - mixer_auto_close()) */ - mixer_auto_close(ao->mixer); - - const ScopeLock protect(ao->mutex); - - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_PAUSE); -} - -void -audio_output_drain_async(struct audio_output *ao) -{ - const ScopeLock protect(ao->mutex); - - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_DRAIN); -} - -void audio_output_cancel(struct audio_output *ao) -{ - const ScopeLock protect(ao->mutex); - - if (audio_output_is_open(ao)) { - ao->allow_play = false; - ao_command_async(ao, AO_COMMAND_CANCEL); - } -} - -void -audio_output_allow_play(struct audio_output *ao) -{ - const ScopeLock protect(ao->mutex); - - ao->allow_play = true; - if (audio_output_is_open(ao)) - ao->cond.signal(); -} - -void -audio_output_release(struct audio_output *ao) -{ - if (ao->always_on) - audio_output_pause(ao); - else - audio_output_close(ao); -} - -void audio_output_close(struct audio_output *ao) -{ - assert(ao != nullptr); - assert(!ao->open || ao->fail_timer == nullptr); - - const ScopeLock protect(ao->mutex); - audio_output_close_locked(ao); -} - -void audio_output_finish(struct audio_output *ao) -{ - audio_output_close(ao); - - assert(ao->fail_timer == nullptr); - - if (ao->thread.IsDefined()) { - assert(ao->allow_play); - ao_lock_command(ao, AO_COMMAND_KILL); - ao->thread.Join(); - } - - audio_output_free(ao); -} diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx deleted file mode 100644 index d8f0b432d..000000000 --- a/src/OutputControl.hxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_CONTROL_HXX -#define MPD_OUTPUT_CONTROL_HXX - -#include "ReplayGainInfo.hxx" - -#include <stddef.h> - -struct audio_output; -struct AudioFormat; -struct config_param; -class MusicPipe; - -void -audio_output_set_replay_gain_mode(struct audio_output *ao, - ReplayGainMode mode); - -/** - * Enables the device. - */ -void -audio_output_enable(struct audio_output *ao); - -/** - * Disables the device. - */ -void -audio_output_disable(struct audio_output *ao); - -/** - * Opens or closes the device, depending on the "enabled" flag. - * - * @return true if the device is open - */ -bool -audio_output_update(struct audio_output *ao, - AudioFormat audio_format, - const MusicPipe &mp); - -void -audio_output_play(struct audio_output *ao); - -void audio_output_pause(struct audio_output *ao); - -void -audio_output_drain_async(struct audio_output *ao); - -/** - * Clear the "allow_play" flag and send the "CANCEL" command - * asynchronously. To finish the operation, the caller has to call - * audio_output_allow_play(). - */ -void audio_output_cancel(struct audio_output *ao); - -/** - * Set the "allow_play" and signal the thread. - */ -void -audio_output_allow_play(struct audio_output *ao); - -void audio_output_close(struct audio_output *ao); - -/** - * Closes the audio output, but if the "always_on" flag is set, put it - * into pause mode instead. - */ -void -audio_output_release(struct audio_output *ao); - -void audio_output_finish(struct audio_output *ao); - -#endif diff --git a/src/OutputError.cxx b/src/OutputError.cxx deleted file mode 100644 index a18bfff23..000000000 --- a/src/OutputError.cxx +++ /dev/null @@ -1,23 +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 "OutputError.hxx" -#include "util/Domain.hxx" - -const Domain output_domain("output"); diff --git a/src/OutputError.hxx b/src/OutputError.hxx deleted file mode 100644 index 22a11fdbd..000000000 --- a/src/OutputError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_ERROR_HXX -#define MPD_OUTPUT_ERROR_HXX - -extern const class Domain output_domain; - -#endif diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx deleted file mode 100644 index db6599b53..000000000 --- a/src/OutputFinish.cxx +++ /dev/null @@ -1,51 +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 "OutputInternal.hxx" -#include "OutputPlugin.hxx" -#include "MixerControl.hxx" -#include "FilterInternal.hxx" - -#include <assert.h> - -void -ao_base_finish(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == nullptr); - assert(!ao->thread.IsDefined()); - - if (ao->mixer != nullptr) - mixer_free(ao->mixer); - - delete ao->replay_gain_filter; - delete ao->other_replay_gain_filter; - delete ao->filter; -} - -void -audio_output_free(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == nullptr); - assert(!ao->thread.IsDefined()); - - ao_plugin_finish(ao); -} diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx deleted file mode 100644 index 28eba1ab2..000000000 --- a/src/OutputInit.cxx +++ /dev/null @@ -1,331 +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 "OutputInternal.hxx" -#include "OutputControl.hxx" -#include "OutputList.hxx" -#include "OutputError.hxx" -#include "OutputAPI.hxx" -#include "FilterConfig.hxx" -#include "AudioParser.hxx" -#include "MixerList.hxx" -#include "MixerType.hxx" -#include "MixerControl.hxx" -#include "mixer/SoftwareMixerPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterRegistry.hxx" -#include "filter/AutoConvertFilterPlugin.hxx" -#include "filter/ReplayGainFilterPlugin.hxx" -#include "filter/ChainFilterPlugin.hxx" -#include "ConfigError.hxx" -#include "ConfigGlobal.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> - -#define AUDIO_OUTPUT_TYPE "type" -#define AUDIO_OUTPUT_NAME "name" -#define AUDIO_OUTPUT_FORMAT "format" -#define AUDIO_FILTERS "filters" - -static const struct audio_output_plugin * -audio_output_detect(Error &error) -{ - LogDefault(output_domain, "Attempt to detect audio output device"); - - audio_output_plugins_for_each(plugin) { - if (plugin->test_default_device == nullptr) - continue; - - FormatDefault(output_domain, - "Attempting to detect a %s audio device", - plugin->name); - if (ao_plugin_test_default_device(plugin)) - return plugin; - } - - error.Set(output_domain, "Unable to detect an audio device"); - return nullptr; -} - -/** - * Determines the mixer type which should be used for the specified - * configuration block. - * - * This handles the deprecated options mixer_type (global) and - * mixer_enabled, if the mixer_type setting is not configured. - */ -gcc_pure -static enum mixer_type -audio_output_mixer_type(const config_param ¶m) -{ - /* read the local "mixer_type" setting */ - const char *p = param.GetBlockValue("mixer_type"); - if (p != nullptr) - return mixer_type_parse(p); - - /* try the local "mixer_enabled" setting next (deprecated) */ - if (!param.GetBlockValue("mixer_enabled", true)) - return MIXER_TYPE_NONE; - - /* fall back to the global "mixer_type" setting (also - deprecated) */ - return mixer_type_parse(config_get_string(CONF_MIXER_TYPE, - "hardware")); -} - -static Mixer * -audio_output_load_mixer(struct audio_output *ao, - const config_param ¶m, - const struct mixer_plugin *plugin, - Filter &filter_chain, - Error &error) -{ - Mixer *mixer; - - switch (audio_output_mixer_type(param)) { - case MIXER_TYPE_NONE: - case MIXER_TYPE_UNKNOWN: - return nullptr; - - case MIXER_TYPE_HARDWARE: - if (plugin == nullptr) - return nullptr; - - return mixer_new(plugin, ao, param, error); - - case MIXER_TYPE_SOFTWARE: - mixer = mixer_new(&software_mixer_plugin, nullptr, - config_param(), - IgnoreError()); - assert(mixer != nullptr); - - filter_chain_append(filter_chain, "software_mixer", - software_mixer_get_filter(mixer)); - return mixer; - } - - assert(false); - gcc_unreachable(); -} - -bool -ao_base_init(struct audio_output *ao, - const struct audio_output_plugin *plugin, - const config_param ¶m, Error &error) -{ - assert(ao != nullptr); - assert(plugin != nullptr); - assert(plugin->finish != nullptr); - assert(plugin->open != nullptr); - assert(plugin->close != nullptr); - assert(plugin->play != nullptr); - - if (!param.IsNull()) { - ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME); - if (ao->name == nullptr) { - error.Set(config_domain, - "Missing \"name\" configuration"); - return false; - } - - const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); - if (p != nullptr) { - bool success = - audio_format_parse(ao->config_audio_format, - p, true, error); - if (!success) - return false; - } else - ao->config_audio_format.Clear(); - } else { - ao->name = "default detected output"; - - ao->config_audio_format.Clear(); - } - - ao->plugin = plugin; - ao->tags = param.GetBlockValue("tags", true); - ao->always_on = param.GetBlockValue("always_on", false); - ao->enabled = param.GetBlockValue("enabled", true); - ao->really_enabled = false; - ao->open = false; - ao->pause = false; - ao->allow_play = true; - ao->in_playback_loop = false; - ao->woken_for_play = false; - ao->fail_timer = nullptr; - - /* set up the filter chain */ - - ao->filter = filter_chain_new(); - assert(ao->filter != nullptr); - - /* create the normalization filter (if configured) */ - - if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { - Filter *normalize_filter = - filter_new(&normalize_filter_plugin, config_param(), - IgnoreError()); - assert(normalize_filter != nullptr); - - filter_chain_append(*ao->filter, "normalize", - autoconvert_filter_new(normalize_filter)); - } - - Error filter_error; - filter_chain_parse(*ao->filter, - param.GetBlockValue(AUDIO_FILTERS, ""), - filter_error); - - // It's not really fatal - Part of the filter chain has been set up already - // and even an empty one will work (if only with unexpected behaviour) - if (filter_error.IsDefined()) - FormatError(filter_error, - "Failed to initialize filter chain for '%s'", - ao->name); - - ao->command = AO_COMMAND_NONE; - - ao->mixer = nullptr; - ao->replay_gain_filter = nullptr; - ao->other_replay_gain_filter = nullptr; - - /* done */ - - return true; -} - -static bool -audio_output_setup(struct audio_output *ao, const config_param ¶m, - Error &error) -{ - - /* create the replay_gain filter */ - - const char *replay_gain_handler = - param.GetBlockValue("replay_gain_handler", "software"); - - if (strcmp(replay_gain_handler, "none") != 0) { - ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, IgnoreError()); - assert(ao->replay_gain_filter != nullptr); - - ao->replay_gain_serial = 0; - - ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, - IgnoreError()); - assert(ao->other_replay_gain_filter != nullptr); - - ao->other_replay_gain_serial = 0; - } else { - ao->replay_gain_filter = nullptr; - ao->other_replay_gain_filter = nullptr; - } - - /* set up the mixer */ - - Error mixer_error; - ao->mixer = audio_output_load_mixer(ao, param, - ao->plugin->mixer_plugin, - *ao->filter, mixer_error); - if (ao->mixer == nullptr && mixer_error.IsDefined()) - FormatError(mixer_error, - "Failed to initialize hardware mixer for '%s'", - ao->name); - - /* use the hardware mixer for replay gain? */ - - if (strcmp(replay_gain_handler, "mixer") == 0) { - if (ao->mixer != nullptr) - replay_gain_filter_set_mixer(ao->replay_gain_filter, - ao->mixer, 100); - else - FormatError(output_domain, - "No such mixer for output '%s'", ao->name); - } else if (strcmp(replay_gain_handler, "software") != 0 && - ao->replay_gain_filter != nullptr) { - error.Set(config_domain, - "Invalid \"replay_gain_handler\" value"); - return false; - } - - /* the "convert" filter must be the last one in the chain */ - - ao->convert_filter = filter_new(&convert_filter_plugin, config_param(), - IgnoreError()); - assert(ao->convert_filter != nullptr); - - filter_chain_append(*ao->filter, "convert", ao->convert_filter); - - return true; -} - -struct audio_output * -audio_output_new(const config_param ¶m, - PlayerControl &pc, - Error &error) -{ - const struct audio_output_plugin *plugin; - - if (!param.IsNull()) { - const char *p; - - p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); - if (p == nullptr) { - error.Set(config_domain, - "Missing \"type\" configuration"); - return nullptr; - } - - plugin = audio_output_plugin_get(p); - if (plugin == nullptr) { - error.Format(config_domain, - "No such audio output plugin: %s", p); - return nullptr; - } - } else { - LogWarning(output_domain, - "No 'audio_output' defined in config file"); - - plugin = audio_output_detect(error); - if (plugin == nullptr) - return nullptr; - - FormatDefault(output_domain, - "Successfully detected a %s audio device", - plugin->name); - } - - struct audio_output *ao = ao_plugin_init(plugin, param, error); - if (ao == nullptr) - return nullptr; - - if (!audio_output_setup(ao, param, error)) { - ao_plugin_finish(ao); - return nullptr; - } - - ao->player_control = &pc; - return ao; -} diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx deleted file mode 100644 index c07cdf856..000000000 --- a/src/OutputInternal.hxx +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_INTERNAL_HXX -#define MPD_OUTPUT_INTERNAL_HXX - -#include "AudioFormat.hxx" -#include "pcm/PcmBuffer.hxx" -#include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "thread/Thread.hxx" - -#include <time.h> - -class Error; -class Filter; -class MusicPipe; -struct config_param; -struct PlayerControl; -typedef struct _GTimer GTimer; - -enum audio_output_command { - AO_COMMAND_NONE = 0, - AO_COMMAND_ENABLE, - AO_COMMAND_DISABLE, - AO_COMMAND_OPEN, - - /** - * This command is invoked when the input audio format - * changes. - */ - AO_COMMAND_REOPEN, - - AO_COMMAND_CLOSE, - AO_COMMAND_PAUSE, - - /** - * Drains the internal (hardware) buffers of the device. This - * operation may take a while to complete. - */ - AO_COMMAND_DRAIN, - - AO_COMMAND_CANCEL, - AO_COMMAND_KILL -}; - -struct audio_output { - /** - * The device's configured display name. - */ - const char *name; - - /** - * The plugin which implements this output device. - */ - const struct audio_output_plugin *plugin; - - /** - * The #mixer object associated with this audio output device. - * May be nullptr if none is available, or if software volume is - * configured. - */ - class Mixer *mixer; - - /** - * Will this output receive tags from the decoder? The - * default is true, but it may be configured to false to - * suppress sending tags to the output. - */ - bool tags; - - /** - * Shall this output always play something (i.e. silence), - * even when playback is stopped? - */ - bool always_on; - - /** - * Has the user enabled this device? - */ - bool enabled; - - /** - * Is this device actually enabled, i.e. the "enable" method - * has succeeded? - */ - bool really_enabled; - - /** - * Is the device (already) open and functional? - * - * This attribute may only be modified by the output thread. - * It is protected with #mutex: write accesses inside the - * output thread and read accesses outside of it may only be - * performed while the lock is held. - */ - bool open; - - /** - * Is the device paused? i.e. the output thread is in the - * ao_pause() loop. - */ - bool pause; - - /** - * When this flag is set, the output thread will not do any - * playback. It will wait until the flag is cleared. - * - * This is used to synchronize the "clear" operation on the - * shared music pipe during the CANCEL command. - */ - bool allow_play; - - /** - * True while the OutputThread is inside ao_play(). This - * means the PlayerThread does not need to wake up the - * OutputThread when new chunks are added to the MusicPipe, - * because the OutputThread is already watching that. - */ - bool in_playback_loop; - - /** - * Has the OutputThread been woken up to play more chunks? - * This is set by audio_output_play() and reset by ao_play() - * to reduce the number of duplicate wakeups. - */ - bool woken_for_play; - - /** - * If not nullptr, the device has failed, and this timer is used - * to estimate how long it should stay disabled (unless - * explicitly reopened with "play"). - */ - GTimer *fail_timer; - - /** - * The configured audio format. - */ - AudioFormat config_audio_format; - - /** - * The audio_format in which audio data is received from the - * player thread (which in turn receives it from the decoder). - */ - AudioFormat in_audio_format; - - /** - * The audio_format which is really sent to the device. This - * is basically config_audio_format (if configured) or - * in_audio_format, but may have been modified by - * plugin->open(). - */ - AudioFormat out_audio_format; - - /** - * The buffer used to allocate the cross-fading result. - */ - PcmBuffer cross_fade_buffer; - - /** - * The filter object of this audio output. This is an - * instance of chain_filter_plugin. - */ - Filter *filter; - - /** - * The replay_gain_filter_plugin instance of this audio - * output. - */ - Filter *replay_gain_filter; - - /** - * The serial number of the last replay gain info. 0 means no - * replay gain info was available. - */ - unsigned replay_gain_serial; - - /** - * The replay_gain_filter_plugin instance of this audio - * output, to be applied to the second chunk during - * cross-fading. - */ - Filter *other_replay_gain_filter; - - /** - * The serial number of the last replay gain info by the - * "other" chunk during cross-fading. - */ - unsigned other_replay_gain_serial; - - /** - * The convert_filter_plugin instance of this audio output. - * It is the last item in the filter chain, and is responsible - * for converting the input data into the appropriate format - * for this audio output. - */ - Filter *convert_filter; - - /** - * The thread handle, or nullptr if the output thread isn't - * running. - */ - Thread thread; - - /** - * The next command to be performed by the output thread. - */ - enum audio_output_command command; - - /** - * The music pipe which provides music chunks to be played. - */ - const MusicPipe *pipe; - - /** - * This mutex protects #open, #fail_timer, #chunk and - * #chunk_finished. - */ - Mutex mutex; - - /** - * This condition object wakes up the output thread after - * #command has been set. - */ - Cond cond; - - /** - * The PlayerControl object which "owns" this output. This - * object is needed to signal command completion. - */ - PlayerControl *player_control; - - /** - * The #music_chunk which is currently being played. All - * chunks before this one may be returned to the - * #music_buffer, because they are not going to be used by - * this output anymore. - */ - const struct music_chunk *chunk; - - /** - * Has the output finished playing #chunk? - */ - bool chunk_finished; -}; - -/** - * Notify object used by the thread's client, i.e. we will send a - * notify signal to this object, expecting the caller to wait on it. - */ -extern struct notify audio_output_client_notify; - -static inline bool -audio_output_is_open(const struct audio_output *ao) -{ - return ao->open; -} - -static inline bool -audio_output_command_is_finished(const struct audio_output *ao) -{ - return ao->command == AO_COMMAND_NONE; -} - -struct audio_output * -audio_output_new(const config_param ¶m, - PlayerControl &pc, - Error &error); - -bool -ao_base_init(struct audio_output *ao, - const struct audio_output_plugin *plugin, - const config_param ¶m, Error &error); - -void -ao_base_finish(struct audio_output *ao); - -void -audio_output_free(struct audio_output *ao); - -#endif diff --git a/src/OutputList.cxx b/src/OutputList.cxx deleted file mode 100644 index b569408dc..000000000 --- a/src/OutputList.cxx +++ /dev/null @@ -1,100 +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 "OutputList.hxx" -#include "OutputAPI.hxx" -#include "output/AlsaOutputPlugin.hxx" -#include "output/AoOutputPlugin.hxx" -#include "output/FifoOutputPlugin.hxx" -#include "output/HttpdOutputPlugin.hxx" -#include "output/JackOutputPlugin.hxx" -#include "output/NullOutputPlugin.hxx" -#include "output/OpenALOutputPlugin.hxx" -#include "output/OssOutputPlugin.hxx" -#include "output/OSXOutputPlugin.hxx" -#include "output/PipeOutputPlugin.hxx" -#include "output/PulseOutputPlugin.hxx" -#include "output/RecorderOutputPlugin.hxx" -#include "output/RoarOutputPlugin.hxx" -#include "output/ShoutOutputPlugin.hxx" -#include "output/SolarisOutputPlugin.hxx" -#include "output/WinmmOutputPlugin.hxx" - -#include <string.h> - -const struct audio_output_plugin *const audio_output_plugins[] = { -#ifdef HAVE_SHOUT - &shout_output_plugin, -#endif - &null_output_plugin, -#ifdef HAVE_FIFO - &fifo_output_plugin, -#endif -#ifdef ENABLE_PIPE_OUTPUT - &pipe_output_plugin, -#endif -#ifdef HAVE_ALSA - &alsa_output_plugin, -#endif -#ifdef HAVE_ROAR - &roar_output_plugin, -#endif -#ifdef HAVE_AO - &ao_output_plugin, -#endif -#ifdef HAVE_OSS - &oss_output_plugin, -#endif -#ifdef HAVE_OPENAL - &openal_output_plugin, -#endif -#ifdef HAVE_OSX - &osx_output_plugin, -#endif -#ifdef ENABLE_SOLARIS_OUTPUT - &solaris_output_plugin, -#endif -#ifdef HAVE_PULSE - &pulse_output_plugin, -#endif -#ifdef HAVE_JACK - &jack_output_plugin, -#endif -#ifdef ENABLE_HTTPD_OUTPUT - &httpd_output_plugin, -#endif -#ifdef ENABLE_RECORDER_OUTPUT - &recorder_output_plugin, -#endif -#ifdef ENABLE_WINMM_OUTPUT - &winmm_output_plugin, -#endif - nullptr -}; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name) -{ - audio_output_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return nullptr; -} diff --git a/src/OutputList.hxx b/src/OutputList.hxx deleted file mode 100644 index 756cf22a4..000000000 --- a/src/OutputList.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_LIST_HXX -#define MPD_OUTPUT_LIST_HXX - -extern const struct audio_output_plugin *const audio_output_plugins[]; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name); - -#define audio_output_plugins_for_each(plugin) \ - for (const struct audio_output_plugin *plugin, \ - *const*output_plugin_iterator = &audio_output_plugins[0]; \ - (plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator) - -#endif diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx deleted file mode 100644 index 31d9cce96..000000000 --- a/src/OutputPlugin.cxx +++ /dev/null @@ -1,109 +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 "OutputPlugin.hxx" -#include "OutputInternal.hxx" - -struct audio_output * -ao_plugin_init(const struct audio_output_plugin *plugin, - const config_param ¶m, - Error &error) -{ - assert(plugin != nullptr); - assert(plugin->init != nullptr); - - return plugin->init(param, error); -} - -void -ao_plugin_finish(struct audio_output *ao) -{ - ao->plugin->finish(ao); -} - -bool -ao_plugin_enable(struct audio_output *ao, Error &error_r) -{ - return ao->plugin->enable != nullptr - ? ao->plugin->enable(ao, error_r) - : true; -} - -void -ao_plugin_disable(struct audio_output *ao) -{ - if (ao->plugin->disable != nullptr) - ao->plugin->disable(ao); -} - -bool -ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - return ao->plugin->open(ao, audio_format, error); -} - -void -ao_plugin_close(struct audio_output *ao) -{ - ao->plugin->close(ao); -} - -unsigned -ao_plugin_delay(struct audio_output *ao) -{ - return ao->plugin->delay != nullptr - ? ao->plugin->delay(ao) - : 0; -} - -void -ao_plugin_send_tag(struct audio_output *ao, const Tag *tag) -{ - if (ao->plugin->send_tag != nullptr) - ao->plugin->send_tag(ao, tag); -} - -size_t -ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - return ao->plugin->play(ao, chunk, size, error); -} - -void -ao_plugin_drain(struct audio_output *ao) -{ - if (ao->plugin->drain != nullptr) - ao->plugin->drain(ao); -} - -void -ao_plugin_cancel(struct audio_output *ao) -{ - if (ao->plugin->cancel != nullptr) - ao->plugin->cancel(ao); -} - -bool -ao_plugin_pause(struct audio_output *ao) -{ - return ao->plugin->pause != nullptr && ao->plugin->pause(ao); -} diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx deleted file mode 100644 index b9f4b7dfb..000000000 --- a/src/OutputPlugin.hxx +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_PLUGIN_HXX -#define MPD_OUTPUT_PLUGIN_HXX - -#include "Compiler.h" - -#include <stddef.h> - -struct config_param; -struct AudioFormat; -struct Tag; -class Error; - -/** - * A plugin which controls an audio output device. - */ -struct audio_output_plugin { - /** - * the plugin's name - */ - const char *name; - - /** - * Test if this plugin can provide a default output, in case - * none has been configured. This method is optional. - */ - bool (*test_default_device)(void); - - /** - * Configure and initialize the device, but do not open it - * yet. - * - * @param param the configuration section, or nullptr if there is - * no configuration - * @return nullptr on error, or an opaque pointer to the plugin's - * data - */ - struct audio_output *(*init)(const config_param ¶m, - Error &error); - - /** - * Free resources allocated by this device. - */ - void (*finish)(struct audio_output *data); - - /** - * Enable the device. This may allocate resources, preparing - * for the device to be opened. Enabling a device cannot - * fail: if an error occurs during that, it should be reported - * by the open() method. - * - * @return true on success, false on error - */ - bool (*enable)(struct audio_output *data, Error &error); - - /** - * Disables the device. It is closed before this method is - * called. - */ - void (*disable)(struct audio_output *data); - - /** - * Really open the device. - * - * @param audio_format the audio format in which data is going - * to be delivered; may be modified by the plugin - */ - bool (*open)(struct audio_output *data, AudioFormat &audio_format, - Error &error); - - /** - * Close the device. - */ - void (*close)(struct audio_output *data); - - /** - * Returns a positive number if the output thread shall delay - * the next call to play() or pause(). This should be - * implemented instead of doing a sleep inside the plugin, - * because this allows MPD to listen to commands meanwhile. - * - * @return the number of milliseconds to wait - */ - unsigned (*delay)(struct audio_output *data); - - /** - * Display metadata for the next chunk. Optional method, - * because not all devices can display metadata. - */ - void (*send_tag)(struct audio_output *data, const Tag *tag); - - /** - * Play a chunk of audio data. - * - * @return the number of bytes played, or 0 on error - */ - size_t (*play)(struct audio_output *data, - const void *chunk, size_t size, - Error &error); - - /** - * Wait until the device has finished playing. - */ - void (*drain)(struct audio_output *data); - - /** - * Try to cancel data which may still be in the device's - * buffers. - */ - void (*cancel)(struct audio_output *data); - - /** - * Pause the device. If supported, it may perform a special - * action, which keeps the device open, but does not play - * anything. Output plugins like "shout" might want to play - * silence during pause, so their clients won't be - * disconnected. Plugins which do not support pausing will - * simply be closed, and have to be reopened when unpaused. - * - * @return false on error (output will be closed then), true - * for continue to pause - */ - bool (*pause)(struct audio_output *data); - - /** - * The mixer plugin associated with this output plugin. This - * may be nullptr if no mixer plugin is implemented. When - * created, this mixer plugin gets the same #config_param as - * this audio output device. - */ - const struct mixer_plugin *mixer_plugin; -}; - -static inline bool -ao_plugin_test_default_device(const struct audio_output_plugin *plugin) -{ - return plugin->test_default_device != nullptr - ? plugin->test_default_device() - : false; -} - -gcc_malloc -struct audio_output * -ao_plugin_init(const struct audio_output_plugin *plugin, - const config_param ¶m, - Error &error); - -void -ao_plugin_finish(struct audio_output *ao); - -bool -ao_plugin_enable(struct audio_output *ao, Error &error); - -void -ao_plugin_disable(struct audio_output *ao); - -bool -ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error); - -void -ao_plugin_close(struct audio_output *ao); - -gcc_pure -unsigned -ao_plugin_delay(struct audio_output *ao); - -void -ao_plugin_send_tag(struct audio_output *ao, const Tag *tag); - -size_t -ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error); - -void -ao_plugin_drain(struct audio_output *ao); - -void -ao_plugin_cancel(struct audio_output *ao); - -bool -ao_plugin_pause(struct audio_output *ao); - -#endif diff --git a/src/OutputPrint.cxx b/src/OutputPrint.cxx deleted file mode 100644 index 30f2c6732..000000000 --- a/src/OutputPrint.cxx +++ /dev/null @@ -1,45 +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. - */ - -/* - * Protocol specific code for the audio output library. - * - */ - -#include "config.h" -#include "OutputPrint.hxx" -#include "OutputAll.hxx" -#include "OutputInternal.hxx" -#include "Client.hxx" - -void -printAudioDevices(Client &client) -{ - const unsigned n = audio_output_count(); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); - - client_printf(client, - "outputid: %i\n" - "outputname: %s\n" - "outputenabled: %i\n", - i, ao->name, ao->enabled); - } -} diff --git a/src/OutputPrint.hxx b/src/OutputPrint.hxx deleted file mode 100644 index 5d446d702..000000000 --- a/src/OutputPrint.hxx +++ /dev/null @@ -1,34 +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. - */ - -/* - * Protocol specific code for the audio output library. - * - */ - -#ifndef MPD_OUTPUT_PRINT_HXX -#define MPD_OUTPUT_PRINT_HXX - -class Client; - -void -printAudioDevices(Client &client); - -#endif diff --git a/src/OutputState.cxx b/src/OutputState.cxx deleted file mode 100644 index a3650413c..000000000 --- a/src/OutputState.cxx +++ /dev/null @@ -1,94 +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. - */ - -/* - * Saving and loading the audio output states to/from the state file. - * - */ - -#include "config.h" -#include "OutputState.hxx" -#include "OutputAll.hxx" -#include "OutputInternal.hxx" -#include "OutputError.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#define AUDIO_DEVICE_STATE "audio_device_state:" - -unsigned audio_output_state_version; - -void -audio_output_state_save(FILE *fp) -{ - unsigned n = audio_output_count(); - - assert(n > 0); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); - - fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", - ao->enabled, ao->name); - } -} - -bool -audio_output_state_read(const char *line) -{ - long value; - char *endptr; - const char *name; - struct audio_output *ao; - - if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) - return false; - - line += sizeof(AUDIO_DEVICE_STATE) - 1; - - value = strtol(line, &endptr, 10); - if (*endptr != ':' || (value != 0 && value != 1)) - return false; - - if (value != 0) - /* state is "enabled": no-op */ - return true; - - name = endptr + 1; - ao = audio_output_find(name); - if (ao == NULL) { - FormatDebug(output_domain, - "Ignoring device state for '%s'", name); - return true; - } - - ao->enabled = false; - return true; -} - -unsigned -audio_output_state_get_version(void) -{ - return audio_output_state_version; -} diff --git a/src/OutputState.hxx b/src/OutputState.hxx deleted file mode 100644 index 5ab765ba8..000000000 --- a/src/OutputState.hxx +++ /dev/null @@ -1,44 +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. - */ - -/* - * Saving and loading the audio output states to/from the state file. - * - */ - -#ifndef MPD_OUTPUT_STATE_HXX -#define MPD_OUTPUT_STATE_HXX - -#include <stdio.h> - -bool -audio_output_state_read(const char *line); - -void -audio_output_state_save(FILE *fp); - -/** - * Generates a version number for the current state of the audio - * outputs. This is used by timer_save_state_file() to determine - * whether the state has changed and the state file should be saved. - */ -unsigned -audio_output_state_get_version(void); - -#endif diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx deleted file mode 100644 index 30d3ba30f..000000000 --- a/src/OutputThread.cxx +++ /dev/null @@ -1,679 +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 "OutputThread.hxx" -#include "OutputInternal.hxx" -#include "OutputAPI.hxx" -#include "OutputError.hxx" -#include "pcm/PcmMix.hxx" -#include "notify.hxx" -#include "FilterInternal.hxx" -#include "filter/ConvertFilterPlugin.hxx" -#include "filter/ReplayGainFilterPlugin.hxx" -#include "PlayerControl.hxx" -#include "MusicPipe.hxx" -#include "MusicChunk.hxx" -#include "system/FatalError.hxx" -#include "util/Error.hxx" -#include "Log.hxx" -#include "Compiler.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static void ao_command_finished(struct audio_output *ao) -{ - assert(ao->command != AO_COMMAND_NONE); - ao->command = AO_COMMAND_NONE; - - ao->mutex.unlock(); - audio_output_client_notify.Signal(); - ao->mutex.lock(); -} - -static bool -ao_enable(struct audio_output *ao) -{ - Error error; - bool success; - - if (ao->really_enabled) - return true; - - ao->mutex.unlock(); - success = ao_plugin_enable(ao, error); - ao->mutex.lock(); - if (!success) { - FormatError(error, - "Failed to enable \"%s\" [%s]", - ao->name, ao->plugin->name); - return false; - } - - ao->really_enabled = true; - return true; -} - -static void -ao_close(struct audio_output *ao, bool drain); - -static void -ao_disable(struct audio_output *ao) -{ - if (ao->open) - ao_close(ao, false); - - if (ao->really_enabled) { - ao->really_enabled = false; - - ao->mutex.unlock(); - ao_plugin_disable(ao); - ao->mutex.lock(); - } -} - -static AudioFormat -ao_filter_open(struct audio_output *ao, AudioFormat &format, - Error &error_r) -{ - assert(format.IsValid()); - - /* the replay_gain filter cannot fail here */ - if (ao->replay_gain_filter != nullptr) - ao->replay_gain_filter->Open(format, error_r); - if (ao->other_replay_gain_filter != nullptr) - ao->other_replay_gain_filter->Open(format, error_r); - - const AudioFormat af = ao->filter->Open(format, error_r); - if (!af.IsDefined()) { - if (ao->replay_gain_filter != nullptr) - ao->replay_gain_filter->Close(); - if (ao->other_replay_gain_filter != nullptr) - ao->other_replay_gain_filter->Close(); - } - - return af; -} - -static void -ao_filter_close(struct audio_output *ao) -{ - if (ao->replay_gain_filter != nullptr) - ao->replay_gain_filter->Close(); - if (ao->other_replay_gain_filter != nullptr) - ao->other_replay_gain_filter->Close(); - - ao->filter->Close(); -} - -static void -ao_open(struct audio_output *ao) -{ - bool success; - Error error; - struct audio_format_string af_string; - - assert(!ao->open); - assert(ao->pipe != nullptr); - assert(ao->chunk == nullptr); - assert(ao->in_audio_format.IsValid()); - - if (ao->fail_timer != nullptr) { - /* this can only happen when this - output thread fails while - audio_output_open() is run in the - player thread */ - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } - - /* enable the device (just in case the last enable has failed) */ - - if (!ao_enable(ao)) - /* still no luck */ - return; - - /* open the filter */ - - const AudioFormat filter_audio_format = - ao_filter_open(ao, ao->in_audio_format, error); - if (!filter_audio_format.IsDefined()) { - FormatError(error, "Failed to open filter for \"%s\" [%s]", - ao->name, ao->plugin->name); - - ao->fail_timer = g_timer_new(); - return; - } - - assert(filter_audio_format.IsValid()); - - ao->out_audio_format = filter_audio_format; - ao->out_audio_format.ApplyMask(ao->config_audio_format); - - ao->mutex.unlock(); - success = ao_plugin_open(ao, ao->out_audio_format, error); - ao->mutex.lock(); - - assert(!ao->open); - - if (!success) { - FormatError(error, "Failed to open \"%s\" [%s]", - ao->name, ao->plugin->name); - - ao_filter_close(ao); - ao->fail_timer = g_timer_new(); - return; - } - - convert_filter_set(ao->convert_filter, ao->out_audio_format); - - ao->open = true; - - FormatDebug(output_domain, - "opened plugin=%s name=\"%s\" audio_format=%s", - ao->plugin->name, ao->name, - audio_format_to_string(ao->out_audio_format, &af_string)); - - if (ao->in_audio_format != ao->out_audio_format) - FormatDebug(output_domain, "converting from %s", - audio_format_to_string(ao->in_audio_format, - &af_string)); -} - -static void -ao_close(struct audio_output *ao, bool drain) -{ - assert(ao->open); - - ao->pipe = nullptr; - - ao->chunk = nullptr; - ao->open = false; - - ao->mutex.unlock(); - - if (drain) - ao_plugin_drain(ao); - else - ao_plugin_cancel(ao); - - ao_plugin_close(ao); - ao_filter_close(ao); - - ao->mutex.lock(); - - FormatDebug(output_domain, "closed plugin=%s name=\"%s\"", - ao->plugin->name, ao->name); -} - -static void -ao_reopen_filter(struct audio_output *ao) -{ - Error error; - - ao_filter_close(ao); - const AudioFormat filter_audio_format = - ao_filter_open(ao, ao->in_audio_format, error); - if (!filter_audio_format.IsDefined()) { - FormatError(error, - "Failed to open filter for \"%s\" [%s]", - ao->name, ao->plugin->name); - - /* this is a little code duplication fro ao_close(), - but we cannot call this function because we must - not call filter_close(ao->filter) again */ - - ao->pipe = nullptr; - - ao->chunk = nullptr; - ao->open = false; - ao->fail_timer = g_timer_new(); - - ao->mutex.unlock(); - ao_plugin_close(ao); - ao->mutex.lock(); - - return; - } - - convert_filter_set(ao->convert_filter, ao->out_audio_format); -} - -static void -ao_reopen(struct audio_output *ao) -{ - if (!ao->config_audio_format.IsFullyDefined()) { - if (ao->open) { - const MusicPipe *mp = ao->pipe; - ao_close(ao, true); - ao->pipe = mp; - } - - /* no audio format is configured: copy in->out, let - the output's open() method determine the effective - out_audio_format */ - ao->out_audio_format = ao->in_audio_format; - ao->out_audio_format.ApplyMask(ao->config_audio_format); - } - - if (ao->open) - /* the audio format has changed, and all filters have - to be reconfigured */ - ao_reopen_filter(ao); - else - ao_open(ao); -} - -/** - * Wait until the output's delay reaches zero. - * - * @return true if playback should be continued, false if a command - * was issued - */ -static bool -ao_wait(struct audio_output *ao) -{ - while (true) { - unsigned delay = ao_plugin_delay(ao); - if (delay == 0) - return true; - - (void)ao->cond.timed_wait(ao->mutex, delay); - - if (ao->command != AO_COMMAND_NONE) - return false; - } -} - -static const void * -ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, - Filter *replay_gain_filter, - unsigned *replay_gain_serial_p, - size_t *length_r) -{ - assert(chunk != nullptr); - assert(!chunk->IsEmpty()); - assert(chunk->CheckFormat(ao->in_audio_format)); - - const void *data = chunk->data; - size_t length = chunk->length; - - (void)ao; - - assert(length % ao->in_audio_format.GetFrameSize() == 0); - - if (length > 0 && replay_gain_filter != nullptr) { - if (chunk->replay_gain_serial != *replay_gain_serial_p) { - replay_gain_filter_set_info(replay_gain_filter, - chunk->replay_gain_serial != 0 - ? &chunk->replay_gain_info - : nullptr); - *replay_gain_serial_p = chunk->replay_gain_serial; - } - - Error error; - data = replay_gain_filter->FilterPCM(data, length, - &length, error); - if (data == nullptr) { - FormatError(error, "\"%s\" [%s] failed to filter", - ao->name, ao->plugin->name); - return nullptr; - } - } - - *length_r = length; - return data; -} - -static const void * -ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, - size_t *length_r) -{ - size_t length; - const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, - &ao->replay_gain_serial, &length); - if (data == nullptr) - return nullptr; - - if (length == 0) { - /* empty chunk, nothing to do */ - *length_r = 0; - return data; - } - - /* cross-fade */ - - if (chunk->other != nullptr) { - size_t other_length; - const void *other_data = - ao_chunk_data(ao, chunk->other, - ao->other_replay_gain_filter, - &ao->other_replay_gain_serial, - &other_length); - if (other_data == nullptr) - return nullptr; - - if (other_length == 0) { - *length_r = 0; - return data; - } - - /* if the "other" chunk is longer, then that trailer - is used as-is, without mixing; it is part of the - "next" song being faded in, and if there's a rest, - it means cross-fading ends here */ - - if (length > other_length) - length = other_length; - - void *dest = ao->cross_fade_buffer.Get(other_length); - memcpy(dest, other_data, other_length); - if (!pcm_mix(dest, data, length, - ao->in_audio_format.format, - 1.0 - chunk->mix_ratio)) { - FormatError(output_domain, - "Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); - return nullptr; - } - - data = dest; - length = other_length; - } - - /* apply filter chain */ - - Error error; - data = ao->filter->FilterPCM(data, length, &length, error); - if (data == nullptr) { - FormatError(error, "\"%s\" [%s] failed to filter", - ao->name, ao->plugin->name); - return nullptr; - } - - *length_r = length; - return data; -} - -static bool -ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) -{ - assert(ao != nullptr); - assert(ao->filter != nullptr); - - if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) { - ao->mutex.unlock(); - ao_plugin_send_tag(ao, chunk->tag); - ao->mutex.lock(); - } - - size_t size; -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - size = 0; -#endif - const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); - if (data == nullptr) { - ao_close(ao, false); - - /* don't automatically reopen this device for 10 - seconds */ - ao->fail_timer = g_timer_new(); - return false; - } - - Error error; - - while (size > 0 && ao->command == AO_COMMAND_NONE) { - size_t nbytes; - - if (!ao_wait(ao)) - break; - - ao->mutex.unlock(); - nbytes = ao_plugin_play(ao, data, size, error); - ao->mutex.lock(); - if (nbytes == 0) { - /* play()==0 means failure */ - FormatError(error, "\"%s\" [%s] failed to play", - ao->name, ao->plugin->name); - - ao_close(ao, false); - - /* don't automatically reopen this device for - 10 seconds */ - assert(ao->fail_timer == nullptr); - ao->fail_timer = g_timer_new(); - - return false; - } - - assert(nbytes <= size); - assert(nbytes % ao->out_audio_format.GetFrameSize() == 0); - - data += nbytes; - size -= nbytes; - } - - return true; -} - -static const struct music_chunk * -ao_next_chunk(struct audio_output *ao) -{ - return ao->chunk != nullptr - /* continue the previous play() call */ - ? ao->chunk->next - /* get the first chunk from the pipe */ - : ao->pipe->Peek(); -} - -/** - * Plays all remaining chunks, until the tail of the pipe has been - * reached (and no more chunks are queued), or until a command is - * received. - * - * @return true if at least one chunk has been available, false if the - * tail of the pipe was already reached - */ -static bool -ao_play(struct audio_output *ao) -{ - bool success; - const struct music_chunk *chunk; - - assert(ao->pipe != nullptr); - - chunk = ao_next_chunk(ao); - if (chunk == nullptr) - /* no chunk available */ - return false; - - ao->chunk_finished = false; - - assert(!ao->in_playback_loop); - ao->in_playback_loop = true; - - while (chunk != nullptr && ao->command == AO_COMMAND_NONE) { - assert(!ao->chunk_finished); - - ao->chunk = chunk; - - success = ao_play_chunk(ao, chunk); - if (!success) { - assert(ao->chunk == nullptr); - break; - } - - assert(ao->chunk == chunk); - chunk = chunk->next; - } - - assert(ao->in_playback_loop); - ao->in_playback_loop = false; - - ao->chunk_finished = true; - - ao->mutex.unlock(); - ao->player_control->LockSignal(); - ao->mutex.lock(); - - return true; -} - -static void ao_pause(struct audio_output *ao) -{ - bool ret; - - ao->mutex.unlock(); - ao_plugin_cancel(ao); - ao->mutex.lock(); - - ao->pause = true; - ao_command_finished(ao); - - do { - if (!ao_wait(ao)) - break; - - ao->mutex.unlock(); - ret = ao_plugin_pause(ao); - ao->mutex.lock(); - - if (!ret) { - ao_close(ao, false); - break; - } - } while (ao->command == AO_COMMAND_NONE); - - ao->pause = false; -} - -static void -audio_output_task(void *arg) -{ - struct audio_output *ao = (struct audio_output *)arg; - - ao->mutex.lock(); - - while (1) { - switch (ao->command) { - case AO_COMMAND_NONE: - break; - - case AO_COMMAND_ENABLE: - ao_enable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_DISABLE: - ao_disable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_OPEN: - ao_open(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_REOPEN: - ao_reopen(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_CLOSE: - assert(ao->open); - assert(ao->pipe != nullptr); - - ao_close(ao, false); - ao_command_finished(ao); - break; - - case AO_COMMAND_PAUSE: - if (!ao->open) { - /* the output has failed after - audio_output_all_pause() has - submitted the PAUSE command; bail - out */ - ao_command_finished(ao); - break; - } - - ao_pause(ao); - /* don't "break" here: this might cause - ao_play() to be called when command==CLOSE - ends the paused state - "continue" checks - the new command first */ - continue; - - case AO_COMMAND_DRAIN: - if (ao->open) { - assert(ao->chunk == nullptr); - assert(ao->pipe->Peek() == nullptr); - - ao->mutex.unlock(); - ao_plugin_drain(ao); - ao->mutex.lock(); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_CANCEL: - ao->chunk = nullptr; - - if (ao->open) { - ao->mutex.unlock(); - ao_plugin_cancel(ao); - ao->mutex.lock(); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_KILL: - ao->chunk = nullptr; - ao_command_finished(ao); - ao->mutex.unlock(); - return; - } - - if (ao->open && ao->allow_play && ao_play(ao)) - /* don't wait for an event if there are more - chunks in the pipe */ - continue; - - if (ao->command == AO_COMMAND_NONE) { - ao->woken_for_play = false; - ao->cond.wait(ao->mutex); - } - } -} - -void audio_output_thread_start(struct audio_output *ao) -{ - assert(ao->command == AO_COMMAND_NONE); - - Error error; - if (!ao->thread.Start(audio_output_task, ao, error)) - FatalError(error); -} diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx deleted file mode 100644 index 1a7932162..000000000 --- a/src/OutputThread.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_THREAD_HXX -#define MPD_OUTPUT_THREAD_HXX - -struct audio_output; - -void audio_output_thread_start(struct audio_output *ao); - -#endif diff --git a/src/Page.cxx b/src/Page.cxx deleted file mode 100644 index 91033a1ec..000000000 --- a/src/Page.cxx +++ /dev/null @@ -1,70 +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 "Page.hxx" - -#include <glib.h> - -#include <new> - -#include <assert.h> -#include <string.h> - -Page * -Page::Create(size_t size) -{ - void *p = g_malloc(sizeof(Page) + size - - sizeof(Page::data)); - return ::new(p) Page(size); -} - -Page * -Page::Copy(const void *data, size_t size) -{ - assert(data != nullptr); - - Page *page = Create(size); - memcpy(page->data, data, size); - return page; -} - -Page * -Page::Concat(const Page &a, const Page &b) -{ - Page *page = Create(a.size + b.size); - - memcpy(page->data, a.data, a.size); - memcpy(page->data + a.size, b.data, b.size); - - return page; -} - -bool -Page::Unref() -{ - bool unused = ref.Decrement(); - - if (unused) { - this->Page::~Page(); - g_free(this); - } - - return unused; -} diff --git a/src/Page.hxx b/src/Page.hxx deleted file mode 100644 index 27c6092cc..000000000 --- a/src/Page.hxx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This is a library which manages reference counted buffers. - */ - -#ifndef MPD_PAGE_HXX -#define MPD_PAGE_HXX - -#include "util/RefCount.hxx" - -#include <algorithm> - -#include <stddef.h> - -/** - * A dynamically allocated buffer which keeps track of its reference - * count. This is useful for passing buffers around, when several - * instances hold references to one buffer. - */ -class Page { - /** - * The number of references to this buffer. This library uses - * atomic functions to access it, i.e. no locks are required. - * As soon as this attribute reaches zero, the buffer is - * freed. - */ - RefCount ref; - -public: - /** - * The size of this buffer in bytes. - */ - const size_t size; - - /** - * Dynamic array containing the buffer data. - */ - unsigned char data[sizeof(long)]; - -protected: - Page(size_t _size):size(_size) {} - ~Page() = default; - - /** - * Allocates a new #Page object, without filling the data - * element. - */ - static Page *Create(size_t size); - -public: - /** - * Creates a new #page object, and copies data from the - * specified buffer. It is initialized with a reference count - * of 1. - * - * @param data the source buffer - * @param size the size of the source buffer - */ - static Page *Copy(const void *data, size_t size); - - /** - * Concatenates two pages to a new page. - * - * @param a the first page - * @param b the second page, which is appended - */ - static Page *Concat(const Page &a, const Page &b); - - /** - * Increases the reference counter. - */ - void Ref() { - ref.Increment(); - } - - /** - * Decreases the reference counter. If it reaches zero, the #page is - * freed. - * - * @return true if the #page has been freed - */ - bool Unref(); -}; - -#endif diff --git a/src/Partition.cxx b/src/Partition.cxx index 55750cfad..de1170557 100644 --- a/src/Partition.cxx +++ b/src/Partition.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,21 +19,29 @@ #include "config.h" #include "Partition.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" +#include "output/MultipleOutputs.hxx" +#include "mixer/Volume.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" + +#ifdef ENABLE_DATABASE void -Partition::DatabaseModified() +Partition::DatabaseModified(const Database &db) { - playlist.DatabaseModified(); + playlist.DatabaseModified(db); } +#endif + void Partition::TagModified() { - Song *song = pc.LockReadTaggedSong(); + DetachedSong *song = pc.LockReadTaggedSong(); if (song != nullptr) { playlist.TagModified(std::move(*song)); - song->Free(); + delete song; } } @@ -42,3 +50,24 @@ Partition::SyncWithPlayer() { playlist.SyncWithPlayer(pc); } + +void +Partition::OnPlayerSync() +{ + GlobalEvents::Emit(GlobalEvents::PLAYLIST); +} + +void +Partition::OnPlayerTagModified() +{ + GlobalEvents::Emit(GlobalEvents::TAG); +} + +void +Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume) +{ + InvalidateHardwareVolume(); + + /* notify clients */ + idle_add(IDLE_MIXER); +} diff --git a/src/Partition.hxx b/src/Partition.hxx index 512ba3bca..4341a9ed3 100644 --- a/src/Partition.hxx +++ b/src/Partition.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 @@ -20,20 +20,27 @@ #ifndef MPD_PARTITION_HXX #define MPD_PARTITION_HXX -#include "Playlist.hxx" +#include "queue/Playlist.hxx" +#include "output/MultipleOutputs.hxx" +#include "mixer/Listener.hxx" #include "PlayerControl.hxx" +#include "PlayerListener.hxx" struct Instance; +class MultipleOutputs; +class SongLoader; /** * A partition of the Music Player Daemon. It is a separate unit with * a playlist, a player, outputs etc. */ -struct Partition { +struct Partition final : private PlayerListener, private MixerListener { Instance &instance; struct playlist playlist; + MultipleOutputs outputs; + PlayerControl pc; Partition(Instance &_instance, @@ -41,21 +48,17 @@ struct Partition { unsigned buffer_chunks, unsigned buffered_before_play) :instance(_instance), playlist(max_length), - pc(buffer_chunks, buffered_before_play) { - } + outputs(*this), + pc(*this, outputs, buffer_chunks, buffered_before_play) {} void ClearQueue() { playlist.Clear(pc); } - PlaylistResult AppendFile(const char *path_utf8, - unsigned *added_id=nullptr) { - return playlist.AppendFile(pc, path_utf8, added_id); - } - - PlaylistResult AppendURI(const char *uri_utf8, - unsigned *added_id=nullptr) { - return playlist.AppendURI(pc, uri_utf8, added_id); + unsigned AppendURI(const SongLoader &loader, + const char *uri_utf8, + Error &error) { + return playlist.AppendURI(pc, loader, uri_utf8, error); } PlaylistResult DeletePosition(unsigned position) { @@ -76,10 +79,14 @@ struct Partition { return playlist.DeleteRange(pc, start, end); } - void DeleteSong(const Song &song) { - playlist.DeleteSong(pc, song); +#ifdef ENABLE_DATABASE + + void DeleteSong(const char *uri) { + playlist.DeleteSong(pc, uri); } +#endif + void Shuffle(unsigned start, unsigned end) { playlist.Shuffle(pc, start, end); } @@ -166,11 +173,13 @@ struct Partition { playlist.SetConsume(new_value); } +#ifdef ENABLE_DATABASE /** * The database has been modified. Propagate the change to * all subsystems. */ - void DatabaseModified(); + void DatabaseModified(const Database &db); +#endif /** * A tag in the play queue has been modified by the player @@ -182,6 +191,14 @@ struct Partition { * Synchronize the player with the play queue. */ void SyncWithPlayer(); + +private: + /* virtual methods from class PlayerListener */ + virtual void OnPlayerSync() override; + virtual void OnPlayerTagModified() override; + + /* virtual methods from class MixerListener */ + virtual void OnMixerVolumeChanged(Mixer &mixer, int volume) override; }; #endif diff --git a/src/Permission.cxx b/src/Permission.cxx index a35c80e94..d6c267ab7 100644 --- a/src/Permission.cxx +++ b/src/Permission.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,9 @@ #include "config.h" #include "Permission.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "system/FatalError.hxx" #include <algorithm> @@ -92,7 +92,7 @@ void initPermissions(void) permission_default = PERMISSION_READ | PERMISSION_ADD | PERMISSION_CONTROL | PERMISSION_ADMIN; - param = config_get_next_param(CONF_PASSWORD, NULL); + param = config_get_param(CONF_PASSWORD); if (param) { permission_default = 0; @@ -115,7 +115,7 @@ void initPermissions(void) permission_passwords.insert(std::make_pair(std::move(password), permission)); - } while ((param = config_get_next_param(CONF_PASSWORD, param))); + } while ((param = param->next) != nullptr); } param = config_get_param(CONF_DEFAULT_PERMS); diff --git a/src/Permission.hxx b/src/Permission.hxx index 228e9ee20..60761c696 100644 --- a/src/Permission.hxx +++ b/src/Permission.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/PlayerControl.cxx b/src/PlayerControl.cxx index f180874b0..244b64f5c 100644 --- a/src/PlayerControl.cxx +++ b/src/PlayerControl.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 @@ -20,16 +20,18 @@ #include "config.h" #include "PlayerControl.hxx" #include "Idle.hxx" -#include "Song.hxx" -#include "DecoderControl.hxx" +#include "DetachedSong.hxx" -#include <cmath> +#include <algorithm> #include <assert.h> -PlayerControl::PlayerControl(unsigned _buffer_chunks, +PlayerControl::PlayerControl(PlayerListener &_listener, + MultipleOutputs &_outputs, + unsigned _buffer_chunks, unsigned _buffered_before_play) - :buffer_chunks(_buffer_chunks), + :listener(_listener), outputs(_outputs), + buffer_chunks(_buffer_chunks), buffered_before_play(_buffered_before_play), command(PlayerCommand::NONE), state(PlayerState::STOP), @@ -43,15 +45,12 @@ PlayerControl::PlayerControl(unsigned _buffer_chunks, PlayerControl::~PlayerControl() { - if (next_song != nullptr) - next_song->Free(); - - if (tagged_song != nullptr) - tagged_song->Free(); + delete next_song; + delete tagged_song; } void -PlayerControl::Play(Song *song) +PlayerControl::Play(DetachedSong *song) { assert(song != nullptr); @@ -196,26 +195,23 @@ PlayerControl::ClearError() } void -PlayerControl::LockSetTaggedSong(const Song &song) +PlayerControl::LockSetTaggedSong(const DetachedSong &song) { Lock(); - if (tagged_song != nullptr) - tagged_song->Free(); - tagged_song = song.DupDetached(); + delete tagged_song; + tagged_song = new DetachedSong(song); Unlock(); } void PlayerControl::ClearTaggedSong() { - if (tagged_song != nullptr) { - tagged_song->Free(); - tagged_song = nullptr; - } + delete tagged_song; + tagged_song = nullptr; } void -PlayerControl::EnqueueSong(Song *song) +PlayerControl::EnqueueSong(DetachedSong *song) { assert(song != nullptr); @@ -225,15 +221,13 @@ PlayerControl::EnqueueSong(Song *song) } bool -PlayerControl::Seek(Song *song, float seek_time) +PlayerControl::Seek(DetachedSong *song, float seek_time) { assert(song != nullptr); Lock(); - if (next_song != nullptr) - next_song->Free(); - + delete next_song; next_song = song; seek_where = seek_time; SynchronousCommand(PlayerCommand::SEEK); diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx index 61bb408d2..b60227d23 100644 --- a/src/PlayerControl.hxx +++ b/src/PlayerControl.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 @@ -29,7 +29,9 @@ #include <stdint.h> -struct Song; +class PlayerListener; +class MultipleOutputs; +class DetachedSong; enum class PlayerState : uint8_t { STOP, @@ -46,7 +48,7 @@ enum class PlayerCommand : uint8_t { CLOSE_AUDIO, /** - * At least one audio_output.enabled flag has been modified; + * At least one AudioOutput.enabled flag has been modified; * commit those changes to the output threads. */ UPDATE_AUDIO, @@ -91,6 +93,10 @@ struct player_status { }; struct PlayerControl { + PlayerListener &listener; + + MultipleOutputs &outputs; + unsigned buffer_chunks; unsigned int buffered_before_play; @@ -131,16 +137,16 @@ struct PlayerControl { Error error; /** - * A copy of the current #Song after its tags have been - * updated by the decoder (for example, a radio stream that - * has sent a new tag after switching to the next song). This - * shall be used by the GlobalEvents::TAG handler to update - * the current #Song in the queue. + * A copy of the current #DetachedSong after its tags have + * been updated by the decoder (for example, a radio stream + * that has sent a new tag after switching to the next song). + * This shall be used by PlayerListener::OnPlayerTagModified() + * to update the current #DetachedSong in the queue. * * Protected by #mutex. Set by the PlayerThread and consumed * by the main thread. */ - Song *tagged_song; + DetachedSong *tagged_song; uint16_t bit_rate; AudioFormat audio_format; @@ -153,7 +159,7 @@ struct PlayerControl { * This is a duplicate, and must be freed when this attribute * is cleared. */ - Song *next_song; + DetachedSong *next_song; double seek_where; @@ -170,7 +176,9 @@ struct PlayerControl { */ bool border_pause; - PlayerControl(unsigned buffer_chunks, + PlayerControl(PlayerListener &_listener, + MultipleOutputs &_outputs, + unsigned buffer_chunks, unsigned buffered_before_play); ~PlayerControl(); @@ -299,7 +307,7 @@ public: * @param song the song to be queued; the given instance will * be owned and freed by the player */ - void Play(Song *song); + void Play(DetachedSong *song); /** * see PlayerCommand::CANCEL @@ -371,9 +379,9 @@ public: /** * Set the #tagged_song attribute to a newly allocated copy of - * the given #Song. Locks and unlocks the object. + * the given #DetachedSong. Locks and unlocks the object. */ - void LockSetTaggedSong(const Song &song); + void LockSetTaggedSong(const DetachedSong &song); void ClearTaggedSong(); @@ -382,8 +390,8 @@ public: * * Caller must lock the object. */ - Song *ReadTaggedSong() { - Song *result = tagged_song; + DetachedSong *ReadTaggedSong() { + DetachedSong *result = tagged_song; tagged_song = nullptr; return result; } @@ -391,9 +399,9 @@ public: /** * Like ReadTaggedSong(), but locks and unlocks the object. */ - Song *LockReadTaggedSong() { + DetachedSong *LockReadTaggedSong() { Lock(); - Song *result = ReadTaggedSong(); + DetachedSong *result = ReadTaggedSong(); Unlock(); return result; } @@ -403,7 +411,7 @@ public: void UpdateAudio(); private: - void EnqueueSongLocked(Song *song) { + void EnqueueSongLocked(DetachedSong *song) { assert(song != nullptr); assert(next_song == nullptr); @@ -416,7 +424,7 @@ public: * @param song the song to be queued; the given instance will be owned * and freed by the player */ - void EnqueueSong(Song *song); + void EnqueueSong(DetachedSong *song); /** * Makes the player thread seek the specified song to a position. @@ -426,7 +434,7 @@ public: * @return true on success, false on failure (e.g. if MPD isn't * playing currently) */ - bool Seek(Song *song, float seek_time); + bool Seek(DetachedSong *song, float seek_time); void SetCrossFade(float cross_fade_seconds); diff --git a/src/PlayerListener.hxx b/src/PlayerListener.hxx new file mode 100644 index 000000000..06f00a4f5 --- /dev/null +++ b/src/PlayerListener.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_PLAYER_LISTENER_HXX +#define MPD_PLAYER_LISTENER_HXX + +class PlayerListener { +public: + /** + * Must call playlist_sync(). + */ + virtual void OnPlayerSync() = 0; + + /** + * The current song's tag has changed. + */ + virtual void OnPlayerTagModified() = 0; +}; + +#endif diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx index 356559e37..00f8b5f78 100644 --- a/src/PlayerThread.cxx +++ b/src/PlayerThread.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,21 +19,21 @@ #include "config.h" #include "PlayerThread.hxx" -#include "DecoderThread.hxx" -#include "DecoderControl.hxx" +#include "PlayerListener.hxx" +#include "decoder/DecoderThread.hxx" +#include "decoder/DecoderControl.hxx" #include "MusicPipe.hxx" #include "MusicBuffer.hxx" #include "MusicChunk.hxx" -#include "Song.hxx" -#include "Main.hxx" +#include "DetachedSong.hxx" #include "system/FatalError.hxx" #include "CrossFade.hxx" #include "PlayerControl.hxx" -#include "OutputAll.hxx" +#include "output/MultipleOutputs.hxx" #include "tag/Tag.hxx" #include "Idle.hxx" -#include "GlobalEvents.hxx" #include "util/Domain.hxx" +#include "thread/Name.hxx" #include "Log.hxx" #include <string.h> @@ -93,7 +93,7 @@ class Player { /** * the song currently being played */ - Song *song; + DetachedSong *song; /** * is cross fading enabled? @@ -125,7 +125,7 @@ class Player { /** * The time stamp of the chunk most recently sent to the * output thread. This attribute is only used if - * audio_output_all_get_elapsed_time() didn't return a usable + * MultipleOutputs::GetElapsedTime() didn't return a usable * value; the output thread can estimate the elapsed time more * precisely. */ @@ -228,8 +228,8 @@ private: bool WaitForDecoder(); /** - * Wrapper for audio_output_all_open(). Upon failure, it pauses the - * player. + * Wrapper for MultipleOutputs::Open(). Upon failure, it + * pauses the player. * * @return true on success */ @@ -291,12 +291,12 @@ Player::StartDecoder(MusicPipe &_pipe) assert(queued || pc.command == PlayerCommand::SEEK); assert(pc.next_song != nullptr); - unsigned start_ms = pc.next_song->start_ms; + unsigned start_ms = pc.next_song->GetStartMS(); if (pc.command == PlayerCommand::SEEK) start_ms += (unsigned)(pc.seek_where * 1000); - dc.Start(pc.next_song->DupDetached(), - start_ms, pc.next_song->end_ms, + dc.Start(new DetachedSong(*pc.next_song), + start_ms, pc.next_song->GetEndMS(), buffer, _pipe); } @@ -330,7 +330,7 @@ Player::WaitForDecoder() if (error.IsDefined()) { pc.SetError(PlayerError::DECODER, std::move(error)); - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; pc.Unlock(); @@ -340,9 +340,7 @@ Player::WaitForDecoder() pc.ClearTaggedSong(); - if (song != nullptr) - song->Free(); - + delete song; song = pc.next_song; elapsed_time = 0.0; @@ -361,7 +359,7 @@ Player::WaitForDecoder() pc.Unlock(); /* call syncPlaylistWithQueue() in the main thread */ - GlobalEvents::Emit(GlobalEvents::PLAYLIST); + pc.listener.OnPlayerSync(); return true; } @@ -371,19 +369,20 @@ Player::WaitForDecoder() * indicated by the decoder plugin. */ static double -real_song_duration(const Song *song, double decoder_duration) +real_song_duration(const DetachedSong &song, double decoder_duration) { - assert(song != nullptr); - if (decoder_duration <= 0.0) /* the decoder plugin didn't provide information; fall back to Song::GetDuration() */ - return song->GetDuration(); + return song.GetDuration(); - if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) - return (song->end_ms - song->start_ms) / 1000.0; + const unsigned start_ms = song.GetStartMS(); + const unsigned end_ms = song.GetEndMS(); - return decoder_duration - song->start_ms / 1000.0; + if (end_ms > 0 && end_ms / 1000.0 < decoder_duration) + return (end_ms - start_ms) / 1000.0; + + return decoder_duration - start_ms / 1000.0; } bool @@ -394,7 +393,7 @@ Player::OpenOutput() pc.state == PlayerState::PAUSE); Error error; - if (audio_output_all_open(play_audio_format, buffer, error)) { + if (pc.outputs.Open(play_audio_format, buffer, error)) { output_open = true; paused = false; @@ -445,13 +444,13 @@ Player::CheckDecoderStartup() pc.Unlock(); if (output_open && - !audio_output_all_wait(pc, 1)) + !pc.outputs.Wait(pc, 1)) /* the output devices havn't finished playing all chunks yet - wait for that */ return true; pc.Lock(); - pc.total_time = real_song_duration(dc.song, dc.total_time); + pc.total_time = real_song_duration(*dc.song, dc.total_time); pc.audio_format = dc.in_audio_format; pc.Unlock(); @@ -461,10 +460,10 @@ Player::CheckDecoderStartup() decoder_starting = false; if (!paused && !OpenOutput()) { - const auto uri = dc.song->GetURI(); FormatError(player_domain, "problems opening audio device " - "while playing \"%s\"", uri.c_str()); + "while playing \"%s\"", + dc.song->GetURI()); return true; } @@ -485,7 +484,7 @@ Player::SendSilence() assert(output_open); assert(play_audio_format.IsDefined()); - struct music_chunk *chunk = buffer.Allocate(); + MusicChunk *chunk = buffer.Allocate(); if (chunk == nullptr) { LogError(player_domain, "Failed to allocate silence buffer"); return false; @@ -505,7 +504,7 @@ Player::SendSilence() memset(chunk->data, 0, chunk->length); Error error; - if (!audio_output_all_play(chunk, error)) { + if (!pc.outputs.Play(chunk, error)) { LogError(error); buffer.Return(chunk); return false; @@ -519,7 +518,7 @@ Player::SeekDecoder() { assert(pc.next_song != nullptr); - const unsigned start_ms = pc.next_song->start_ms; + const unsigned start_ms = pc.next_song->GetStartMS(); if (!dc.LockIsCurrentSong(*pc.next_song)) { /* the decoder is already decoding the "next" song - @@ -545,7 +544,7 @@ Player::SeekDecoder() ClearAndReplacePipe(dc.pipe); } - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; queued = false; } @@ -583,7 +582,7 @@ Player::SeekDecoder() /* re-fill the buffer after seeking */ buffering = true; - audio_output_all_cancel(); + pc.outputs.Cancel(); return true; } @@ -600,7 +599,7 @@ Player::ProcessCommand() case PlayerCommand::UPDATE_AUDIO: pc.Unlock(); - audio_output_all_enable_disable(); + pc.outputs.EnableDisable(); pc.Lock(); pc.CommandFinished(); break; @@ -619,7 +618,7 @@ Player::ProcessCommand() paused = !paused; if (paused) { - audio_output_all_pause(); + pc.outputs.Pause(); pc.Lock(); pc.state = PlayerState::PAUSE; @@ -661,7 +660,7 @@ Player::ProcessCommand() pc.Lock(); } - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; queued = false; pc.CommandFinished(); @@ -670,11 +669,11 @@ Player::ProcessCommand() case PlayerCommand::REFRESH: if (output_open && !paused) { pc.Unlock(); - audio_output_all_check(); + pc.outputs.Check(); pc.Lock(); } - pc.elapsed_time = audio_output_all_get_elapsed_time(); + pc.elapsed_time = pc.outputs.GetElapsedTime(); if (pc.elapsed_time < 0.0) pc.elapsed_time = elapsed_time; @@ -684,23 +683,20 @@ Player::ProcessCommand() } static void -update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag) +update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag) { - if (song->IsFile()) + if (song.IsFile()) /* don't update tags of local files, only remote streams may change tags dynamically */ return; - Tag *old_tag = song->tag; - song->tag = new Tag(new_tag); - - delete old_tag; + song.SetTag(new_tag); - pc.LockSetTaggedSong(*song); + pc.LockSetTaggedSong(song); /* the main thread will update the playlist version when he receives this event */ - GlobalEvents::Emit(GlobalEvents::TAG); + pc.listener.OnPlayerTagModified(); /* notify all clients that the tag of the current song has changed */ @@ -708,7 +704,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag) } /** - * Plays a #music_chunk object (after applying software volume). If + * Plays a #MusicChunk object (after applying software volume). If * it contains a (stream) tag, copy it to the current song, so MPD's * playlist reflects the new stream tag. * @@ -716,7 +712,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag) */ static bool play_chunk(PlayerControl &pc, - Song *song, struct music_chunk *chunk, + DetachedSong &song, MusicChunk *chunk, MusicBuffer &buffer, const AudioFormat format, Error &error) @@ -737,7 +733,7 @@ play_chunk(PlayerControl &pc, /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk, error)) + if (!pc.outputs.Play(chunk, error)) return false; pc.total_play_time += (double)chunk->length / @@ -748,17 +744,17 @@ play_chunk(PlayerControl &pc, inline bool Player::PlayNextChunk() { - if (!audio_output_all_wait(pc, 64)) + if (!pc.outputs.Wait(pc, 64)) /* the output pipe is still large enough, don't send another chunk */ return true; unsigned cross_fade_position; - struct music_chunk *chunk = nullptr; + MusicChunk *chunk = nullptr; if (xfade_state == CrossFadeState::ENABLED && IsDecoderAtNextSong() && (cross_fade_position = pipe->GetSize()) <= cross_fade_chunks) { /* perform cross fade */ - music_chunk *other_chunk = dc.pipe->Shift(); + MusicChunk *other_chunk = dc.pipe->Shift(); if (!cross_fading) { /* beginning of the cross fade - adjust @@ -790,7 +786,7 @@ Player::PlayNextChunk() } if (other_chunk->IsEmpty()) { - /* the "other" chunk was a music_chunk + /* the "other" chunk was a MusicChunk which had only a tag, but no music data - we cannot cross-fade that; but since this happens only at the @@ -839,7 +835,7 @@ Player::PlayNextChunk() /* play the current chunk */ Error error; - if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) { + if (!play_chunk(pc, *song, chunk, buffer, play_audio_format, error)) { LogError(error); buffer.Return(chunk); @@ -883,14 +879,11 @@ Player::SongBorder() { xfade_state = CrossFadeState::UNKNOWN; - { - const auto uri = song->GetURI(); - FormatDefault(player_domain, "played \"%s\"", uri.c_str()); - } + FormatDefault(player_domain, "played \"%s\"", song->GetURI()); ReplacePipe(dc.pipe); - audio_output_all_song_border(); + pc.outputs.SongBorder(); if (!WaitForDecoder()) return false; @@ -940,7 +933,7 @@ Player::Run() pc.command == PlayerCommand::EXIT || pc.command == PlayerCommand::CLOSE_AUDIO) { pc.Unlock(); - audio_output_all_cancel(); + pc.outputs.Cancel(); break; } @@ -956,7 +949,7 @@ Player::Run() /* not enough decoded buffer space yet */ if (!paused && output_open && - audio_output_all_check() < 4 && + pc.outputs.Check() < 4 && !SendSilence()) break; @@ -1036,7 +1029,7 @@ Player::Run() to the audio output */ PlayNextChunk(); - } else if (audio_output_all_check() > 0) { + } else if (pc.outputs.Check() > 0) { /* not enough data from decoder, but the output thread is still busy, so it's okay */ @@ -1061,7 +1054,7 @@ Player::Run() if (pipe->IsEmpty()) { /* wait for the hardware to finish playback */ - audio_output_all_drain(); + pc.outputs.Drain(); break; } } else if (output_open) { @@ -1082,9 +1075,8 @@ Player::Run() delete cross_fade_tag; if (song != nullptr) { - const auto uri = song->GetURI(); - FormatDefault(player_domain, "played \"%s\"", uri.c_str()); - song->Free(); + FormatDefault(player_domain, "played \"%s\"", song->GetURI()); + delete song; } pc.Lock(); @@ -1093,7 +1085,7 @@ Player::Run() if (queued) { assert(pc.next_song != nullptr); - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; } @@ -1115,6 +1107,8 @@ player_task(void *arg) { PlayerControl &pc = *(PlayerControl *)arg; + SetThreadName("player"); + DecoderControl dc(pc.mutex, pc.cond); decoder_thread_start(dc); @@ -1130,22 +1124,20 @@ player_task(void *arg) pc.Unlock(); do_play(pc, dc, buffer); - GlobalEvents::Emit(GlobalEvents::PLAYLIST); + pc.listener.OnPlayerSync(); pc.Lock(); break; case PlayerCommand::STOP: pc.Unlock(); - audio_output_all_cancel(); + pc.outputs.Cancel(); pc.Lock(); /* fall through */ case PlayerCommand::PAUSE: - if (pc.next_song != nullptr) { - pc.next_song->Free(); - pc.next_song = nullptr; - } + delete pc.next_song; + pc.next_song = nullptr; pc.CommandFinished(); break; @@ -1153,7 +1145,7 @@ player_task(void *arg) case PlayerCommand::CLOSE_AUDIO: pc.Unlock(); - audio_output_all_release(); + pc.outputs.Release(); pc.Lock(); pc.CommandFinished(); @@ -1164,7 +1156,7 @@ player_task(void *arg) case PlayerCommand::UPDATE_AUDIO: pc.Unlock(); - audio_output_all_enable_disable(); + pc.outputs.EnableDisable(); pc.Lock(); pc.CommandFinished(); break; @@ -1174,16 +1166,14 @@ player_task(void *arg) dc.Quit(); - audio_output_all_close(); + pc.outputs.Close(); player_command_finished(pc); return; case PlayerCommand::CANCEL: - if (pc.next_song != nullptr) { - pc.next_song->Free(); - pc.next_song = nullptr; - } + delete pc.next_song; + pc.next_song = nullptr; pc.CommandFinished(); break; diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx index efdcf05ca..2fb0b1430 100644 --- a/src/PlayerThread.hxx +++ b/src/PlayerThread.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 @@ -21,7 +21,7 @@ * * The player thread controls the playback. It acts as a bridge * between the decoder thread and the output thread(s): it receives - * #music_chunk objects from the decoder, optionally mixes them + * #MusicChunk objects from the decoder, optionally mixes them * (cross-fading), applies software volume, and sends them to the * audio outputs via audio_output_all_play(). * @@ -31,7 +31,7 @@ * * The player thread itself does not do any I/O. It synchronizes with * other threads via #GMutex and #GCond objects, and passes - * #music_chunk instances around in #MusicPipe objects. + * #MusicChunk instances around in #MusicPipe objects. */ #ifndef MPD_PLAYER_THREAD_HXX diff --git a/src/Playlist.cxx b/src/Playlist.cxx deleted file mode 100644 index 526f35298..000000000 --- a/src/Playlist.cxx +++ /dev/null @@ -1,349 +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 "Playlist.hxx" -#include "PlaylistError.hxx" -#include "PlayerControl.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "Idle.hxx" -#include "Log.hxx" - -#include <assert.h> - -void -playlist::TagModified(Song &&song) -{ - if (!playing || song.tag == nullptr) - return; - - assert(current >= 0); - - Song ¤t_song = queue.GetOrder(current); - if (SongEquals(song, current_song)) - current_song.ReplaceTag(std::move(*song.tag)); - - queue.ModifyAtOrder(current); - queue.IncrementVersion(); - idle_add(IDLE_PLAYLIST); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(playlist &playlist, PlayerControl &pc, - unsigned order) -{ - assert(playlist.queue.IsValidOrder(order)); - - playlist.queued = order; - - Song *song = playlist.queue.GetOrder(order).DupDetached(); - - { - const auto uri = song->GetURI(); - FormatDebug(playlist_domain, "queue song %i:\"%s\"", - playlist.queued, uri.c_str()); - } - - pc.EnqueueSong(song); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(playlist &playlist, PlayerControl &pc) -{ - assert(pc.next_song == nullptr); - assert(playlist.queued >= -1); - - /* queued song has started: copy queued to current, - and notify the clients */ - - int current = playlist.current; - playlist.current = playlist.queued; - playlist.queued = -1; - - if(playlist.queue.consume) - playlist.DeleteOrder(pc, current); - - idle_add(IDLE_PLAYER); -} - -const Song * -playlist::GetQueuedSong() const -{ - return playing && queued >= 0 - ? &queue.GetOrder(queued) - : nullptr; -} - -void -playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev) -{ - if (!playing) - return; - - if (prev == nullptr && bulk_edit) - /* postponed until CommitBulk() to avoid always - queueing the first song that is being added (in - random mode) */ - return; - - assert(!queue.IsEmpty()); - assert((queued < 0) == (prev == nullptr)); - - const int next_order = current >= 0 - ? queue.GetNextOrder(current) - : 0; - - if (next_order == 0 && queue.random && !queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - const unsigned current_position = - queue.OrderToPosition(current); - - queue.ShuffleOrder(); - - /* make sure that the current still points to - the current song, after the song order has been - shuffled */ - current = queue.PositionToOrder(current_position); - } - - const Song *const next_song = next_order >= 0 - ? &queue.GetOrder(next_order) - : nullptr; - - if (prev != nullptr && next_song != prev) { - /* clear the currently queued song */ - pc.Cancel(); - queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(*this, pc, next_order); - else - queued = next_order; - } -} - -void -playlist::PlayOrder(PlayerControl &pc, int order) -{ - playing = true; - queued = -1; - - Song *song = queue.GetOrder(order).DupDetached(); - - { - const auto uri = song->GetURI(); - FormatDebug(playlist_domain, "play %i:\"%s\"", - order, uri.c_str()); - } - - pc.Play(song); - current = order; -} - -static void -playlist_resume_playback(playlist &playlist, PlayerControl &pc); - -void -playlist::SyncWithPlayer(PlayerControl &pc) -{ - if (!playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - pc.Lock(); - const PlayerState pc_state = pc.GetState(); - const Song *pc_next_song = pc.next_song; - pc.Unlock(); - - if (pc_state == PlayerState::STOP) - /* the player thread has stopped: check if playback - should be restarted with the next song. That can - happen if the playlist isn't filling the queue fast - enough */ - playlist_resume_playback(*this, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == nullptr && queued != -1) - playlist_song_started(*this, pc); - - pc.Lock(); - pc_next_song = pc.next_song; - pc.Unlock(); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == nullptr && queued < 0) - UpdateQueuedSong(pc, nullptr); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(playlist &playlist, PlayerControl &pc) -{ - assert(playlist.playing); - assert(pc.GetState() == PlayerState::STOP); - - const auto error = pc.GetErrorType(); - if (error == PlayerError::NONE) - playlist.error_count = 0; - else - ++playlist.error_count; - - if ((playlist.stop_on_error && error != PlayerError::NONE) || - error == PlayerError::OUTPUT || - playlist.error_count >= playlist.queue.GetLength()) - /* too many errors, or critical error: stop - playback */ - playlist.Stop(pc); - else - /* continue playback at the next song */ - playlist.PlayNext(pc); -} - -void -playlist::SetRepeat(PlayerControl &pc, bool status) -{ - if (status == queue.repeat) - return; - - queue.repeat = status; - - pc.SetBorderPause(queue.single && !queue.repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - UpdateQueuedSong(pc, GetQueuedSong()); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(playlist &playlist) -{ - if (playlist.current >= 0) - /* update playlist.current, order==position now */ - playlist.current = playlist.queue.OrderToPosition(playlist.current); - - playlist.queue.RestoreOrder(); -} - -void -playlist::SetSingle(PlayerControl &pc, bool status) -{ - if (status == queue.single) - return; - - queue.single = status; - - pc.SetBorderPause(queue.single && !queue.repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - UpdateQueuedSong(pc, GetQueuedSong()); - - idle_add(IDLE_OPTIONS); -} - -void -playlist::SetConsume(bool status) -{ - if (status == queue.consume) - return; - - queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist::SetRandom(PlayerControl &pc, bool status) -{ - if (status == queue.random) - return; - - const Song *const queued_song = GetQueuedSong(); - - queue.random = status; - - if (queue.random) { - /* shuffle the queue order, but preserve current */ - - const int current_position = playing - ? GetCurrentPosition() - : -1; - - queue.ShuffleOrder(); - - if (current_position >= 0) { - /* make sure the current song is the first in - the order list, so the whole rest of the - playlist is played after that */ - unsigned current_order = - queue.PositionToOrder(current_position); - queue.SwapOrders(0, current_order); - current = 0; - } else - current = -1; - } else - playlist_order(*this); - - UpdateQueuedSong(pc, queued_song); - - idle_add(IDLE_OPTIONS); -} - -int -playlist::GetCurrentPosition() const -{ - return current >= 0 - ? queue.OrderToPosition(current) - : -1; -} - -int -playlist::GetNextPosition() const -{ - if (current < 0) - return -1; - - if (queue.single && queue.repeat) - return queue.OrderToPosition(current); - else if (queue.IsValidOrder(current + 1)) - return queue.OrderToPosition(current + 1); - else if (queue.repeat) - return queue.OrderToPosition(0); - - return -1; -} diff --git a/src/Playlist.hxx b/src/Playlist.hxx deleted file mode 100644 index b660ecb40..000000000 --- a/src/Playlist.hxx +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_HXX -#define MPD_PLAYLIST_HXX - -#include "Queue.hxx" -#include "PlaylistError.hxx" - -struct PlayerControl; -struct Song; - -struct playlist { - /** - * The song queue - it contains the "real" playlist. - */ - struct Queue queue; - - /** - * This value is true if the player is currently playing (or - * should be playing). - */ - bool playing; - - /** - * If true, then any error is fatal; if false, MPD will - * attempt to play the next song on non-fatal errors. During - * seeking, this flag is set. - */ - bool stop_on_error; - - /** - * If true, then a bulk edit has been initiated by - * BeginBulk(), and UpdateQueuedSong() and OnModified() will - * be postponed until CommitBulk() - */ - bool bulk_edit; - - /** - * Has the queue been modified during bulk edit mode? - */ - bool bulk_modified; - - /** - * Number of errors since playback was started. If this - * number exceeds the length of the playlist, MPD gives up, - * because all songs have been tried. - */ - unsigned error_count; - - /** - * The "current song pointer". This is the song which is - * played when we get the "play" command. It is also the song - * which is currently being played. - */ - int current; - - /** - * The "next" song to be played, when the current one - * finishes. The decoder thread may start decoding and - * buffering it, while the "current" song is still playing. - * - * This variable is only valid if #playing is true. - */ - int queued; - - playlist(unsigned max_length) - :queue(max_length), playing(false), - bulk_edit(false), - current(-1), queued(-1) { - } - - ~playlist() { - } - - uint32_t GetVersion() const { - return queue.version; - } - - unsigned GetLength() const { - return queue.GetLength(); - } - - unsigned PositionToId(unsigned position) const { - return queue.PositionToId(position); - } - - gcc_pure - int GetCurrentPosition() const; - - gcc_pure - int GetNextPosition() const; - - /** - * Returns the song object which is currently queued. Returns - * none if there is none (yet?) or if MPD isn't playing. - */ - gcc_pure - const Song *GetQueuedSong() const; - - /** - * This is the "PLAYLIST" event handler. It is invoked by the - * player thread whenever it requests a new queued song, or - * when it exits. - */ - void SyncWithPlayer(PlayerControl &pc); - -protected: - /** - * Called by all editing methods after a modification. - * Updates the queue version and emits #IDLE_PLAYLIST. - */ - void OnModified(); - - /** - * Updates the "queued song". Calculates the next song - * according to the current one (if MPD isn't playing, it - * takes the first song), and queues this song. Clears the - * old queued song if there was one. - * - * @param prev the song which was previously queued, as - * determined by playlist_get_queued_song() - */ - void UpdateQueuedSong(PlayerControl &pc, const Song *prev); - -public: - void BeginBulk(); - void CommitBulk(PlayerControl &pc); - - void Clear(PlayerControl &pc); - - /** - * A tag in the play queue has been modified by the player - * thread. Apply the given song's tag to the current song if - * the song matches. - */ - void TagModified(Song &&song); - - /** - * The database has been modified. Pull all updates. - */ - void DatabaseModified(); - - PlaylistResult AppendSong(PlayerControl &pc, - Song *song, - unsigned *added_id=nullptr); - - /** - * Appends a local file (outside the music database) to the - * playlist. - * - * Note: the caller is responsible for checking permissions. - */ - PlaylistResult AppendFile(PlayerControl &pc, - const char *path_utf8, - unsigned *added_id=nullptr); - - PlaylistResult AppendURI(PlayerControl &pc, - const char *uri_utf8, - unsigned *added_id=nullptr); - -protected: - void DeleteInternal(PlayerControl &pc, - unsigned song, const Song **queued_p); - -public: - PlaylistResult DeletePosition(PlayerControl &pc, - unsigned position); - - PlaylistResult DeleteOrder(PlayerControl &pc, - unsigned order) { - return DeletePosition(pc, queue.OrderToPosition(order)); - } - - PlaylistResult DeleteId(PlayerControl &pc, unsigned id); - - /** - * Deletes a range of songs from the playlist. - * - * @param start the position of the first song to delete - * @param end the position after the last song to delete - */ - PlaylistResult DeleteRange(PlayerControl &pc, - unsigned start, unsigned end); - - void DeleteSong(PlayerControl &pc, const Song &song); - - void Shuffle(PlayerControl &pc, unsigned start, unsigned end); - - PlaylistResult MoveRange(PlayerControl &pc, - unsigned start, unsigned end, int to); - - PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to); - - PlaylistResult SwapPositions(PlayerControl &pc, - unsigned song1, unsigned song2); - - PlaylistResult SwapIds(PlayerControl &pc, - unsigned id1, unsigned id2); - - PlaylistResult SetPriorityRange(PlayerControl &pc, - unsigned start_position, - unsigned end_position, - uint8_t priority); - - PlaylistResult SetPriorityId(PlayerControl &pc, - unsigned song_id, uint8_t priority); - - void Stop(PlayerControl &pc); - - PlaylistResult PlayPosition(PlayerControl &pc, int position); - - void PlayOrder(PlayerControl &pc, int order); - - PlaylistResult PlayId(PlayerControl &pc, int id); - - void PlayNext(PlayerControl &pc); - - void PlayPrevious(PlayerControl &pc); - - PlaylistResult SeekSongPosition(PlayerControl &pc, - unsigned song_position, - float seek_time); - - PlaylistResult SeekSongId(PlayerControl &pc, - unsigned song_id, float seek_time); - - /** - * Seek within the current song. Fails if MPD is not currently - * playing. - * - * @param time the time in seconds - * @param relative if true, then the specified time is relative to the - * current position - */ - PlaylistResult SeekCurrent(PlayerControl &pc, - float seek_time, bool relative); - - bool GetRepeat() const { - return queue.repeat; - } - - void SetRepeat(PlayerControl &pc, bool new_value); - - bool GetRandom() const { - return queue.random; - } - - void SetRandom(PlayerControl &pc, bool new_value); - - bool GetSingle() const { - return queue.single; - } - - void SetSingle(PlayerControl &pc, bool new_value); - - bool GetConsume() const { - return queue.consume; - } - - void SetConsume(bool new_value); -}; - -#endif diff --git a/src/PlaylistAny.cxx b/src/PlaylistAny.cxx deleted file mode 100644 index 52304700f..000000000 --- a/src/PlaylistAny.cxx +++ /dev/null @@ -1,70 +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 "PlaylistAny.hxx" -#include "PlaylistMapper.hxx" -#include "PlaylistRegistry.hxx" -#include "PlaylistError.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "InputStream.hxx" -#include "Log.hxx" - -#include <assert.h> - -static SongEnumerator * -playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - assert(uri_has_scheme(uri)); - - SongEnumerator *playlist = playlist_list_open_uri(uri, mutex, cond); - if (playlist != nullptr) { - *is_r = nullptr; - return playlist; - } - - Error error; - InputStream *is = InputStream::Open(uri, mutex, cond, error); - if (is == nullptr) { - if (error.IsDefined()) - FormatError(error, "Failed to open %s", uri); - - return nullptr; - } - - playlist = playlist_list_open_stream(*is, uri); - if (playlist == nullptr) { - is->Close(); - return nullptr; - } - - *is_r = is; - return playlist; -} - -SongEnumerator * -playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - return uri_has_scheme(uri) - ? playlist_open_remote(uri, mutex, cond, is_r) - : playlist_mapper_open(uri, mutex, cond, is_r); -} diff --git a/src/PlaylistAny.hxx b/src/PlaylistAny.hxx deleted file mode 100644 index 743db2e39..000000000 --- a/src/PlaylistAny.hxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_ANY_HXX -#define MPD_PLAYLIST_ANY_HXX - -class Mutex; -class Cond; -class SongEnumerator; -struct InputStream; - -/** - * Opens a playlist from the specified URI, which can be either an - * absolute remote URI (with a scheme) or a relative path to the - * music orplaylist directory. - * - * @param is_r on success, an input_stream object may be returned - * here, which must be closed after the playlist_provider object is - * freed - */ -SongEnumerator * -playlist_open_any(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r); - -#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx deleted file mode 100644 index 58971a4b4..000000000 --- a/src/PlaylistControl.cxx +++ /dev/null @@ -1,261 +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. - */ - -/* - * Functions for controlling playback on the playlist level. - * - */ - -#include "config.h" -#include "Playlist.hxx" -#include "PlaylistError.hxx" -#include "PlayerControl.hxx" -#include "Song.hxx" -#include "Log.hxx" - -void -playlist::Stop(PlayerControl &pc) -{ - if (!playing) - return; - - assert(current >= 0); - - FormatDebug(playlist_domain, "stop"); - pc.Stop(); - queued = -1; - playing = false; - - if (queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = queue.OrderToPosition(current); - - queue.ShuffleOrder(); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - current = queue.PositionToOrder(current_position); - } -} - -PlaylistResult -playlist::PlayPosition(PlayerControl &pc, int song) -{ - pc.ClearError(); - - unsigned i = song; - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue.IsEmpty()) - return PlaylistResult::SUCCESS; - - if (playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc.SetPause(false); - return PlaylistResult::SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = current >= 0 - ? current - : 0; - } else if (!queue.IsValidPosition(song)) - return PlaylistResult::BAD_RANGE; - - if (queue.random) { - if (song >= 0) - /* "i" is currently the song position (which - would be equal to the order number in - no-random mode); convert it to a order - number, because random mode is enabled */ - i = queue.PositionToOrder(song); - - if (!playing) - current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue.SwapOrders(i, current); - i = current; - } - - stop_on_error = false; - error_count = 0; - - PlayOrder(pc, i); - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::PlayId(PlayerControl &pc, int id) -{ - if (id == -1) - return PlayPosition(pc, id); - - int song = queue.IdToPosition(id); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return PlayPosition(pc, song); -} - -void -playlist::PlayNext(PlayerControl &pc) -{ - if (!playing) - return; - - assert(!queue.IsEmpty()); - assert(queue.IsValidOrder(current)); - - const int old_current = current; - stop_on_error = false; - - /* determine the next song from the queue's order list */ - - const int next_order = queue.GetNextOrder(current); - if (next_order < 0) { - /* no song after this one: stop playback */ - Stop(pc); - - /* reset "current song" */ - current = -1; - } - else - { - if (next_order == 0 && queue.random) { - /* The queue told us that the next song is the first - song. This means we are in repeat mode. Shuffle - the queue order, so this time, the user hears the - songs in a different than before */ - assert(queue.repeat); - - queue.ShuffleOrder(); - - /* note that current and queued are - now invalid, but PlayOrder() will - discard them anyway */ - } - - PlayOrder(pc, next_order); - } - - /* Consume mode removes each played songs. */ - if (queue.consume) - DeleteOrder(pc, old_current); -} - -void -playlist::PlayPrevious(PlayerControl &pc) -{ - if (!playing) - return; - - assert(!queue.IsEmpty()); - - int order; - if (current > 0) { - /* play the preceding song */ - order = current - 1; - } else if (queue.repeat) { - /* play the last song in "repeat" mode */ - order = queue.GetLength() - 1; - } else { - /* re-start playing the current song if it's - the first one */ - order = current; - } - - PlayOrder(pc, order); -} - -PlaylistResult -playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) -{ - if (!queue.IsValidPosition(song)) - return PlaylistResult::BAD_RANGE; - - const Song *queued_song = GetQueuedSong(); - - unsigned i = queue.random - ? queue.PositionToOrder(song) - : song; - - pc.ClearError(); - stop_on_error = true; - error_count = 0; - - if (!playing || (unsigned)current != i) { - /* seeking is not within the current song - prepare - song change */ - - playing = true; - current = i; - - queued_song = nullptr; - } - - Song *the_song = queue.GetOrder(i).DupDetached(); - if (!pc.Seek(the_song, seek_time)) { - UpdateQueuedSong(pc, queued_song); - - return PlaylistResult::NOT_PLAYING; - } - - queued = -1; - UpdateQueuedSong(pc, nullptr); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time) -{ - int song = queue.IdToPosition(id); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return SeekSongPosition(pc, song, seek_time); -} - -PlaylistResult -playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative) -{ - if (!playing) - return PlaylistResult::NOT_PLAYING; - - if (relative) { - const auto status = pc.GetStatus(); - - if (status.state != PlayerState::PLAY && - status.state != PlayerState::PAUSE) - return PlaylistResult::NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return SeekSongPosition(pc, current, seek_time); -} diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx index a6d15e755..3421ecb02 100644 --- a/src/PlaylistDatabase.cxx +++ b/src/PlaylistDatabase.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,9 @@ #include "config.h" #include "PlaylistDatabase.hxx" -#include "PlaylistVector.hxx" -#include "TextFile.hxx" +#include "db/PlaylistVector.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/BufferedOutputStream.hxx" #include "util/StringUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -31,13 +32,13 @@ static constexpr Domain playlist_database_domain("playlist_database"); void -playlist_vector_save(FILE *fp, const PlaylistVector &pv) +playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv) { for (const PlaylistInfo &pi : pv) - fprintf(fp, PLAYLIST_META_BEGIN "%s\n" - "mtime: %li\n" - "playlist_end\n", - pi.name.c_str(), (long)pi.mtime); + os.Format(PLAYLIST_META_BEGIN "%s\n" + "mtime: %li\n" + "playlist_end\n", + pi.name.c_str(), (long)pi.mtime); } bool @@ -59,7 +60,7 @@ playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, } *colon++ = 0; - value = strchug_fast(colon); + value = StripLeft(colon); if (strcmp(line, "mtime") == 0) pm.mtime = strtol(value, nullptr, 10); diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx index 1481f621f..17f82f64b 100644 --- a/src/PlaylistDatabase.hxx +++ b/src/PlaylistDatabase.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 @@ -22,16 +22,15 @@ #include "check.h" -#include <stdio.h> - #define PLAYLIST_META_BEGIN "playlist_begin: " class PlaylistVector; +class BufferedOutputStream; class TextFile; class Error; void -playlist_vector_save(FILE *fp, const PlaylistVector &pv); +playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv); bool playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name, diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx deleted file mode 100644 index 0dffd3d80..000000000 --- a/src/PlaylistEdit.cxx +++ /dev/null @@ -1,463 +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. - */ - -/* - * Functions for editing the playlist (adding, removing, reordering - * songs in the queue). - * - */ - -#include "config.h" -#include "Playlist.hxx" -#include "PlaylistError.hxx" -#include "PlayerControl.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "Song.hxx" -#include "Idle.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "Log.hxx" - -#include <stdlib.h> - -void -playlist::OnModified() -{ - if (bulk_edit) { - /* postponed to CommitBulk() */ - bulk_modified = true; - return; - } - - queue.IncrementVersion(); - - idle_add(IDLE_PLAYLIST); -} - -void -playlist::Clear(PlayerControl &pc) -{ - Stop(pc); - - queue.Clear(); - current = -1; - - OnModified(); -} - -void -playlist::BeginBulk() -{ - assert(!bulk_edit); - - bulk_edit = true; - bulk_modified = false; -} - -void -playlist::CommitBulk(PlayerControl &pc) -{ - assert(bulk_edit); - - bulk_edit = false; - if (!bulk_modified) - return; - - if (queued < 0) - /* if no song was queued, UpdateQueuedSong() is being - ignored in "bulk" edit mode; now that we have - shuffled all new songs, we can pick a random one - (instead of always picking the first one that was - added) */ - UpdateQueuedSong(pc, nullptr); - - OnModified(); -} - -PlaylistResult -playlist::AppendFile(PlayerControl &pc, - const char *path_utf8, unsigned *added_id) -{ - Song *song = Song::LoadFile(path_utf8, nullptr); - if (song == nullptr) - return PlaylistResult::NO_SUCH_SONG; - - const auto result = AppendSong(pc, song, added_id); - song->Free(); - return result; -} - -PlaylistResult -playlist::AppendSong(PlayerControl &pc, - Song *song, unsigned *added_id) -{ - unsigned id; - - if (queue.IsFull()) - return PlaylistResult::TOO_LARGE; - - const Song *const queued_song = GetQueuedSong(); - - id = queue.Append(song, 0); - - if (queue.random) { - /* shuffle the new song into the list of remaining - songs to play */ - - unsigned start; - if (queued >= 0) - start = queued + 1; - else - start = current + 1; - if (start < queue.GetLength()) - queue.ShuffleOrderLast(start, queue.GetLength()); - } - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - if (added_id) - *added_id = id; - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::AppendURI(PlayerControl &pc, - const char *uri, unsigned *added_id) -{ - FormatDebug(playlist_domain, "add to playlist: %s", uri); - - const Database *db = nullptr; - Song *song; - if (uri_has_scheme(uri)) { - song = Song::NewRemote(uri); - } else { - db = GetDatabase(); - if (db == nullptr) - return PlaylistResult::NO_SUCH_SONG; - - song = db->GetSong(uri, IgnoreError()); - if (song == nullptr) - return PlaylistResult::NO_SUCH_SONG; - } - - PlaylistResult result = AppendSong(pc, song, added_id); - if (db != nullptr) - db->ReturnSong(song); - else - song->Free(); - - return result; -} - -PlaylistResult -playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2) -{ - if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) - return PlaylistResult::BAD_RANGE; - - const Song *const queued_song = GetQueuedSong(); - - queue.SwapPositions(song1, song2); - - if (queue.random) { - /* update the queue order, so that current - still points to the current song order */ - - queue.SwapOrders(queue.PositionToOrder(song1), - queue.PositionToOrder(song2)); - } else { - /* correct the "current" song order */ - - if (current == (int)song1) - current = song2; - else if (current == (int)song2) - current = song1; - } - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2) -{ - int song1 = queue.IdToPosition(id1); - int song2 = queue.IdToPosition(id2); - - if (song1 < 0 || song2 < 0) - return PlaylistResult::NO_SUCH_SONG; - - return SwapPositions(pc, song1, song2); -} - -PlaylistResult -playlist::SetPriorityRange(PlayerControl &pc, - unsigned start, unsigned end, - uint8_t priority) -{ - if (start >= GetLength()) - return PlaylistResult::BAD_RANGE; - - if (end > GetLength()) - end = GetLength(); - - if (start >= end) - return PlaylistResult::SUCCESS; - - /* remember "current" and "queued" */ - - const int current_position = GetCurrentPosition(); - const Song *const queued_song = GetQueuedSong(); - - /* apply the priority changes */ - - queue.SetPriorityRange(start, end, priority, current); - - /* restore "current" and choose a new "queued" */ - - if (current_position >= 0) - current = queue.PositionToOrder(current_position); - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::SetPriorityId(PlayerControl &pc, - unsigned song_id, uint8_t priority) -{ - int song_position = queue.IdToPosition(song_id); - if (song_position < 0) - return PlaylistResult::NO_SUCH_SONG; - - return SetPriorityRange(pc, song_position, song_position + 1, - priority); - -} - -void -playlist::DeleteInternal(PlayerControl &pc, - unsigned song, const Song **queued_p) -{ - assert(song < GetLength()); - - unsigned songOrder = queue.PositionToOrder(song); - - if (playing && current == (int)songOrder) { - const bool paused = pc.GetState() == PlayerState::PAUSE; - - /* the current song is going to be deleted: see which - song is going to be played instead */ - - current = queue.GetNextOrder(current); - if (current == (int)songOrder) - current = -1; - - if (current >= 0 && !paused) - /* play the song after the deleted one */ - PlayOrder(pc, current); - else { - /* stop the player */ - - pc.Stop(); - playing = false; - } - - *queued_p = nullptr; - } else if (current == (int)songOrder) - /* there's a "current song" but we're not playing - currently - clear "current" */ - current = -1; - - /* now do it: remove the song */ - - queue.DeletePosition(song); - - /* update the "current" and "queued" variables */ - - if (current > (int)songOrder) - current--; -} - -PlaylistResult -playlist::DeletePosition(PlayerControl &pc, unsigned song) -{ - if (song >= queue.GetLength()) - return PlaylistResult::BAD_RANGE; - - const Song *queued_song = GetQueuedSong(); - - DeleteInternal(pc, song, &queued_song); - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end) -{ - if (start >= queue.GetLength()) - return PlaylistResult::BAD_RANGE; - - if (end > queue.GetLength()) - end = queue.GetLength(); - - if (start >= end) - return PlaylistResult::SUCCESS; - - const Song *queued_song = GetQueuedSong(); - - do { - DeleteInternal(pc, --end, &queued_song); - } while (end != start); - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::DeleteId(PlayerControl &pc, unsigned id) -{ - int song = queue.IdToPosition(id); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return DeletePosition(pc, song); -} - -void -playlist::DeleteSong(PlayerControl &pc, const struct Song &song) -{ - for (int i = queue.GetLength() - 1; i >= 0; --i) - if (SongEquals(song, queue.Get(i))) - DeletePosition(pc, i); -} - -PlaylistResult -playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to) -{ - if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) - return PlaylistResult::BAD_RANGE; - - if ((to >= 0 && to + end - start - 1 >= GetLength()) || - (to < 0 && unsigned(abs(to)) > GetLength())) - return PlaylistResult::BAD_RANGE; - - if ((int)start == to) - /* nothing happens */ - return PlaylistResult::SUCCESS; - - const Song *const queued_song = GetQueuedSong(); - - /* - * (to < 0) => move to offset from current song - * (-playlist.length == to) => move to position BEFORE current song - */ - const int currentSong = GetCurrentPosition(); - if (to < 0) { - if (currentSong < 0) - /* can't move relative to current song, - because there is no current song */ - return PlaylistResult::BAD_RANGE; - - if (start <= (unsigned)currentSong && (unsigned)currentSong < end) - /* no-op, can't be moved to offset of itself */ - return PlaylistResult::SUCCESS; - to = (currentSong + abs(to)) % GetLength(); - if (start < (unsigned)to) - to--; - } - - queue.MoveRange(start, end, to); - - if (!queue.random) { - /* update current/queued */ - if ((int)start <= current && (unsigned)current < end) - current += to - start; - else if (current >= (int)end && current <= to) - current -= end - start; - else if (current >= to && current < (int)start) - current += end - start; - } - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::MoveId(PlayerControl &pc, unsigned id1, int to) -{ - int song = queue.IdToPosition(id1); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return MoveRange(pc, song, song + 1, to); -} - -void -playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) -{ - if (end > GetLength()) - /* correct the "end" offset */ - end = GetLength(); - - if (start + 1 >= end) - /* needs at least two entries. */ - return; - - const Song *const queued_song = GetQueuedSong(); - if (playing && current >= 0) { - unsigned current_position = queue.OrderToPosition(current); - - if (current_position >= start && current_position < end) { - /* put current playing song first */ - queue.SwapPositions(start, current_position); - - if (queue.random) { - current = queue.PositionToOrder(start); - } else - current = start; - - /* start shuffle after the current song */ - start++; - } - } else { - /* no playback currently: reset current */ - - current = -1; - } - - queue.ShuffleRange(start, end); - - UpdateQueuedSong(pc, queued_song); - OnModified(); -} diff --git a/src/PlaylistError.cxx b/src/PlaylistError.cxx index 91291f551..085246f15 100644 --- a/src/PlaylistError.cxx +++ b/src/PlaylistError.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 diff --git a/src/PlaylistError.hxx b/src/PlaylistError.hxx index 8ea65ca34..0f2424f41 100644 --- a/src/PlaylistError.hxx +++ b/src/PlaylistError.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/PlaylistFile.cxx b/src/PlaylistFile.cxx index e7dae6258..f0aa2d2d7 100644 --- a/src/PlaylistFile.cxx +++ b/src/PlaylistFile.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 @@ -20,16 +20,15 @@ #include "config.h" #include "PlaylistFile.hxx" #include "PlaylistSave.hxx" -#include "PlaylistInfo.hxx" -#include "PlaylistVector.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseGlue.hxx" -#include "Song.hxx" +#include "db/PlaylistInfo.hxx" +#include "db/PlaylistVector.hxx" +#include "DetachedSong.hxx" +#include "SongLoader.hxx" #include "Mapper.hxx" -#include "TextFile.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "ConfigDefaults.hxx" +#include "fs/io/TextFile.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigDefaults.hxx" #include "Idle.hxx" #include "fs/Limits.hxx" #include "fs/AllocatedPath.hxx" @@ -37,16 +36,12 @@ #include "fs/Charset.hxx" #include "fs/FileSystem.hxx" #include "fs/DirectoryReader.hxx" +#include "util/StringUtil.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include <glib.h> - #include <assert.h> -#include <sys/types.h> #include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> #include <string.h> #include <errno.h> @@ -121,6 +116,29 @@ spl_map_to_fs(const char *name_utf8, Error &error) return path_fs; } +gcc_pure +static bool +IsNotFoundError(const Error &error) +{ +#ifdef WIN32 + return error.IsDomain(win32_domain) && + error.GetCode() == ERROR_FILE_NOT_FOUND; +#else + return error.IsDomain(errno_domain) && + error.GetCode() == ENOENT; +#endif +} + +static void +TranslatePlaylistError(Error &error) +{ + if (IsNotFoundError(error)) { + error.Clear(); + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_LIST), + "No such playlist"); + } +} + /** * Create an #Error for the current errno. */ @@ -141,8 +159,8 @@ playlist_errno(Error &error) static bool LoadPlaylistFileInfo(PlaylistInfo &info, - const AllocatedPath &parent_path_fs, - const AllocatedPath &name_fs) + const Path parent_path_fs, + const Path name_fs) { const char *name_fs_str = name_fs.c_str(); size_t name_length = strlen(name_fs_str); @@ -151,7 +169,7 @@ LoadPlaylistFileInfo(PlaylistInfo &info, memchr(name_fs_str, '\n', name_length) != nullptr) return false; - if (!g_str_has_suffix(name_fs_str, PLAYLIST_FILE_SUFFIX)) + if (!StringEndsWith(name_fs_str, PLAYLIST_FILE_SUFFIX)) return false; const auto path_fs = AllocatedPath::Build(parent_path_fs, name_fs); @@ -159,10 +177,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info, if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) return false; - char *name = g_strndup(name_fs_str, - name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); - std::string name_utf8 = PathToUTF8(name); - g_free(name); + std::string name(name_fs_str, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + std::string name_utf8 = PathToUTF8(name.c_str()); if (name_utf8.empty()) return false; @@ -234,9 +251,9 @@ LoadPlaylistFile(const char *utf8path, Error &error) if (path_fs.IsNull()) return contents; - TextFile file(path_fs); + TextFile file(path_fs, error); if (file.HasFailed()) { - playlist_errno(error); + TranslatePlaylistError(error); return contents; } @@ -248,9 +265,10 @@ LoadPlaylistFile(const char *utf8path, Error &error) std::string uri_utf8; if (!uri_has_scheme(s)) { +#ifdef ENABLE_DATABASE uri_utf8 = map_fs_to_utf8(s); if (uri_utf8.empty()) { - if (PathTraits::IsAbsoluteFS(s)) { + if (PathTraitsFS::IsAbsolute(s)) { uri_utf8 = PathToUTF8(s); if (uri_utf8.empty()) continue; @@ -259,6 +277,9 @@ LoadPlaylistFile(const char *utf8path, Error &error) } else continue; } +#else + continue; +#endif } else { uri_utf8 = PathToUTF8(s); if (uri_utf8.empty()) @@ -365,7 +386,7 @@ spl_remove_index(const char *utf8path, unsigned pos, Error &error) } bool -spl_append_song(const char *utf8path, const Song &song, Error &error) +spl_append_song(const char *utf8path, const DetachedSong &song, Error &error) { if (spl_map(error).IsNull()) return false; @@ -403,26 +424,17 @@ spl_append_song(const char *utf8path, const Song &song, Error &error) } bool -spl_append_uri(const char *url, const char *utf8file, Error &error) +spl_append_uri(const char *utf8file, + const SongLoader &loader, const char *url, + Error &error) { - if (uri_has_scheme(url)) { - Song *song = Song::NewRemote(url); - bool success = spl_append_song(utf8file, *song, error); - song->Free(); - return success; - } else { - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - Song *song = db->GetSong(url, error); - if (song == nullptr) - return false; - - bool success = spl_append_song(utf8file, *song, error); - db->ReturnSong(song); - return success; - } + DetachedSong *song = loader.LoadSong(url, error); + if (song == nullptr) + return false; + + bool success = spl_append_song(utf8file, *song, error); + delete song; + return success; } static bool diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx index f04530bcc..7154b1f84 100644 --- a/src/PlaylistFile.hxx +++ b/src/PlaylistFile.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 @@ -23,8 +23,8 @@ #include <vector> #include <string> -struct Song; -struct PlaylistInfo; +class DetachedSong; +class SongLoader; class PlaylistVector; class Error; @@ -69,10 +69,12 @@ bool spl_remove_index(const char *utf8path, unsigned pos, Error &error); bool -spl_append_song(const char *utf8path, const Song &song, Error &error); +spl_append_song(const char *utf8path, const DetachedSong &song, Error &error); bool -spl_append_uri(const char *file, const char *utf8file, Error &error); +spl_append_uri(const char *path_utf8, + const SongLoader &loader, const char *uri_utf8, + Error &error); bool spl_rename(const char *utf8from, const char *utf8to, Error &error); diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx index 97902275b..dacfad0c7 100644 --- a/src/PlaylistGlobal.cxx +++ b/src/PlaylistGlobal.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 @@ -24,7 +24,6 @@ #include "config.h" #include "PlaylistGlobal.hxx" -#include "Playlist.hxx" #include "Main.hxx" #include "Instance.hxx" #include "GlobalEvents.hxx" diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx index 4397292db..a2e3bb030 100644 --- a/src/PlaylistGlobal.hxx +++ b/src/PlaylistGlobal.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/PlaylistInfo.hxx b/src/PlaylistInfo.hxx deleted file mode 100644 index 2c5b9ae1a..000000000 --- a/src/PlaylistInfo.hxx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_INFO_HXX -#define MPD_PLAYLIST_INFO_HXX - -#include "check.h" -#include "Compiler.h" - -#include <string> - -#include <sys/time.h> - -/** - * A directory entry pointing to a playlist file. - */ -struct PlaylistInfo { - /** - * The UTF-8 encoded name of the playlist file. - */ - std::string name; - - time_t mtime; - - class CompareName { - const char *const name; - - public: - constexpr CompareName(const char *_name):name(_name) {} - - gcc_pure - bool operator()(const PlaylistInfo &pi) const { - return pi.name.compare(name) == 0; - } - }; - - PlaylistInfo() = default; - - template<typename N> - PlaylistInfo(N &&_name, time_t _mtime) - :name(std::forward<N>(_name)), mtime(_mtime) {} - - PlaylistInfo(const PlaylistInfo &other) = delete; - PlaylistInfo(PlaylistInfo &&) = default; -}; - -#endif diff --git a/src/PlaylistMapper.cxx b/src/PlaylistMapper.cxx deleted file mode 100644 index 68ac22438..000000000 --- a/src/PlaylistMapper.cxx +++ /dev/null @@ -1,102 +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 "PlaylistMapper.hxx" -#include "PlaylistFile.hxx" -#include "PlaylistRegistry.hxx" -#include "Mapper.hxx" -#include "fs/AllocatedPath.hxx" -#include "util/UriUtil.hxx" - -#include <assert.h> - -static SongEnumerator * -playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - auto playlist = playlist_list_open_uri(path_fs, mutex, cond); - if (playlist != nullptr) - *is_r = nullptr; - else - playlist = playlist_list_open_path(path_fs, mutex, cond, is_r); - - return playlist; -} - -/** - * Load a playlist from the configured playlist directory. - */ -static SongEnumerator * -playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - assert(spl_valid_name(uri)); - - const auto &playlist_directory_fs = map_spl_path(); - if (playlist_directory_fs.IsNull()) - return nullptr; - - const auto uri_fs = AllocatedPath::FromUTF8(uri); - if (uri_fs.IsNull()) - return nullptr; - - const auto path_fs = - AllocatedPath::Build(playlist_directory_fs, uri_fs); - assert(!path_fs.IsNull()); - - return playlist_open_path(path_fs.c_str(), mutex, cond, is_r); -} - -/** - * Load a playlist from the configured music directory. - */ -static SongEnumerator * -playlist_open_in_music_dir(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - assert(uri_safe_local(uri)); - - const auto path = map_uri_fs(uri); - if (path.IsNull()) - return nullptr; - - return playlist_open_path(path.c_str(), mutex, cond, is_r); -} - -SongEnumerator * -playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - if (spl_valid_name(uri)) { - auto playlist = playlist_open_in_playlist_dir(uri, mutex, cond, - is_r); - if (playlist != nullptr) - return playlist; - } - - if (uri_safe_local(uri)) { - auto playlist = playlist_open_in_music_dir(uri, mutex, cond, - is_r); - if (playlist != nullptr) - return playlist; - } - - return nullptr; -} diff --git a/src/PlaylistMapper.hxx b/src/PlaylistMapper.hxx deleted file mode 100644 index 7132415f1..000000000 --- a/src/PlaylistMapper.hxx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_MAPPER_HXX -#define MPD_PLAYLIST_MAPPER_HXX - -class Mutex; -class Cond; -class SongEnumerator; -struct InputStream; - -/** - * Opens a playlist from an URI relative to the playlist or music - * directory. - * - * @param is_r on success, an input_stream object may be returned - * here, which must be closed after the playlist_provider object is - * freed - */ -SongEnumerator * -playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond, - InputStream **is_r); - -#endif diff --git a/src/PlaylistPlugin.hxx b/src/PlaylistPlugin.hxx deleted file mode 100644 index 6eb0f4771..000000000 --- a/src/PlaylistPlugin.hxx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_PLUGIN_HXX -#define MPD_PLAYLIST_PLUGIN_HXX - -struct config_param; -struct InputStream; -struct Tag; -class Mutex; -class Cond; -class SongEnumerator; - -struct playlist_plugin { - const char *name; - - /** - * Initialize the plugin. Optional method. - * - * @param param a configuration block for this plugin, or nullptr - * if none is configured - * @return true if the plugin was initialized successfully, - * false if the plugin is not available - */ - bool (*init)(const config_param ¶m); - - /** - * Deinitialize a plugin which was initialized successfully. - * Optional method. - */ - void (*finish)(void); - - /** - * Opens the playlist on the specified URI. This URI has - * either matched one of the schemes or one of the suffixes. - */ - SongEnumerator *(*open_uri)(const char *uri, - Mutex &mutex, Cond &cond); - - /** - * Opens the playlist in the specified input stream. It has - * either matched one of the suffixes or one of the MIME - * types. - */ - SongEnumerator *(*open_stream)(InputStream &is); - - const char *const*schemes; - const char *const*suffixes; - const char *const*mime_types; -}; - -/** - * Initialize a plugin. - * - * @param param a configuration block for this plugin, or nullptr if none - * is configured - * @return true if the plugin was initialized successfully, false if - * the plugin is not available - */ -static inline bool -playlist_plugin_init(const struct playlist_plugin *plugin, - const config_param ¶m) -{ - return plugin->init != nullptr - ? plugin->init(param) - : true; -} - -/** - * Deinitialize a plugin which was initialized successfully. - */ -static inline void -playlist_plugin_finish(const struct playlist_plugin *plugin) -{ - if (plugin->finish != nullptr) - plugin->finish(); -} - -static inline SongEnumerator * -playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, - Mutex &mutex, Cond &cond) -{ - return plugin->open_uri(uri, mutex, cond); -} - -static inline SongEnumerator * -playlist_plugin_open_stream(const struct playlist_plugin *plugin, - InputStream &is) -{ - return plugin->open_stream(is); -} - -#endif diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx index e3d500be3..cfa56c7b3 100644 --- a/src/PlaylistPrint.cxx +++ b/src/PlaylistPrint.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 @@ -20,23 +20,22 @@ #include "config.h" #include "PlaylistPrint.hxx" #include "PlaylistFile.hxx" -#include "PlaylistAny.hxx" -#include "PlaylistSong.hxx" -#include "Playlist.hxx" -#include "PlaylistRegistry.hxx" -#include "PlaylistPlugin.hxx" -#include "QueuePrint.hxx" -#include "SongEnumerator.hxx" +#include "queue/Playlist.hxx" +#include "queue/QueuePrint.hxx" #include "SongPrint.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "Client.hxx" -#include "InputStream.hxx" -#include "Song.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "db/Interface.hxx" +#include "client/Client.hxx" +#include "input/InputStream.hxx" +#include "DetachedSong.hxx" #include "fs/Traits.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" +#define SONG_FILE "file: " +#define SONG_TIME "Time: " + void playlist_print_uris(Client &client, const playlist &playlist) { @@ -112,14 +111,16 @@ playlist_print_changes_position(Client &client, queue_print_changes_position(client, playlist.queue, version); } +#ifdef ENABLE_DATABASE + static bool PrintSongDetails(Client &client, const char *uri_utf8) { - const Database *db = GetDatabase(); + const Database *db = client.partition.instance.database; if (db == nullptr) return false; - Song *song = db->GetSong(uri_utf8, IgnoreError()); + auto *song = db->GetSong(uri_utf8, IgnoreError()); if (song == nullptr) return false; @@ -128,63 +129,27 @@ PrintSongDetails(Client &client, const char *uri_utf8) return true; } +#endif + bool spl_print(Client &client, const char *name_utf8, bool detail, Error &error) { +#ifndef ENABLE_DATABASE + (void)detail; +#endif + PlaylistFileContents contents = LoadPlaylistFile(name_utf8, error); if (contents.empty() && error.IsDefined()) return false; for (const auto &uri_utf8 : contents) { +#ifdef ENABLE_DATABASE if (!detail || !PrintSongDetails(client, uri_utf8.c_str())) +#endif client_printf(client, SONG_FILE "%s\n", uri_utf8.c_str()); } return true; } - -static void -playlist_provider_print(Client &client, const char *uri, - SongEnumerator &e, bool detail) -{ - const std::string base_uri = uri != nullptr - ? PathTraits::GetParentUTF8(uri) - : std::string("."); - - Song *song; - while ((song = e.NextSong()) != nullptr) { - song = playlist_check_translate_song(song, base_uri.c_str(), - false); - if (song == nullptr) - continue; - - if (detail) - song_print_info(client, *song); - else - song_print_uri(client, *song); - - song->Free(); - } -} - -bool -playlist_file_print(Client &client, const char *uri, bool detail) -{ - Mutex mutex; - Cond cond; - - InputStream *is; - SongEnumerator *playlist = playlist_open_any(uri, mutex, cond, &is); - if (playlist == nullptr) - return false; - - playlist_provider_print(client, uri, *playlist, detail); - delete playlist; - - if (is != nullptr) - is->Close(); - - return true; -} diff --git a/src/PlaylistPrint.hxx b/src/PlaylistPrint.hxx index 9cbc46325..38a4cc7cf 100644 --- a/src/PlaylistPrint.hxx +++ b/src/PlaylistPrint.hxx @@ -1,6 +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 @@ -96,15 +95,4 @@ bool spl_print(Client &client, const char *name_utf8, bool detail, Error &error); -/** - * Send the playlist file to the client. - * - * @param client the client which requested the playlist - * @param uri the URI of the playlist file in UTF-8 encoding - * @param detail true if all details should be printed - * @return true on success, false if the playlist does not exist - */ -bool -playlist_file_print(Client &client, const char *uri, bool detail); - #endif diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx deleted file mode 100644 index a8359d427..000000000 --- a/src/PlaylistQueue.cxx +++ /dev/null @@ -1,90 +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 "PlaylistQueue.hxx" -#include "PlaylistPlugin.hxx" -#include "PlaylistAny.hxx" -#include "PlaylistSong.hxx" -#include "Playlist.hxx" -#include "InputStream.hxx" -#include "SongEnumerator.hxx" -#include "Song.hxx" -#include "thread/Cond.hxx" -#include "fs/Traits.hxx" - -PlaylistResult -playlist_load_into_queue(const char *uri, SongEnumerator &e, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure) -{ - const std::string base_uri = uri != nullptr - ? PathTraits::GetParentUTF8(uri) - : std::string("."); - - Song *song; - for (unsigned i = 0; - i < end_index && (song = e.NextSong()) != nullptr; - ++i) { - if (i < start_index) { - /* skip songs before the start index */ - song->Free(); - continue; - } - - song = playlist_check_translate_song(song, base_uri.c_str(), - secure); - if (song == nullptr) - continue; - - PlaylistResult result = dest.AppendSong(pc, song); - song->Free(); - if (result != PlaylistResult::SUCCESS) - return result; - } - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure) -{ - Mutex mutex; - Cond cond; - - InputStream *is; - auto playlist = playlist_open_any(uri, mutex, cond, &is); - if (playlist == nullptr) - return PlaylistResult::NO_SUCH_LIST; - - PlaylistResult result = - playlist_load_into_queue(uri, *playlist, - start_index, end_index, - dest, pc, secure); - delete playlist; - - if (is != nullptr) - is->Close(); - - return result; -} diff --git a/src/PlaylistQueue.hxx b/src/PlaylistQueue.hxx deleted file mode 100644 index fc566ec78..000000000 --- a/src/PlaylistQueue.hxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/*! \file - * \brief Glue between playlist plugin and the play queue - */ - -#ifndef MPD_PLAYLIST_QUEUE_HXX -#define MPD_PLAYLIST_QUEUE_HXX - -#include "PlaylistError.hxx" - -class SongEnumerator; -struct playlist; -struct PlayerControl; - -/** - * Loads the contents of a playlist and append it to the specified - * play queue. - * - * @param uri the URI of the playlist, used to resolve relative song - * URIs - * @param start_index the index of the first song - * @param end_index the index of the last song (excluding) - */ -PlaylistResult -playlist_load_into_queue(const char *uri, SongEnumerator &e, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure); - -/** - * Opens a playlist with a playlist plugin and append to the specified - * play queue. - */ -PlaylistResult -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure); - -#endif - diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx deleted file mode 100644 index 9afbe349d..000000000 --- a/src/PlaylistRegistry.cxx +++ /dev/null @@ -1,341 +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 "PlaylistRegistry.hxx" -#include "PlaylistPlugin.hxx" -#include "playlist/ExtM3uPlaylistPlugin.hxx" -#include "playlist/M3uPlaylistPlugin.hxx" -#include "playlist/XspfPlaylistPlugin.hxx" -#include "playlist/DespotifyPlaylistPlugin.hxx" -#include "playlist/SoundCloudPlaylistPlugin.hxx" -#include "playlist/PlsPlaylistPlugin.hxx" -#include "playlist/AsxPlaylistPlugin.hxx" -#include "playlist/RssPlaylistPlugin.hxx" -#include "playlist/CuePlaylistPlugin.hxx" -#include "playlist/EmbeddedCuePlaylistPlugin.hxx" -#include "InputStream.hxx" -#include "util/UriUtil.hxx" -#include "util/StringUtil.hxx" -#include "util/Error.hxx" -#include "util/Macros.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -const struct playlist_plugin *const playlist_plugins[] = { - &extm3u_playlist_plugin, - &m3u_playlist_plugin, - &xspf_playlist_plugin, - &pls_playlist_plugin, - &asx_playlist_plugin, - &rss_playlist_plugin, -#ifdef ENABLE_DESPOTIFY - &despotify_playlist_plugin, -#endif -#ifdef ENABLE_SOUNDCLOUD - &soundcloud_playlist_plugin, -#endif - &cue_playlist_plugin, - &embcue_playlist_plugin, - nullptr -}; - -static constexpr unsigned n_playlist_plugins = - ARRAY_SIZE(playlist_plugins) - 1; - -/** which plugins have been initialized successfully? */ -static bool playlist_plugins_enabled[n_playlist_plugins]; - -#define playlist_plugins_for_each_enabled(plugin) \ - playlist_plugins_for_each(plugin) \ - if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins]) - -/** - * Find the "playlist" configuration block for the specified plugin. - * - * @param plugin_name the name of the playlist plugin - * @return the configuration block, or nullptr if none was configured - */ -static const struct config_param * -playlist_plugin_config(const char *plugin_name) -{ - const struct config_param *param = nullptr; - - assert(plugin_name != nullptr); - - while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != nullptr) { - const char *name = param->GetBlockValue("name"); - if (name == nullptr) - FormatFatalError("playlist configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return nullptr; -} - -void -playlist_list_global_init(void) -{ - const config_param empty; - - for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { - const struct playlist_plugin *plugin = playlist_plugins[i]; - const struct config_param *param = - playlist_plugin_config(plugin->name); - if (param == nullptr) - param = ∅ - else if (!param->GetBlockValue("enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - playlist_plugins_enabled[i] = - playlist_plugin_init(playlist_plugins[i], *param); - } -} - -void -playlist_list_global_finish(void) -{ - playlist_plugins_for_each_enabled(plugin) - playlist_plugin_finish(plugin); -} - -static SongEnumerator * -playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, - bool *tried) -{ - char *scheme; - SongEnumerator *playlist = nullptr; - - assert(uri != nullptr); - - scheme = g_uri_parse_scheme(uri); - if (scheme == nullptr) - return nullptr; - - for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { - const struct playlist_plugin *plugin = playlist_plugins[i]; - - assert(!tried[i]); - - if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr && - plugin->schemes != nullptr && - string_array_contains(plugin->schemes, scheme)) { - playlist = playlist_plugin_open_uri(plugin, uri, - mutex, cond); - if (playlist != nullptr) - break; - - tried[i] = true; - } - } - - g_free(scheme); - return playlist; -} - -static SongEnumerator * -playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond, - const bool *tried) -{ - const char *suffix; - SongEnumerator *playlist = nullptr; - - assert(uri != nullptr); - - suffix = uri_get_suffix(uri); - if (suffix == nullptr) - return nullptr; - - for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { - const struct playlist_plugin *plugin = playlist_plugins[i]; - - if (playlist_plugins_enabled[i] && !tried[i] && - plugin->open_uri != nullptr && plugin->suffixes != nullptr && - string_array_contains(plugin->suffixes, suffix)) { - playlist = playlist_plugin_open_uri(plugin, uri, - mutex, cond); - if (playlist != nullptr) - break; - } - } - - return playlist; -} - -SongEnumerator * -playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond) -{ - /** this array tracks which plugins have already been tried by - playlist_list_open_uri_scheme() */ - bool tried[n_playlist_plugins]; - - assert(uri != nullptr); - - memset(tried, false, sizeof(tried)); - - auto playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried); - if (playlist == nullptr) - playlist = playlist_list_open_uri_suffix(uri, mutex, cond, - tried); - - return playlist; -} - -static SongEnumerator * -playlist_list_open_stream_mime2(InputStream &is, const char *mime) -{ - assert(mime != nullptr); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->open_stream != nullptr && - plugin->mime_types != nullptr && - string_array_contains(plugin->mime_types, mime)) { - /* rewind the stream, so each plugin gets a - fresh start */ - is.Rewind(IgnoreError()); - - auto playlist = playlist_plugin_open_stream(plugin, - is); - if (playlist != nullptr) - return playlist; - } - } - - return nullptr; -} - -static SongEnumerator * -playlist_list_open_stream_mime(InputStream &is, const char *full_mime) -{ - assert(full_mime != nullptr); - - const char *semicolon = strchr(full_mime, ';'); - if (semicolon == nullptr) - return playlist_list_open_stream_mime2(is, full_mime); - - if (semicolon == full_mime) - return nullptr; - - /* probe only the portion before the semicolon*/ - const std::string mime(full_mime, semicolon); - return playlist_list_open_stream_mime2(is, mime.c_str()); -} - -static SongEnumerator * -playlist_list_open_stream_suffix(InputStream &is, const char *suffix) -{ - assert(suffix != nullptr); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->open_stream != nullptr && - plugin->suffixes != nullptr && - string_array_contains(plugin->suffixes, suffix)) { - /* rewind the stream, so each plugin gets a - fresh start */ - is.Rewind(IgnoreError()); - - auto playlist = playlist_plugin_open_stream(plugin, is); - if (playlist != nullptr) - return playlist; - } - } - - return nullptr; -} - -SongEnumerator * -playlist_list_open_stream(InputStream &is, const char *uri) -{ - const char *suffix; - - is.LockWaitReady(); - - const char *const mime = is.GetMimeType(); - if (mime != nullptr) { - auto playlist = playlist_list_open_stream_mime(is, mime); - if (playlist != nullptr) - return playlist; - } - - suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr; - if (suffix != nullptr) { - auto playlist = playlist_list_open_stream_suffix(is, suffix); - if (playlist != nullptr) - return playlist; - } - - return nullptr; -} - -bool -playlist_suffix_supported(const char *suffix) -{ - assert(suffix != nullptr); - - playlist_plugins_for_each_enabled(plugin) { - if (plugin->suffixes != nullptr && - string_array_contains(plugin->suffixes, suffix)) - return true; - } - - return false; -} - -SongEnumerator * -playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, - InputStream **is_r) -{ - const char *suffix; - - assert(path_fs != nullptr); - - suffix = uri_get_suffix(path_fs); - if (suffix == nullptr || !playlist_suffix_supported(suffix)) - return nullptr; - - Error error; - InputStream *is = InputStream::Open(path_fs, mutex, cond, error); - if (is == nullptr) { - if (error.IsDefined()) - LogError(error); - - return nullptr; - } - - is->LockWaitReady(); - - auto playlist = playlist_list_open_stream_suffix(*is, suffix); - if (playlist != nullptr) - *is_r = is; - else - is->Close(); - - return playlist; -} diff --git a/src/PlaylistRegistry.hxx b/src/PlaylistRegistry.hxx deleted file mode 100644 index 2b747316d..000000000 --- a/src/PlaylistRegistry.hxx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_REGISTRY_HXX -#define MPD_PLAYLIST_REGISTRY_HXX - -class Mutex; -class Cond; -class SongEnumerator; -struct InputStream; - -extern const struct playlist_plugin *const playlist_plugins[]; - -#define playlist_plugins_for_each(plugin) \ - for (const struct playlist_plugin *plugin, \ - *const*playlist_plugin_iterator = &playlist_plugins[0]; \ - (plugin = *playlist_plugin_iterator) != nullptr; \ - ++playlist_plugin_iterator) - -/** - * Initializes all playlist plugins. - */ -void -playlist_list_global_init(void); - -/** - * Deinitializes all playlist plugins. - */ -void -playlist_list_global_finish(void); - -/** - * Opens a playlist by its URI. - */ -SongEnumerator * -playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond); - -/** - * Opens a playlist from an input stream. - * - * @param is an #input_stream object which is open and ready - * @param uri optional URI which was used to open the stream; may be - * used to select the appropriate playlist plugin - */ -SongEnumerator * -playlist_list_open_stream(InputStream &is, const char *uri); - -/** - * Determines if there is a playlist plugin which can handle the - * specified file name suffix. - */ -bool -playlist_suffix_supported(const char *suffix); - -/** - * Opens a playlist from a local file. - * - * @param path_fs the path of the playlist file - * @param is_r on success, an input_stream object is returned here, - * which must be closed after the playlist_provider object is freed - * @return a playlist, or nullptr on error - */ -SongEnumerator * -playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, - InputStream **is_r); - -#endif diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx index 29bf193fd..67f05267f 100644 --- a/src/PlaylistSave.cxx +++ b/src/PlaylistSave.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 @@ -21,44 +21,44 @@ #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" #include "PlaylistError.hxx" -#include "Playlist.hxx" -#include "Song.hxx" +#include "queue/Playlist.hxx" +#include "DetachedSong.hxx" +#include "SongLoader.hxx" #include "Mapper.hxx" #include "Idle.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" +#include "util/Alloc.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - #include <string.h> void -playlist_print_song(FILE *file, const Song &song) +playlist_print_song(FILE *file, const DetachedSong &song) { - if (playlist_saveAbsolutePaths && song.IsInDatabase()) { - const auto path = map_song_fs(song); - if (!path.IsNull()) - fprintf(file, "%s\n", path.c_str()); - } else { - const auto uri_utf8 = song.GetURI(); - const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8.c_str()); - - if (!uri_fs.IsNull()) - fprintf(file, "%s\n", uri_fs.c_str()); - } + const char *uri_utf8 = playlist_saveAbsolutePaths + ? song.GetRealURI() + : song.GetURI(); + + const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8); + if (!uri_fs.IsNull()) + fprintf(file, "%s\n", uri_fs.c_str()); } void playlist_print_uri(FILE *file, const char *uri) { - auto path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - !PathTraits::IsAbsoluteUTF8(uri) + auto path = +#ifdef ENABLE_DATABASE + playlist_saveAbsolutePaths && !uri_has_scheme(uri) && + !PathTraitsUTF8::IsAbsolute(uri) ? map_uri_fs(uri) - : AllocatedPath::FromUTF8(uri); + : +#endif + AllocatedPath::FromUTF8(uri); if (!path.IsNull()) fprintf(file, "%s\n", path.c_str()); @@ -99,49 +99,3 @@ spl_save_playlist(const char *name_utf8, const playlist &playlist) { return spl_save_queue(name_utf8, playlist.queue); } - -bool -playlist_load_spl(struct playlist &playlist, PlayerControl &pc, - const char *name_utf8, - unsigned start_index, unsigned end_index, - Error &error) -{ - PlaylistFileContents contents = LoadPlaylistFile(name_utf8, error); - if (contents.empty() && error.IsDefined()) - return false; - - if (end_index > contents.size()) - end_index = contents.size(); - - for (unsigned i = start_index; i < end_index; ++i) { - const auto &uri_utf8 = contents[i]; - - if (memcmp(uri_utf8.c_str(), "file:///", 8) == 0) { - const char *path_utf8 = uri_utf8.c_str() + 7; - - if (playlist.AppendFile(pc, path_utf8) != PlaylistResult::SUCCESS) - FormatError(playlist_domain, - "can't add file \"%s\"", path_utf8); - continue; - } - - if ((playlist.AppendURI(pc, uri_utf8.c_str())) != PlaylistResult::SUCCESS) { - /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(uri_utf8.c_str()); - char *p = temp2; - while (*p) { - if (*p == '\\') - *p = '/'; - p++; - } - - if (playlist.AppendURI(pc, temp2) != PlaylistResult::SUCCESS) - FormatError(playlist_domain, - "can't add file \"%s\"", temp2); - - g_free(temp2); - } - } - - return true; -} diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx index 71e0a8189..914c8c086 100644 --- a/src/PlaylistSave.hxx +++ b/src/PlaylistSave.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 @@ -24,14 +24,14 @@ #include <stdio.h> -struct Song; struct Queue; struct playlist; struct PlayerControl; +class DetachedSong; class Error; void -playlist_print_song(FILE *fp, const Song &song); +playlist_print_song(FILE *file, const DetachedSong &song); void playlist_print_uri(FILE *fp, const char *uri); @@ -48,14 +48,4 @@ spl_save_queue(const char *name_utf8, const Queue &queue); PlaylistResult spl_save_playlist(const char *name_utf8, const playlist &playlist); -/** - * Loads a stored playlist file, and append all songs to the global - * playlist. - */ -bool -playlist_load_spl(struct playlist &playlist, PlayerControl &pc, - const char *name_utf8, - unsigned start_index, unsigned end_index, - Error &error); - #endif diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx deleted file mode 100644 index 60774dc36..000000000 --- a/src/PlaylistSong.cxx +++ /dev/null @@ -1,176 +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 "PlaylistSong.hxx" -#include "Mapper.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseGlue.hxx" -#include "ls.hxx" -#include "tag/Tag.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/Traits.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "Song.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static void -merge_song_metadata(Song *dest, const Song *base, - const Song *add) -{ - dest->tag = base->tag != nullptr - ? (add->tag != nullptr - ? Tag::Merge(*base->tag, *add->tag) - : new Tag(*base->tag)) - : (add->tag != nullptr - ? new Tag(*add->tag) - : nullptr); - - dest->mtime = base->mtime; - dest->start_ms = add->start_ms; - dest->end_ms = add->end_ms; -} - -static Song * -apply_song_metadata(Song *dest, const Song *src) -{ - Song *tmp; - - assert(dest != nullptr); - assert(src != nullptr); - - if (src->tag == nullptr && src->start_ms == 0 && src->end_ms == 0) - return dest; - - if (dest->IsInDatabase()) { - const auto path_fs = map_song_fs(*dest); - if (path_fs.IsNull()) - return dest; - - std::string path_utf8 = path_fs.ToUTF8(); - if (path_utf8.empty()) - path_utf8 = path_fs.c_str(); - - tmp = Song::NewFile(path_utf8.c_str(), nullptr); - - merge_song_metadata(tmp, dest, src); - } else { - tmp = Song::NewFile(dest->uri, nullptr); - merge_song_metadata(tmp, dest, src); - } - - if (dest->tag != nullptr && dest->tag->time > 0 && - src->start_ms > 0 && src->end_ms == 0 && - src->start_ms / 1000 < (unsigned)dest->tag->time) - /* the range is open-ended, and the playlist plugin - did not know the total length of the song file - (e.g. last track on a CUE file); fix it up here */ - tmp->tag->time = dest->tag->time - src->start_ms / 1000; - - dest->Free(); - return tmp; -} - -static Song * -playlist_check_load_song(const Song *song, const char *uri, bool secure) -{ - Song *dest; - - if (uri_has_scheme(uri)) { - dest = Song::NewRemote(uri); - } else if (PathTraits::IsAbsoluteUTF8(uri) && secure) { - dest = Song::LoadFile(uri, nullptr); - if (dest == nullptr) - return nullptr; - } else { - const Database *db = GetDatabase(); - if (db == nullptr) - return nullptr; - - Song *tmp = db->GetSong(uri, IgnoreError()); - if (tmp == nullptr) - /* not found in database */ - return nullptr; - - dest = tmp->DupDetached(); - db->ReturnSong(tmp); - } - - return apply_song_metadata(dest, song); -} - -Song * -playlist_check_translate_song(Song *song, const char *base_uri, - bool secure) -{ - if (song->IsInDatabase()) - /* already ok */ - return song; - - const char *uri = song->uri; - - if (uri_has_scheme(uri)) { - if (uri_supported_scheme(uri)) - /* valid remote song */ - return song; - else { - /* unsupported remote song */ - song->Free(); - return nullptr; - } - } - - if (base_uri != nullptr && strcmp(base_uri, ".") == 0) - /* PathTraits::GetParentUTF8() returns "." when there - is no directory name in the given path; clear that - now, because it would break the database lookup - functions */ - base_uri = nullptr; - - if (PathTraits::IsAbsoluteUTF8(uri)) { - /* XXX fs_charset vs utf8? */ - const char *suffix = map_to_relative_path(uri); - assert(suffix != nullptr); - - if (suffix != uri) - uri = suffix; - else if (!secure) { - /* local files must be relative to the music - directory when "secure" is enabled */ - song->Free(); - return nullptr; - } - - base_uri = nullptr; - } - - char *allocated = nullptr; - if (base_uri != nullptr) - uri = allocated = g_build_filename(base_uri, uri, nullptr); - - Song *dest = playlist_check_load_song(song, uri, secure); - song->Free(); - g_free(allocated); - return dest; -} diff --git a/src/PlaylistSong.hxx b/src/PlaylistSong.hxx deleted file mode 100644 index d0db99868..000000000 --- a/src/PlaylistSong.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_SONG_HXX -#define MPD_PLAYLIST_SONG_HXX - -struct Song; - -/** - * Verifies the song, returns nullptr if it is unsafe. Translate the - * song to a new song object within the database, if it is a local - * file. The old song object is freed. - * - * @param secure if true, then local files are only allowed if they - * are relative to base_uri - */ -Song * -playlist_check_translate_song(Song *song, const char *base_uri, - bool secure); - -#endif diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx deleted file mode 100644 index ac2deebbf..000000000 --- a/src/PlaylistState.cxx +++ /dev/null @@ -1,244 +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. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#include "config.h" -#include "PlaylistState.hxx" -#include "PlaylistError.hxx" -#include "Playlist.hxx" -#include "QueueSave.hxx" -#include "TextFile.hxx" -#include "PlayerControl.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "fs/Limits.hxx" -#include "util/CharUtil.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <string.h> -#include <stdlib.h> - -#define PLAYLIST_STATE_FILE_STATE "state: " -#define PLAYLIST_STATE_FILE_RANDOM "random: " -#define PLAYLIST_STATE_FILE_REPEAT "repeat: " -#define PLAYLIST_STATE_FILE_SINGLE "single: " -#define PLAYLIST_STATE_FILE_CONSUME "consume: " -#define PLAYLIST_STATE_FILE_CURRENT "current: " -#define PLAYLIST_STATE_FILE_TIME "time: " -#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " -#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " -#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " -#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" -#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" - -#define PLAYLIST_STATE_FILE_STATE_PLAY "play" -#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" -#define PLAYLIST_STATE_FILE_STATE_STOP "stop" - -#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX - -void -playlist_state_save(FILE *fp, const struct playlist &playlist, - PlayerControl &pc) -{ - const auto player_status = pc.GetStatus(); - - fputs(PLAYLIST_STATE_FILE_STATE, fp); - - if (playlist.playing) { - switch (player_status.state) { - case PlayerState::PAUSE: - fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); - break; - default: - fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); - } - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - playlist.queue.OrderToPosition(playlist.current)); - fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", - (int)player_status.elapsed_time); - } else { - fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); - - if (playlist.current >= 0) - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - playlist.queue.OrderToPosition(playlist.current)); - } - - fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random); - fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat); - fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single); - fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", - playlist.queue.consume); - fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", - (int)pc.GetCrossFade()); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", - pc.GetMixRampDb()); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc.GetMixRampDelay()); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); - queue_save(fp, playlist.queue); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); -} - -static void -playlist_state_load(TextFile &file, struct playlist &playlist) -{ - const char *line = file.ReadLine(); - if (line == nullptr) { - LogWarning(playlist_domain, "No playlist in state file"); - return; - } - - while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - queue_load_song(file, line, playlist.queue); - - line = file.ReadLine(); - if (line == nullptr) { - LogWarning(playlist_domain, - "'" PLAYLIST_STATE_FILE_PLAYLIST_END - "' not found in state file"); - break; - } - } - - playlist.queue.IncrementVersion(); -} - -bool -playlist_state_restore(const char *line, TextFile &file, - struct playlist &playlist, PlayerControl &pc) -{ - int current = -1; - int seek_time = 0; - bool random_mode = false; - - if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) - return false; - - line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; - - PlayerState state; - if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) - state = PlayerState::PLAY; - else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) - state = PlayerState::PAUSE; - else - state = PlayerState::STOP; - - while ((line = file.ReadLine()) != nullptr) { - if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { - seek_time = - atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { - playlist.SetRepeat(pc, - strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), - "1") == 0); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { - playlist.SetSingle(pc, - strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), - "1") == 0); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { - playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), - "1") == 0); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { - pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { - pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { - const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY); - - /* this check discards "nan" which was used - prior to MPD 0.18 */ - if (IsDigitASCII(*p)) - pc.SetMixRampDelay(atof(p)); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { - random_mode = - strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), - "1") == 0; - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) { - current = atoi(&(line - [strlen - (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (g_str_has_prefix(line, - PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(file, playlist); - } - } - - playlist.SetRandom(pc, random_mode); - - if (!playlist.queue.IsEmpty()) { - if (!playlist.queue.IsValidPosition(current)) - current = 0; - - if (state == PlayerState::PLAY && - config_get_bool(CONF_RESTORE_PAUSED, false)) - /* the user doesn't want MPD to auto-start - playback after startup; fall back to - "pause" */ - state = PlayerState::PAUSE; - - /* enable all devices for the first time; this must be - called here, after the audio output states were - restored, before playback begins */ - if (state != PlayerState::STOP) - pc.UpdateAudio(); - - if (state == PlayerState::STOP /* && config_option */) - playlist.current = current; - else if (seek_time == 0) - playlist.PlayPosition(pc, current); - else - playlist.SeekSongPosition(pc, current, seek_time); - - if (state == PlayerState::PAUSE) - pc.Pause(); - } - - return true; -} - -unsigned -playlist_state_get_hash(const playlist &playlist, - PlayerControl &pc) -{ - const auto player_status = pc.GetStatus(); - - return playlist.queue.version ^ - (player_status.state != PlayerState::STOP - ? ((int)player_status.elapsed_time << 8) - : 0) ^ - (playlist.current >= 0 - ? (playlist.queue.OrderToPosition(playlist.current) << 16) - : 0) ^ - ((int)pc.GetCrossFade() << 20) ^ - (unsigned(player_status.state) << 24) ^ - (playlist.queue.random << 27) ^ - (playlist.queue.repeat << 28) ^ - (playlist.queue.single << 29) ^ - (playlist.queue.consume << 30) ^ - (playlist.queue.random << 31); -} diff --git a/src/PlaylistState.hxx b/src/PlaylistState.hxx deleted file mode 100644 index 01cc94d03..000000000 --- a/src/PlaylistState.hxx +++ /dev/null @@ -1,52 +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. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#ifndef MPD_PLAYLIST_STATE_HXX -#define MPD_PLAYLIST_STATE_HXX - -#include <stdio.h> - -struct playlist; -struct PlayerControl; -class TextFile; - -void -playlist_state_save(FILE *fp, const struct playlist &playlist, - PlayerControl &pc); - -bool -playlist_state_restore(const char *line, TextFile &file, - struct playlist &playlist, PlayerControl &pc); - -/** - * Generates a hash number for the current state of the playlist and - * the playback options. This is used by timer_save_state_file() to - * determine whether the state has changed and the state file should - * be saved. - */ -unsigned -playlist_state_get_hash(const struct playlist &playlist, - PlayerControl &c); - -#endif diff --git a/src/PlaylistUpdate.cxx b/src/PlaylistUpdate.cxx deleted file mode 100644 index 0e72ef671..000000000 --- a/src/PlaylistUpdate.cxx +++ /dev/null @@ -1,80 +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 "Playlist.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "Idle.hxx" -#include "util/Error.hxx" - -static bool -UpdatePlaylistSong(const Database &db, Song &song) -{ - if (!song.IsInDatabase() || !song.IsDetached()) - /* only update Songs instances that are "detached" - from the Database */ - return false; - - Song *original = db.GetSong(song.uri, IgnoreError()); - if (original == nullptr) - /* not found - shouldn't happen, because the update - thread should ensure that all stale Song instances - have been purged */ - return false; - - if (original->mtime == song.mtime) { - /* not modified */ - db.ReturnSong(original); - return false; - } - - song.mtime = original->mtime; - - if (original->tag != nullptr) - song.ReplaceTag(Tag(*original->tag)); - - db.ReturnSong(original); - return true; -} - -void -playlist::DatabaseModified() -{ - const Database *db = GetDatabase(); - if (db == nullptr) - /* how can this ever happen? */ - return; - - bool modified = false; - - for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) { - if (UpdatePlaylistSong(*db, queue.Get(i))) { - queue.ModifyAtPosition(i); - modified = true; - } - } - - if (modified) { - queue.IncrementVersion(); - idle_add(IDLE_PLAYLIST); - } -} diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx deleted file mode 100644 index 5bb0cdf64..000000000 --- a/src/PlaylistVector.cxx +++ /dev/null @@ -1,67 +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 "PlaylistVector.hxx" -#include "DatabaseLock.hxx" - -#include <algorithm> - -#include <assert.h> -#include <string.h> - -PlaylistVector::iterator -PlaylistVector::find(const char *name) -{ - assert(holding_db_lock()); - assert(name != nullptr); - - return std::find_if(begin(), end(), - PlaylistInfo::CompareName(name)); -} - -bool -PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi) -{ - assert(holding_db_lock()); - - auto i = find(pi.name.c_str()); - if (i != end()) { - if (pi.mtime == i->mtime) - return false; - - i->mtime = pi.mtime; - } else - push_back(std::move(pi)); - - return true; -} - -bool -PlaylistVector::erase(const char *name) -{ - assert(holding_db_lock()); - - auto i = find(name); - if (i == end()) - return false; - - erase(i); - return true; -} diff --git a/src/PlaylistVector.hxx b/src/PlaylistVector.hxx deleted file mode 100644 index 8ef8e44c7..000000000 --- a/src/PlaylistVector.hxx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_VECTOR_HXX -#define MPD_PLAYLIST_VECTOR_HXX - -#include "PlaylistInfo.hxx" -#include "Compiler.h" - -#include <list> - -class PlaylistVector : protected std::list<PlaylistInfo> { -protected: - /** - * Caller must lock the #db_mutex. - */ - gcc_pure - iterator find(const char *name); - -public: - using std::list<PlaylistInfo>::empty; - using std::list<PlaylistInfo>::begin; - using std::list<PlaylistInfo>::end; - using std::list<PlaylistInfo>::push_back; - using std::list<PlaylistInfo>::erase; - - /** - * Caller must lock the #db_mutex. - * - * @return true if the vector or one of its items was modified - */ - bool UpdateOrInsert(PlaylistInfo &&pi); - - /** - * Caller must lock the #db_mutex. - */ - bool erase(const char *name); -}; - -#endif /* SONGVEC_H */ diff --git a/src/Queue.cxx b/src/Queue.cxx deleted file mode 100644 index 92beefd0e..000000000 --- a/src/Queue.cxx +++ /dev/null @@ -1,490 +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 "Queue.hxx" -#include "Song.hxx" - -#include <stdlib.h> - -Queue::Queue(unsigned _max_length) - :max_length(_max_length), length(0), - version(1), - items(new Item[max_length]), - order(new unsigned[max_length]), - id_table(max_length * HASH_MULT), - repeat(false), - single(false), - consume(false), - random(false) -{ -} - -Queue::~Queue() -{ - Clear(); - - delete[] items; - delete[] order; -} - -int -Queue::GetNextOrder(unsigned _order) const -{ - assert(_order < length); - - if (single && repeat && !consume) - return _order; - else if (_order + 1 < length) - return _order + 1; - else if (repeat && (_order > 0 || !consume)) - /* restart at first song */ - return 0; - else - /* end of queue */ - return -1; -} - -void -Queue::IncrementVersion() -{ - static unsigned long max = ((uint32_t) 1 << 31) - 1; - - version++; - - if (version >= max) { - for (unsigned i = 0; i < length; i++) - items[i].version = 0; - - version = 1; - } -} - -void -Queue::ModifyAtOrder(unsigned _order) -{ - assert(_order < length); - - unsigned position = order[_order]; - ModifyAtPosition(position); -} - -unsigned -Queue::Append(Song *song, uint8_t priority) -{ - assert(!IsFull()); - - const unsigned position = length++; - const unsigned id = id_table.Insert(position); - - auto &item = items[position]; - item.song = song->DupDetached(); - item.id = id; - item.version = version; - item.priority = priority; - - order[position] = position; - - return id; -} - -void -Queue::SwapPositions(unsigned position1, unsigned position2) -{ - unsigned id1 = items[position1].id; - unsigned id2 = items[position2].id; - - std::swap(items[position1], items[position2]); - - items[position1].version = version; - items[position2].version = version; - - id_table.Move(id1, position2); - id_table.Move(id2, position1); -} - -void -Queue::MovePostion(unsigned from, unsigned to) -{ - const Item tmp = items[from]; - - /* move songs to one less in from->to */ - - for (unsigned i = from; i < to; i++) - MoveItemTo(i + 1, i); - - /* move songs to one more in to->from */ - - for (unsigned i = from; i > to; i--) - MoveItemTo(i - 1, i); - - /* put song at _to_ */ - - id_table.Move(tmp.id, to); - items[to] = tmp; - items[to].version = version; - - /* now deal with order */ - - if (random) { - for (unsigned i = 0; i < length; i++) { - if (order[i] > from && order[i] <= to) - order[i]--; - else if (order[i] < from && - order[i] >= to) - order[i]++; - else if (from == order[i]) - order[i] = to; - } - } -} - -void -Queue::MoveRange(unsigned start, unsigned end, unsigned to) -{ - Item tmp[end - start]; - // Copy the original block [start,end-1] - for (unsigned i = start; i < end; i++) - tmp[i - start] = items[i]; - - // If to > start, we need to move to-start items to start, starting from end - for (unsigned i = end; i < end + to - start; i++) - MoveItemTo(i, start + i - end); - - // If to < start, we need to move start-to items to newend (= end + to - start), starting from to - // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 - // We have to iterate in this order to avoid writing over something we haven't yet moved - for (int i = start - 1; i >= int(to); i--) - MoveItemTo(i, i + end - start); - - // Copy the original block back in, starting at to. - for (unsigned i = start; i< end; i++) - { - id_table.Move(tmp[i - start].id, to + i - start); - items[to + i - start] = tmp[i-start]; - items[to + i - start].version = version; - } - - if (random) { - // Update the positions in the queue. - // Note that the ranges for these cases are the same as the ranges of - // the loops above. - for (unsigned i = 0; i < length; i++) { - if (order[i] >= end && order[i] < to + end - start) - order[i] -= end - start; - else if (order[i] < start && - order[i] >= to) - order[i] += end - start; - else if (start <= order[i] && order[i] < end) - order[i] += to - start; - } - } -} - -void -Queue::MoveOrder(unsigned from_order, unsigned to_order) -{ - assert(from_order < length); - assert(to_order <= length); - - const unsigned from_position = OrderToPosition(from_order); - - if (from_order < to_order) { - for (unsigned i = from_order; i < to_order; ++i) - order[i] = order[i + 1]; - } else { - for (unsigned i = from_order; i > to_order; --i) - order[i] = order[i - 1]; - } - - order[to_order] = from_position; -} - -void -Queue::DeletePosition(unsigned position) -{ - assert(position < length); - - { - Song &song = Get(position); - assert(!song.IsInDatabase() || song.IsDetached()); - song.Free(); - } - - const unsigned id = PositionToId(position); - const unsigned _order = PositionToOrder(position); - - --length; - - /* release the song id */ - - id_table.Erase(id); - - /* delete song from songs array */ - - for (unsigned i = position; i < length; i++) - MoveItemTo(i + 1, i); - - /* delete the entry from the order array */ - - for (unsigned i = _order; i < length; i++) - order[i] = order[i + 1]; - - /* readjust values in the order array */ - - for (unsigned i = 0; i < length; i++) - if (order[i] > position) - --order[i]; -} - -void -Queue::Clear() -{ - for (unsigned i = 0; i < length; i++) { - Item *item = &items[i]; - - assert(!item->song->IsInDatabase() || - item->song->IsDetached()); - item->song->Free(); - - id_table.Erase(item->id); - } - - length = 0; -} - -static void -queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end) -{ - assert(queue != nullptr); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - auto cmp = [queue](unsigned a_pos, unsigned b_pos){ - const Queue::Item &a = queue->items[a_pos]; - const Queue::Item &b = queue->items[b_pos]; - - return a.priority > b.priority; - }; - - std::stable_sort(queue->order + start, queue->order + end, cmp); -} - -void -Queue::ShuffleOrderRange(unsigned start, unsigned end) -{ - assert(random); - assert(start <= end); - assert(end <= length); - - rand.AutoCreate(); - std::shuffle(order + start, order + end, rand); -} - -/** - * Sort the "order" of items by priority, and then shuffle each - * priority group. - */ -void -Queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end) -{ - assert(random); - assert(start <= end); - assert(end <= length); - - if (start == end) - return; - - /* first group the range by priority */ - queue_sort_order_by_priority(this, start, end); - - /* now shuffle each priority group */ - unsigned group_start = start; - uint8_t group_priority = GetOrderPriority(start); - - for (unsigned i = start + 1; i < end; ++i) { - const uint8_t priority = GetOrderPriority(i); - assert(priority <= group_priority); - - if (priority != group_priority) { - /* start of a new group - shuffle the one that - has just ended */ - ShuffleOrderRange(group_start, i); - group_start = i; - group_priority = priority; - } - } - - /* shuffle the last group */ - ShuffleOrderRange(group_start, end); -} - -void -Queue::ShuffleOrder() -{ - ShuffleOrderRangeWithPriority(0, length); -} - -void -Queue::ShuffleOrderFirst(unsigned start, unsigned end) -{ - rand.AutoCreate(); - - std::uniform_int_distribution<unsigned> distribution(start, end - 1); - SwapOrders(start, distribution(rand)); -} - -void -Queue::ShuffleOrderLast(unsigned start, unsigned end) -{ - rand.AutoCreate(); - - std::uniform_int_distribution<unsigned> distribution(start, end - 1); - SwapOrders(end - 1, distribution(rand)); -} - -void -Queue::ShuffleRange(unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= length); - - rand.AutoCreate(); - - for (unsigned i = start; i < end; i++) { - std::uniform_int_distribution<unsigned> distribution(start, - end - 1); - unsigned ri = distribution(rand); - SwapPositions(i, ri); - } -} - -unsigned -Queue::FindPriorityOrder(unsigned start_order, uint8_t priority, - unsigned exclude_order) const -{ - assert(random); - assert(start_order <= length); - - for (unsigned i = start_order; i < length; ++i) { - const unsigned position = OrderToPosition(i); - const Item *item = &items[position]; - if (item->priority <= priority && i != exclude_order) - return i; - } - - return length; -} - -unsigned -Queue::CountSamePriority(unsigned start_order, uint8_t priority) const -{ - assert(random); - assert(start_order <= length); - - for (unsigned i = start_order; i < length; ++i) { - const unsigned position = OrderToPosition(i); - const Item *item = &items[position]; - if (item->priority != priority) - return i - start_order; - } - - return length - start_order; -} - -bool -Queue::SetPriority(unsigned position, uint8_t priority, int after_order) -{ - assert(position < length); - - Item *item = &items[position]; - uint8_t old_priority = item->priority; - if (old_priority == priority) - return false; - - item->version = version; - item->priority = priority; - - if (!random) - /* don't reorder if not in random mode */ - return true; - - unsigned _order = PositionToOrder(position); - if (after_order >= 0) { - if (_order == (unsigned)after_order) - /* don't reorder the current song */ - return true; - - if (_order < (unsigned)after_order) { - /* the specified song has been played already - - enqueue it only if its priority has just - become bigger than the current one's */ - - const unsigned after_position = - OrderToPosition(after_order); - const Item *after_item = - &items[after_position]; - if (old_priority > after_item->priority || - priority <= after_item->priority) - /* priority hasn't become bigger */ - return true; - } - } - - /* move the item to the beginning of the priority group (or - create a new priority group) */ - - const unsigned before_order = - FindPriorityOrder(after_order + 1, priority, _order); - const unsigned new_order = before_order > _order - ? before_order - 1 - : before_order; - MoveOrder(_order, new_order); - - /* shuffle the song within that priority group */ - - const unsigned priority_count = CountSamePriority(new_order, priority); - assert(priority_count >= 1); - ShuffleOrderFirst(new_order, new_order + priority_count); - - return true; -} - -bool -Queue::SetPriorityRange(unsigned start_position, unsigned end_position, - uint8_t priority, int after_order) -{ - assert(start_position <= end_position); - assert(end_position <= length); - - bool modified = false; - int after_position = after_order >= 0 - ? (int)OrderToPosition(after_order) - : -1; - for (unsigned i = start_position; i < end_position; ++i) { - after_order = after_position >= 0 - ? (int)PositionToOrder(after_position) - : -1; - - modified |= SetPriority(i, priority, after_order); - } - - return modified; -} diff --git a/src/Queue.hxx b/src/Queue.hxx deleted file mode 100644 index 3c6001b4f..000000000 --- a/src/Queue.hxx +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_QUEUE_HXX -#define MPD_QUEUE_HXX - -#include "Compiler.h" -#include "IdTable.hxx" -#include "util/LazyRandomEngine.hxx" - -#include <algorithm> - -#include <assert.h> -#include <stdint.h> - -struct Song; - -/** - * A queue of songs. This is the backend of the playlist: it contains - * an ordered list of songs. - * - * Songs can be addressed in three possible ways: - * - * - the position in the queue - * - the unique id (which stays the same, regardless of moves) - * - the order number (which only differs from "position" in random mode) - */ -struct Queue { - /** - * reserve max_length * HASH_MULT elements in the id - * number space - */ - static constexpr unsigned HASH_MULT = 4; - - /** - * One element of the queue: basically a song plus some queue specific - * information attached. - */ - struct Item { - Song *song; - - /** the unique id of this item in the queue */ - unsigned id; - - /** when was this item last changed? */ - uint32_t version; - - /** - * The priority of this item, between 0 and 255. High - * priority value means that this song gets played first in - * "random" mode. - */ - uint8_t priority; - }; - - /** configured maximum length of the queue */ - unsigned max_length; - - /** number of songs in the queue */ - unsigned length; - - /** the current version number */ - uint32_t version; - - /** all songs in "position" order */ - Item *items; - - /** map order numbers to positions */ - unsigned *order; - - /** map song ids to positions */ - IdTable id_table; - - /** repeat playback when the end of the queue has been - reached? */ - bool repeat; - - /** play only current song. */ - bool single; - - /** remove each played files. */ - bool consume; - - /** play back songs in random order? */ - bool random; - - /** random number generator for shuffle and random mode */ - LazyRandomEngine rand; - - explicit Queue(unsigned max_length); - - /** - * Deinitializes a queue object. It does not free the queue - * pointer itself. - */ - ~Queue(); - - Queue(const Queue &) = delete; - Queue &operator=(const Queue &) = delete; - - unsigned GetLength() const { - assert(length <= max_length); - - return length; - } - - /** - * Determine if the queue is empty, i.e. there are no songs. - */ - bool IsEmpty() const { - return length == 0; - } - - /** - * Determine if the maximum number of songs has been reached. - */ - bool IsFull() const { - assert(length <= max_length); - - return length >= max_length; - } - - /** - * Is that a valid position number? - */ - bool IsValidPosition(unsigned position) const { - return position < length; - } - - /** - * Is that a valid order number? - */ - bool IsValidOrder(unsigned _order) const { - return _order < length; - } - - int IdToPosition(unsigned id) const { - return id_table.IdToPosition(id); - } - - int PositionToId(unsigned position) const - { - assert(position < length); - - return items[position].id; - } - - gcc_pure - unsigned OrderToPosition(unsigned _order) const { - assert(_order < length); - - return order[_order]; - } - - gcc_pure - unsigned PositionToOrder(unsigned position) const { - assert(position < length); - - for (unsigned i = 0;; ++i) { - assert(i < length); - - if (order[i] == position) - return i; - } - } - - gcc_pure - uint8_t GetPriorityAtPosition(unsigned position) const { - assert(position < length); - - return items[position].priority; - } - - const Item &GetOrderItem(unsigned i) const { - assert(IsValidOrder(i)); - - return items[OrderToPosition(i)]; - } - - uint8_t GetOrderPriority(unsigned i) const { - return GetOrderItem(i).priority; - } - - /** - * Returns the song at the specified position. - */ - Song &Get(unsigned position) const { - assert(position < length); - - return *items[position].song; - } - - /** - * Returns the song at the specified order number. - */ - Song &GetOrder(unsigned _order) const { - return Get(OrderToPosition(_order)); - } - - /** - * Is the song at the specified position newer than the specified - * version? - */ - bool IsNewerAtPosition(unsigned position, uint32_t _version) const { - assert(position < length); - - return _version > version || - items[position].version >= _version || - items[position].version == 0; - } - - /** - * Returns the order number following the specified one. This takes - * end of queue and "repeat" mode into account. - * - * @return the next order number, or -1 to stop playback - */ - gcc_pure - int GetNextOrder(unsigned order) const; - - /** - * Increments the queue's version number. This handles integer - * overflow well. - */ - void IncrementVersion(); - - /** - * Marks the specified song as "modified". Call - * IncrementVersion() after all modifications have been made. - * number. - */ - void ModifyAtPosition(unsigned position) { - assert(position < length); - - items[position].version = version; - } - - /** - * Marks the specified song as "modified". Call - * IncrementVersion() after all modifications have been made. - * number. - */ - void ModifyAtOrder(unsigned order); - - /** - * Appends a song to the queue and returns its position. Prior to - * that, the caller must check if the queue is already full. - * - * If a song is not in the database (determined by - * Song::IsInDatabase()), it is freed when removed from the - * queue. - * - * @param priority the priority of this new queue item - */ - unsigned Append(Song *song, uint8_t priority); - - /** - * Swaps two songs, addressed by their position. - */ - void SwapPositions(unsigned position1, unsigned position2); - - /** - * Swaps two songs, addressed by their order number. - */ - void SwapOrders(unsigned order1, unsigned order2) { - std::swap(order[order1], order[order2]); - } - - /** - * Moves a song to a new position. - */ - void MovePostion(unsigned from, unsigned to); - - /** - * Moves a range of songs to a new position. - */ - void MoveRange(unsigned start, unsigned end, unsigned to); - - /** - * Removes a song from the playlist. - */ - void DeletePosition(unsigned position); - - /** - * Removes all songs from the playlist. - */ - void Clear(); - - /** - * Initializes the "order" array, and restores "normal" order. - */ - void RestoreOrder() { - for (unsigned i = 0; i < length; ++i) - order[i] = i; - } - - /** - * Shuffle the order of items in the specified range, ignoring - * their priorities. - */ - void ShuffleOrderRange(unsigned start, unsigned end); - - /** - * Shuffle the order of items in the specified range, taking their - * priorities into account. - */ - void ShuffleOrderRangeWithPriority(unsigned start, unsigned end); - - /** - * Shuffles the virtual order of songs, but does not move them - * physically. This is used in random mode. - */ - void ShuffleOrder(); - - void ShuffleOrderFirst(unsigned start, unsigned end); - - /** - * Shuffles the virtual order of the last song in the specified - * (order) range. This is used in random mode after a song has been - * appended by queue_append(). - */ - void ShuffleOrderLast(unsigned start, unsigned end); - - /** - * Shuffles a (position) range in the queue. The songs are physically - * shuffled, not by using the "order" mapping. - */ - void ShuffleRange(unsigned start, unsigned end); - - bool SetPriority(unsigned position, uint8_t priority, int after_order); - - bool SetPriorityRange(unsigned start_position, unsigned end_position, - uint8_t priority, int after_order); - -private: - /** - * Moves a song to a new position in the "order" list. - */ - void MoveOrder(unsigned from_order, unsigned to_order); - - void MoveItemTo(unsigned from, unsigned to) { - unsigned from_id = items[from].id; - - items[to] = items[from]; - items[to].version = version; - id_table.Move(from_id, to); - } - - /** - * Find the first item that has this specified priority or - * higher. - */ - gcc_pure - unsigned FindPriorityOrder(unsigned start_order, uint8_t priority, - unsigned exclude_order) const; - - gcc_pure - unsigned CountSamePriority(unsigned start_order, - uint8_t priority) const; -}; - -#endif diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx deleted file mode 100644 index 89f3c0ad3..000000000 --- a/src/QueuePrint.cxx +++ /dev/null @@ -1,107 +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 "QueuePrint.hxx" -#include "Queue.hxx" -#include "SongFilter.hxx" -#include "SongPrint.hxx" -#include "Mapper.hxx" -#include "Client.hxx" - -extern "C" { -#include "Song.hxx" -} - -/** - * Send detailed information about a range of songs in the queue to a - * client. - * - * @param client the client which has requested information - * @param start the index of the first song (including) - * @param end the index of the last song (excluding) - */ -static void -queue_print_song_info(Client &client, const Queue &queue, - unsigned position) -{ - song_print_info(client, queue.Get(position)); - client_printf(client, "Pos: %u\nId: %u\n", - position, queue.PositionToId(position)); - - uint8_t priority = queue.GetPriorityAtPosition(position); - if (priority != 0) - client_printf(client, "Prio: %u\n", priority); -} - -void -queue_print_info(Client &client, const Queue &queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue.GetLength()); - - for (unsigned i = start; i < end; ++i) - queue_print_song_info(client, queue, i); -} - -void -queue_print_uris(Client &client, const Queue &queue, - unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue.GetLength()); - - for (unsigned i = start; i < end; ++i) { - client_printf(client, "%i:", i); - song_print_uri(client, queue.Get(i)); - } -} - -void -queue_print_changes_info(Client &client, const Queue &queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue.GetLength(); i++) { - if (queue.IsNewerAtPosition(i, version)) - queue_print_song_info(client, queue, i); - } -} - -void -queue_print_changes_position(Client &client, const Queue &queue, - uint32_t version) -{ - for (unsigned i = 0; i < queue.GetLength(); i++) - if (queue.IsNewerAtPosition(i, version)) - client_printf(client, "cpos: %i\nId: %i\n", - i, queue.PositionToId(i)); -} - -void -queue_find(Client &client, const Queue &queue, - const SongFilter &filter) -{ - for (unsigned i = 0; i < queue.GetLength(); i++) { - const Song &song = queue.Get(i); - - if (filter.Match(song)) - queue_print_song_info(client, queue, i); - } -} diff --git a/src/QueuePrint.hxx b/src/QueuePrint.hxx deleted file mode 100644 index bc91bb5de..000000000 --- a/src/QueuePrint.hxx +++ /dev/null @@ -1,54 +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. - */ - -/* - * This library sends information about songs in the queue to the - * client. - */ - -#ifndef MPD_QUEUE_PRINT_HXX -#define MPD_QUEUE_PRINT_HXX - -#include <stdint.h> - -struct Queue; -class SongFilter; -class Client; - -void -queue_print_info(Client &client, const Queue &queue, - unsigned start, unsigned end); - -void -queue_print_uris(Client &client, const Queue &queue, - unsigned start, unsigned end); - -void -queue_print_changes_info(Client &client, const Queue &queue, - uint32_t version); - -void -queue_print_changes_position(Client &client, const Queue &queue, - uint32_t version); - -void -queue_find(Client &client, const Queue &queue, - const SongFilter &filter); - -#endif diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx deleted file mode 100644 index 2ab5f6280..000000000 --- a/src/QueueSave.cxx +++ /dev/null @@ -1,133 +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 "QueueSave.hxx" -#include "Queue.hxx" -#include "PlaylistError.hxx" -#include "Song.hxx" -#include "SongSave.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseGlue.hxx" -#include "TextFile.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "fs/Traits.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <stdlib.h> - -#define PRIO_LABEL "Prio: " - -static void -queue_save_database_song(FILE *fp, int idx, const Song &song) -{ - const auto uri = song.GetURI(); - fprintf(fp, "%i:%s\n", idx, uri.c_str()); -} - -static void -queue_save_full_song(FILE *fp, const Song &song) -{ - song_save(fp, song); -} - -static void -queue_save_song(FILE *fp, int idx, const Song &song) -{ - if (song.IsInDatabase()) - queue_save_database_song(fp, idx, song); - else - queue_save_full_song(fp, song); -} - -void -queue_save(FILE *fp, const Queue &queue) -{ - for (unsigned i = 0; i < queue.GetLength(); i++) { - uint8_t prio = queue.GetPriorityAtPosition(i); - if (prio != 0) - fprintf(fp, PRIO_LABEL "%u\n", prio); - - queue_save_song(fp, i, queue.Get(i)); - } -} - -void -queue_load_song(TextFile &file, const char *line, Queue &queue) -{ - if (queue.IsFull()) - return; - - uint8_t priority = 0; - if (g_str_has_prefix(line, PRIO_LABEL)) { - priority = strtoul(line + sizeof(PRIO_LABEL) - 1, nullptr, 10); - - line = file.ReadLine(); - if (line == nullptr) - return; - } - - const Database *db = nullptr; - Song *song; - - if (g_str_has_prefix(line, SONG_BEGIN)) { - const char *uri = line + sizeof(SONG_BEGIN) - 1; - if (!uri_has_scheme(uri) && !PathTraits::IsAbsoluteUTF8(uri)) - return; - - Error error; - song = song_load(file, nullptr, uri, error); - if (song == nullptr) { - LogError(error); - return; - } - } else { - char *endptr; - long ret = strtol(line, &endptr, 10); - if (ret < 0 || *endptr != ':' || endptr[1] == 0) { - LogError(playlist_domain, - "Malformed playlist line in state file"); - return; - } - - const char *uri = endptr + 1; - - if (uri_has_scheme(uri)) { - song = Song::NewRemote(uri); - } else { - db = GetDatabase(); - if (db == nullptr) - return; - - song = db->GetSong(uri, IgnoreError()); - if (song == nullptr) - return; - } - } - - queue.Append(song, priority); - - if (db != nullptr) - db->ReturnSong(song); - else - song->Free(); -} diff --git a/src/QueueSave.hxx b/src/QueueSave.hxx deleted file mode 100644 index 6c618c0dc..000000000 --- a/src/QueueSave.hxx +++ /dev/null @@ -1,42 +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. - */ - -/* - * This library saves the queue into the state file, and also loads it - * back into memory. - */ - -#ifndef MPD_QUEUE_SAVE_HXX -#define MPD_QUEUE_SAVE_HXX - -#include <stdio.h> - -struct Queue; -class TextFile; - -void -queue_save(FILE *fp, const Queue &queue); - -/** - * Loads one song from the state file and appends it to the queue. - */ -void -queue_load_song(TextFile &file, const char *line, Queue &queue); - -#endif diff --git a/src/ReplayGainConfig.cxx b/src/ReplayGainConfig.cxx index 8f9b0d3f7..c3bbcac3c 100644 --- a/src/ReplayGainConfig.cxx +++ b/src/ReplayGainConfig.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 @@ -20,14 +20,14 @@ #include "config.h" #include "ReplayGainConfig.hxx" #include "Idle.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "Playlist.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" #include "system/FatalError.hxx" #include <assert.h> #include <stdlib.h> #include <string.h> +#include <math.h> ReplayGainMode replay_gain_mode = REPLAY_GAIN_OFF; diff --git a/src/ReplayGainConfig.hxx b/src/ReplayGainConfig.hxx index 7777a859b..e498a56dd 100644 --- a/src/ReplayGainConfig.hxx +++ b/src/ReplayGainConfig.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/ReplayGainInfo.cxx b/src/ReplayGainInfo.cxx index cf6b0ba69..580773521 100644 --- a/src/ReplayGainInfo.cxx +++ b/src/ReplayGainInfo.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 diff --git a/src/ReplayGainInfo.hxx b/src/ReplayGainInfo.hxx index b8dfd16a1..37815c933 100644 --- a/src/ReplayGainInfo.hxx +++ b/src/ReplayGainInfo.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/SignalHandlers.cxx b/src/SignalHandlers.cxx deleted file mode 100644 index 542239083..000000000 --- a/src/SignalHandlers.cxx +++ /dev/null @@ -1,82 +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 "SignalHandlers.hxx" -#include "event/SignalMonitor.hxx" - -#ifndef WIN32 - -#include "Log.hxx" -#include "LogInit.hxx" -#include "event/Loop.hxx" -#include "system/FatalError.hxx" -#include "util/Domain.hxx" - -#include <signal.h> - -static constexpr Domain signal_handlers_domain("signal_handlers"); - -static void -HandleShutdownSignal() -{ - SignalMonitorGetEventLoop().Break(); -} - -static void -x_sigaction(int signum, const struct sigaction *act) -{ - if (sigaction(signum, act, NULL) < 0) - FatalSystemError("sigaction() failed"); -} - -static void -handle_reload_event(void) -{ - LogDebug(signal_handlers_domain, "got SIGHUP, reopening log files"); - cycle_log_files(); -} - -#endif - -void -SignalHandlersInit(EventLoop &loop) -{ - SignalMonitorInit(loop); - -#ifndef WIN32 - struct sigaction sa; - - sa.sa_flags = 0; - sigemptyset(&sa.sa_mask); - sa.sa_handler = SIG_IGN; - x_sigaction(SIGPIPE, &sa); - - SignalMonitorRegister(SIGINT, HandleShutdownSignal); - SignalMonitorRegister(SIGTERM, HandleShutdownSignal); - - SignalMonitorRegister(SIGHUP, handle_reload_event); -#endif -} - -void -SignalHandlersFinish() -{ - SignalMonitorFinish(); -} diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx deleted file mode 100644 index 90dab6dec..000000000 --- a/src/SignalHandlers.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIGNAL_HANDLERS_HXX -#define MPD_SIGNAL_HANDLERS_HXX - -class EventLoop; - -void -SignalHandlersInit(EventLoop &loop); - -void -SignalHandlersFinish(); - -#endif diff --git a/src/Song.cxx b/src/Song.cxx deleted file mode 100644 index 6213d5e66..000000000 --- a/src/Song.cxx +++ /dev/null @@ -1,183 +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 "Song.hxx" -#include "Directory.hxx" -#include "tag/Tag.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -Directory detached_root; - -static Song * -song_alloc(const char *uri, Directory *parent) -{ - size_t uri_length; - - assert(uri); - uri_length = strlen(uri); - assert(uri_length); - - Song *song = (Song *) - g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); - - song->tag = nullptr; - memcpy(song->uri, uri, uri_length + 1); - song->parent = parent; - song->mtime = 0; - song->start_ms = song->end_ms = 0; - - return song; -} - -Song * -Song::NewRemote(const char *uri) -{ - return song_alloc(uri, nullptr); -} - -Song * -Song::NewFile(const char *path, Directory *parent) -{ - assert((parent == nullptr) == (*path == '/')); - - return song_alloc(path, parent); -} - -Song * -Song::ReplaceURI(const char *new_uri) -{ - Song *new_song = song_alloc(new_uri, parent); - new_song->tag = tag; - new_song->mtime = mtime; - new_song->start_ms = start_ms; - new_song->end_ms = end_ms; - g_free(this); - return new_song; -} - -Song * -Song::NewDetached(const char *uri) -{ - assert(uri != nullptr); - - return song_alloc(uri, &detached_root); -} - -Song * -Song::DupDetached() const -{ - Song *song; - if (IsInDatabase()) { - const auto new_uri = GetURI(); - song = NewDetached(new_uri.c_str()); - } else - song = song_alloc(uri, nullptr); - - song->tag = tag != nullptr ? new Tag(*tag) : nullptr; - song->mtime = mtime; - song->start_ms = start_ms; - song->end_ms = end_ms; - - return song; -} - -void -Song::Free() -{ - delete tag; - g_free(this); -} - -void -Song::ReplaceTag(Tag &&_tag) -{ - if (tag == nullptr) - tag = new Tag(); - *tag = std::move(_tag); -} - -gcc_pure -static inline bool -directory_equals(const Directory &a, const Directory &b) -{ - return strcmp(a.path, b.path) == 0; -} - -gcc_pure -static inline bool -directory_is_same(const Directory *a, const Directory *b) -{ - return a == b || - (a != nullptr && b != nullptr && - directory_equals(*a, *b)); - -} - -bool -SongEquals(const Song &a, const Song &b) -{ - if (a.parent != nullptr && b.parent != nullptr && - !directory_equals(*a.parent, *b.parent) && - (a.parent == &detached_root || b.parent == &detached_root)) { - /* must compare the full URI if one of the objects is - "detached" */ - const auto au = a.GetURI(); - const auto bu = b.GetURI(); - return au == bu; - } - - return directory_is_same(a.parent, b.parent) && - strcmp(a.uri, b.uri) == 0; -} - -std::string -Song::GetURI() const -{ - assert(*uri); - - if (!IsInDatabase() || parent->IsRoot()) - return std::string(uri); - else { - const char *path = parent->GetPath(); - - std::string result; - result.reserve(strlen(path) + 1 + strlen(uri)); - result.assign(path); - result.push_back('/'); - result.append(uri); - return result; - } -} - -double -Song::GetDuration() const -{ - if (end_ms > 0) - return (end_ms - start_ms) / 1000.0; - - if (tag == nullptr) - return 0; - - return tag->time - start_ms / 1000.0; -} diff --git a/src/Song.hxx b/src/Song.hxx deleted file mode 100644 index b74690e77..000000000 --- a/src/Song.hxx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_HXX -#define MPD_SONG_HXX - -#include "util/list.h" -#include "Compiler.h" - -#include <string> - -#include <assert.h> -#include <sys/time.h> - -#define SONG_FILE "file: " -#define SONG_TIME "Time: " - -struct Tag; - -/** - * A dummy #directory instance that is used for "detached" song - * copies. - */ -extern struct Directory detached_root; - -struct Song { - /** - * Pointers to the siblings of this directory within the - * parent directory. It is unused (undefined) if this song is - * not in the database. - * - * This attribute is protected with the global #db_mutex. - * Read access in the update thread does not need protection. - */ - struct list_head siblings; - - Tag *tag; - Directory *parent; - time_t mtime; - - /** - * Start of this sub-song within the file in milliseconds. - */ - unsigned start_ms; - - /** - * End of this sub-song within the file in milliseconds. - * Unused if zero. - */ - unsigned end_ms; - - char uri[sizeof(int)]; - - /** allocate a new song with a remote URL */ - gcc_malloc - static Song *NewRemote(const char *uri); - - /** allocate a new song with a local file name */ - gcc_malloc - static Song *NewFile(const char *path_utf8, Directory *parent); - - /** - * allocate a new song structure with a local file name and attempt to - * load its metadata. If all decoder plugin fail to read its meta - * data, nullptr is returned. - */ - gcc_malloc - static Song *LoadFile(const char *path_utf8, Directory *parent); - - static Song *LoadFile(const char *path_utf8, Directory &parent) { - return LoadFile(path_utf8, &parent); - } - - /** - * Replaces the URI of a song object. The given song object - * is destroyed, and a newly allocated one is returned. It - * does not update the reference within the parent directory; - * the caller is responsible for doing that. - */ - gcc_malloc - Song *ReplaceURI(const char *uri); - - /** - * Creates a "detached" song object. - */ - gcc_malloc - static Song *NewDetached(const char *uri); - - /** - * Creates a duplicate of the song object. If the object is - * in the database, it creates a "detached" copy of this song, - * see Song::IsDetached(). - */ - gcc_malloc - Song *DupDetached() const; - - void Free(); - - bool IsInDatabase() const { - return parent != nullptr; - } - - bool IsFile() const { - return IsInDatabase() || uri[0] == '/'; - } - - bool IsDetached() const { - assert(IsInDatabase()); - - return parent == &detached_root; - } - - void ReplaceTag(Tag &&tag); - - bool UpdateFile(); - bool UpdateFileInArchive(); - - /** - * Returns the URI of the song in UTF-8 encoding, including its - * location within the music directory. - */ - gcc_pure - std::string GetURI() const; - - gcc_pure - double GetDuration() const; -}; - -/** - * Returns true if both objects refer to the same physical song. - */ -gcc_pure -bool -SongEquals(const Song &a, const Song &b); - -#endif diff --git a/src/SongEnumerator.hxx b/src/SongEnumerator.hxx deleted file mode 100644 index 0e268a31a..000000000 --- a/src/SongEnumerator.hxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_ENUMERATOR_HXX -#define MPD_SONG_ENUMERATOR_HXX - -struct Song; - -/** - * An object which provides serial access to a number of #Song - * objects. It is used to enumerate the contents of a playlist file. - */ -class SongEnumerator { -public: - virtual ~SongEnumerator() {} - - /** - * Obtain the next song. The caller is responsible for - * freeing the returned #Song object. Returns nullptr if - * there are no more songs. - */ - virtual Song *NextSong() = 0; -}; - -#endif diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx index 235dfe7a0..794cb9208 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.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,12 +19,13 @@ #include "config.h" #include "SongFilter.hxx" -#include "Song.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" +#include "util/ConstBuffer.hxx" #include "util/ASCII.hxx" #include "util/UriUtil.hxx" - -#include <glib.h> +#include "lib/icu/Collate.hxx" #include <assert.h> #include <string.h> @@ -47,17 +48,10 @@ locate_parse_type(const char *str) if (strcmp(str, "base") == 0) return LOCATE_TAG_BASE_TYPE; - return tag_name_parse_i(str); -} + if (strcmp(str, "modified-since") == 0) + return LOCATE_TAG_MODIFIED_SINCE; -gcc_pure -static std::string -CaseFold(const char *p) -{ - char *q = g_utf8_casefold(p, -1); - std::string result(q); - g_free(q); - return result; + return tag_name_parse_i(str); } gcc_pure @@ -65,7 +59,7 @@ static std::string ImportString(const char *p, bool fold_case) { return fold_case - ? CaseFold(p) + ? IcuCaseFold(p) : std::string(p); } @@ -75,16 +69,19 @@ SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case) { } +SongFilter::Item::Item(unsigned _tag, time_t _time) + :tag(_tag), time(_time) +{ +} + bool SongFilter::Item::StringMatch(const char *s) const { assert(s != nullptr); if (fold_case) { - char *p = g_utf8_casefold(s, -1); - const bool result = strstr(p, value.c_str()) != NULL; - g_free(p); - return result; + const std::string folded = IcuCaseFold(s); + return folded.find(value) != folded.npos; } else { return s == value; } @@ -103,10 +100,10 @@ SongFilter::Item::Match(const Tag &_tag) const bool visited_types[TAG_NUM_OF_ITEM_TYPES]; std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false); - for (unsigned i = 0; i < _tag.num_items; i++) { - visited_types[_tag.items[i]->type] = true; + for (const auto &i : _tag) { + visited_types[i.type] = true; - if (Match(*_tag.items[i])) + if (Match(i)) return true; } @@ -123,12 +120,10 @@ SongFilter::Item::Match(const Tag &_tag) const if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) { /* if we're looking for "album artist", but only "artist" exists, use that */ - for (unsigned i = 0; i < _tag.num_items; i++) { - const TagItem &item = *_tag.items[i]; + for (const auto &item : _tag) if (item.type == TAG_ARTIST && StringMatch(item.value)) return true; - } } } @@ -136,19 +131,37 @@ SongFilter::Item::Match(const Tag &_tag) const } bool -SongFilter::Item::Match(const Song &song) const +SongFilter::Item::Match(const DetachedSong &song) const +{ + if (tag == LOCATE_TAG_BASE_TYPE) + return uri_is_child_or_same(value.c_str(), song.GetURI()); + + if (tag == LOCATE_TAG_MODIFIED_SINCE) + return song.GetLastModified() >= time; + + if (tag == LOCATE_TAG_FILE_TYPE) + return StringMatch(song.GetURI()); + + return Match(song.GetTag()); +} + +bool +SongFilter::Item::Match(const LightSong &song) const { if (tag == LOCATE_TAG_BASE_TYPE) { const auto uri = song.GetURI(); return uri_is_child_or_same(value.c_str(), uri.c_str()); } + if (tag == LOCATE_TAG_MODIFIED_SINCE) + return song.mtime >= time; + if (tag == LOCATE_TAG_FILE_TYPE) { const auto uri = song.GetURI(); return StringMatch(uri.c_str()); } - return song.tag != NULL && Match(*song.tag); + return Match(*song.tag); } SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) @@ -161,6 +174,58 @@ SongFilter::~SongFilter() /* this destructor exists here just so it won't get inlined */ } +#if !defined(__GLIBC__) && !defined(WIN32) + +/** + * Determine the time zone offset in a portable way. + */ +gcc_const +static time_t +GetTimeZoneOffset() +{ + time_t t = 1234567890; + struct tm tm; + tm.tm_isdst = 0; + gmtime_r(&t, &tm); + return t - mktime(&tm); +} + +#endif + +gcc_pure +static time_t +ParseTimeStamp(const char *s) +{ + assert(s != nullptr); + + char *endptr; + unsigned long long value = strtoull(s, &endptr, 10); + if (*endptr == 0 && endptr > s) + /* it's an integral UNIX time stamp */ + return (time_t)value; + +#ifdef WIN32 + /* TODO: emulate strptime()? */ + return 0; +#else + /* try ISO 8601 */ + + struct tm tm; + const char *end = strptime(s, "%FT%TZ", &tm); + if (end == nullptr || *end != 0) + return 0; + +#ifdef __GLIBC__ + /* timegm() is a GNU extension */ + return timegm(&tm); +#else + tm.tm_isdst = 0; + return mktime(&tm) + GetTimeZoneOffset(); +#endif /* !__GLIBC__ */ + +#endif /* !WIN32 */ +} + bool SongFilter::Parse(const char *tag_string, const char *value, bool fold_case) { @@ -176,25 +241,44 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case) fold_case = false; } + if (tag == LOCATE_TAG_MODIFIED_SINCE) { + time_t t = ParseTimeStamp(value); + if (t == 0) + return false; + + items.push_back(Item(tag, t)); + return true; + } + items.push_back(Item(tag, value, fold_case)); return true; } bool -SongFilter::Parse(unsigned argc, char *argv[], bool fold_case) +SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case) { - if (argc == 0 || argc % 2 != 0) + if (args.size == 0 || args.size % 2 != 0) return false; - for (unsigned i = 0; i < argc; i += 2) - if (!Parse(argv[i], argv[i + 1], fold_case)) + for (unsigned i = 0; i < args.size; i += 2) + if (!Parse(args[i], args[i + 1], fold_case)) + return false; + + return true; +} + +bool +SongFilter::Match(const DetachedSong &song) const +{ + for (const auto &i : items) + if (!i.Match(song)) return false; return true; } bool -SongFilter::Match(const Song &song) const +SongFilter::Match(const LightSong &song) const { for (const auto &i : items) if (!i.Match(song)) diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx index 8c46ed5f3..f51bd85c6 100644 --- a/src/SongFilter.hxx +++ b/src/SongFilter.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 @@ -26,18 +26,23 @@ #include <string> #include <stdint.h> +#include <time.h> /** * Limit the search to files within the given directory. */ #define LOCATE_TAG_BASE_TYPE (TAG_NUM_OF_ITEM_TYPES + 1) +#define LOCATE_TAG_MODIFIED_SINCE (TAG_NUM_OF_ITEM_TYPES + 2) #define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 #define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 +template<typename T> struct ConstBuffer; struct Tag; struct TagItem; struct Song; +struct LightSong; +class DetachedSong; class SongFilter { public: @@ -48,9 +53,15 @@ public: std::string value; + /** + * For #LOCATE_TAG_MODIFIED_SINCE + */ + time_t time; + public: gcc_nonnull(3) Item(unsigned tag, const char *value, bool fold_case=false); + Item(unsigned tag, time_t time); Item(const Item &other) = delete; Item(Item &&) = default; @@ -79,7 +90,10 @@ public: bool Match(const Tag &tag) const; gcc_pure - bool Match(const Song &song) const; + bool Match(const DetachedSong &song) const; + + gcc_pure + bool Match(const LightSong &song) const; }; private: @@ -96,14 +110,16 @@ public: gcc_nonnull(2,3) bool Parse(const char *tag, const char *value, bool fold_case=false); - gcc_nonnull(3) - bool Parse(unsigned argc, char *argv[], bool fold_case=false); + bool Parse(ConstBuffer<const char *> args, bool fold_case=false); gcc_pure bool Match(const Tag &tag) const; gcc_pure - bool Match(const Song &song) const; + bool Match(const DetachedSong &song) const; + + gcc_pure + bool Match(const LightSong &song) const; const std::list<Item> &GetItems() const { return items; diff --git a/src/SongLoader.cxx b/src/SongLoader.cxx new file mode 100644 index 000000000..c766a16a9 --- /dev/null +++ b/src/SongLoader.cxx @@ -0,0 +1,111 @@ +/* + * 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 "SongLoader.hxx" +#include "client/Client.hxx" +#include "db/DatabaseSong.hxx" +#include "storage/StorageInterface.hxx" +#include "ls.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" +#include "PlaylistError.hxx" + +#include <assert.h> +#include <string.h> + +#ifdef ENABLE_DATABASE + +SongLoader::SongLoader(const Client &_client) + :client(&_client), db(_client.GetDatabase(IgnoreError())), + storage(_client.GetStorage()) {} + +#endif + +DetachedSong * +SongLoader::LoadFile(const char *path_utf8, Error &error) const +{ +#ifdef ENABLE_DATABASE + if (storage != nullptr) { + const char *suffix = storage->MapToRelativeUTF8(path_utf8); + if (suffix != nullptr) + /* this path was relative to the music + directory - obtain it from the database */ + return LoadSong(suffix, error); + } +#endif + + if (client != nullptr) { + const auto path_fs = AllocatedPath::FromUTF8(path_utf8, error); + if (path_fs.IsNull()) + return nullptr; + + if (!client->AllowFile(path_fs, error)) + return nullptr; + } + + DetachedSong *song = new DetachedSong(path_utf8); + if (!song->Update()) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such file"); + delete song; + return nullptr; + } + + return song; +} + +DetachedSong * +SongLoader::LoadSong(const char *uri_utf8, Error &error) const +{ + assert(uri_utf8 != nullptr); + + if (memcmp(uri_utf8, "file:///", 8) == 0) + /* absolute path */ + return LoadFile(uri_utf8 + 7, error); + else if (PathTraitsUTF8::IsAbsolute(uri_utf8)) + /* absolute path */ + return LoadFile(uri_utf8, error); + else if (uri_has_scheme(uri_utf8)) { + /* remove URI */ + if (!uri_supported_scheme(uri_utf8)) { + error.Set(playlist_domain, + int(PlaylistResult::NO_SUCH_SONG), + "Unsupported URI scheme"); + return nullptr; + } + + return new DetachedSong(uri_utf8); + } else { + /* URI relative to the music directory */ + +#ifdef ENABLE_DATABASE + if (db != nullptr) + return DatabaseDetachSong(*db, *storage, + uri_utf8, error); +#endif + + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No database"); + return nullptr; + } +} diff --git a/src/SongLoader.hxx b/src/SongLoader.hxx new file mode 100644 index 000000000..229703972 --- /dev/null +++ b/src/SongLoader.hxx @@ -0,0 +1,77 @@ +/* + * 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_SONG_LOADER_HXX +#define MPD_SONG_LOADER_HXX + +#include "check.h" +#include "Compiler.h" + +#include <cstddef> + +class Client; +class Database; +class Storage; +class DetachedSong; +class Error; + +/** + * A utility class that loads a #DetachedSong object by its URI. If + * the URI is an absolute local file, it applies security checks via + * Client::AllowFile(). If no #Client pointer was specified, then it + * is assumed that all local files are allowed. + */ +class SongLoader { + const Client *const client; + +#ifdef ENABLE_DATABASE + const Database *const db; + const Storage *const storage; +#endif + +public: +#ifdef ENABLE_DATABASE + explicit SongLoader(const Client &_client); + SongLoader(const Database *_db, const Storage *_storage) + :client(nullptr), db(_db), storage(_storage) {} + SongLoader(const Client &_client, const Database *_db, + const Storage *_storage) + :client(&_client), db(_db), storage(_storage) {} +#else + explicit SongLoader(const Client &_client) + :client(&_client) {} + explicit SongLoader(std::nullptr_t, std::nullptr_t) + :client(nullptr) {} +#endif + +#ifdef ENABLE_DATABASE + const Storage *GetStorage() const { + return storage; + } +#endif + + gcc_nonnull_all + DetachedSong *LoadSong(const char *uri_utf8, Error &error) const; + +private: + gcc_nonnull_all + DetachedSong *LoadFile(const char *path_utf8, Error &error) const; +}; + +#endif diff --git a/src/SongPointer.hxx b/src/SongPointer.hxx deleted file mode 100644 index ded3b3e1d..000000000 --- a/src/SongPointer.hxx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_POINTER_HXX -#define MPD_SONG_POINTER_HXX - -#include "Song.hxx" - -#include <utility> - -class SongPointer { - Song *song; - -public: - explicit SongPointer(Song *_song) - :song(_song) {} - - SongPointer(const SongPointer &) = delete; - - SongPointer(SongPointer &&other):song(other.song) { - other.song = nullptr; - } - - ~SongPointer() { - if (song != nullptr) - song->Free(); - } - - SongPointer &operator=(const SongPointer &) = delete; - - SongPointer &operator=(SongPointer &&other) { - std::swap(song, other.song); - return *this; - } - - operator const Song *() const { - return song; - } - - Song *Steal() { - auto result = song; - song = nullptr; - return result; - } -}; - -#endif diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx index ea164d02b..c2501d037 100644 --- a/src/SongPrint.cxx +++ b/src/SongPrint.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,35 +19,61 @@ #include "config.h" #include "SongPrint.hxx" -#include "Song.hxx" -#include "Directory.hxx" +#include "db/LightSong.hxx" +#include "storage/StorageInterface.hxx" +#include "DetachedSong.hxx" #include "TimePrint.hxx" #include "TagPrint.hxx" -#include "Mapper.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "util/UriUtil.hxx" -void -song_print_uri(Client &client, const Song &song) +#define SONG_FILE "file: " + +static void +song_print_uri(Client &client, const char *uri, bool base) { - if (song.IsInDatabase() && !song.parent->IsRoot()) { - client_printf(client, "%s%s/%s\n", SONG_FILE, - song.parent->GetPath(), song.uri); + std::string allocated; + + if (base) { + uri = PathTraitsUTF8::GetBase(uri); } else { - const char *uri = song.uri; - const std::string allocated = uri_remove_auth(uri); +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage != nullptr) { + const char *suffix = storage->MapToRelativeUTF8(uri); + if (suffix != nullptr) + uri = suffix; + } +#endif + + allocated = uri_remove_auth(uri); if (!allocated.empty()) uri = allocated.c_str(); - - client_printf(client, "%s%s\n", SONG_FILE, - map_to_relative_path(uri)); } + + client_printf(client, "%s%s\n", SONG_FILE, uri); } void -song_print_info(Client &client, const Song &song) +song_print_uri(Client &client, const LightSong &song, bool base) { - song_print_uri(client, song); + if (!base && song.directory != nullptr) { + client_printf(client, "%s%s/%s\n", SONG_FILE, + song.directory, song.uri); + } else + song_print_uri(client, song.uri, base); +} + +void +song_print_uri(Client &client, const DetachedSong &song, bool base) +{ + song_print_uri(client, song.GetURI(), base); +} + +void +song_print_info(Client &client, const LightSong &song, bool base) +{ + song_print_uri(client, song, base); if (song.end_ms > 0) client_printf(client, "Range: %u.%03u-%u.%03u\n", @@ -63,6 +89,34 @@ song_print_info(Client &client, const Song &song) if (song.mtime > 0) time_print(client, "Last-Modified", song.mtime); - if (song.tag != nullptr) - tag_print(client, *song.tag); + tag_print(client, *song.tag); +} + +void +song_print_info(Client &client, const DetachedSong &song, bool base) +{ + song_print_uri(client, song, base); + + const unsigned start_ms = song.GetStartMS(); + const unsigned end_ms = song.GetEndMS(); + + if (end_ms > 0) + client_printf(client, "Range: %u.%03u-%u.%03u\n", + start_ms / 1000, + start_ms % 1000, + end_ms / 1000, + end_ms % 1000); + else if (start_ms > 0) + client_printf(client, "Range: %u.%03u-\n", + start_ms / 1000, + start_ms % 1000); + + if (song.GetLastModified() > 0) + time_print(client, "Last-Modified", song.GetLastModified()); + + tag_print_values(client, song.GetTag()); + + double duration = song.GetDuration(); + if (duration >= 0) + client_printf(client, "Time: %u\n", unsigned(duration + 0.5)); } diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx index f8df89d38..5e4c93a74 100644 --- a/src/SongPrint.hxx +++ b/src/SongPrint.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 @@ -20,13 +20,20 @@ #ifndef MPD_SONG_PRINT_HXX #define MPD_SONG_PRINT_HXX -struct Song; +struct LightSong; +class DetachedSong; class Client; void -song_print_info(Client &client, const Song &song); +song_print_info(Client &client, const DetachedSong &song, bool base=false); void -song_print_uri(Client &client, const Song &song); +song_print_info(Client &client, const LightSong &song, bool base=false); + +void +song_print_uri(Client &client, const LightSong &song, bool base=false); + +void +song_print_uri(Client &client, const DetachedSong &song, bool base=false); #endif diff --git a/src/SongSave.cxx b/src/SongSave.cxx index 63e279a16..93613e938 100644 --- a/src/SongSave.cxx +++ b/src/SongSave.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,10 +19,11 @@ #include "config.h" #include "SongSave.hxx" -#include "Song.hxx" +#include "db/plugins/simple/Song.hxx" +#include "DetachedSong.hxx" #include "TagSave.hxx" -#include "Directory.hxx" -#include "TextFile.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/BufferedOutputStream.hxx" #include "tag/Tag.hxx" #include "tag/TagBuilder.hxx" #include "util/StringUtil.hxx" @@ -37,41 +38,55 @@ static constexpr Domain song_save_domain("song_save"); +static void +range_save(BufferedOutputStream &os, unsigned start_ms, unsigned end_ms) +{ + if (end_ms > 0) + os.Format("Range: %u-%u\n", start_ms, end_ms); + else if (start_ms > 0) + os.Format("Range: %u-\n", start_ms); +} + +void +song_save(BufferedOutputStream &os, const Song &song) +{ + os.Format(SONG_BEGIN "%s\n", song.uri); + + range_save(os, song.start_ms, song.end_ms); + + tag_save(os, song.tag); + + os.Format(SONG_MTIME ": %li\n", (long)song.mtime); + os.Format(SONG_END "\n"); +} + void -song_save(FILE *fp, const Song &song) +song_save(BufferedOutputStream &os, const DetachedSong &song) { - fprintf(fp, SONG_BEGIN "%s\n", song.uri); + os.Format(SONG_BEGIN "%s\n", song.GetURI()); - if (song.end_ms > 0) - fprintf(fp, "Range: %u-%u\n", song.start_ms, song.end_ms); - else if (song.start_ms > 0) - fprintf(fp, "Range: %u-\n", song.start_ms); + range_save(os, song.GetStartMS(), song.GetEndMS()); - if (song.tag != nullptr) - tag_save(fp, *song.tag); + tag_save(os, song.GetTag()); - fprintf(fp, SONG_MTIME ": %li\n", (long)song.mtime); - fprintf(fp, SONG_END "\n"); + os.Format(SONG_MTIME ": %li\n", (long)song.GetLastModified()); + os.Format(SONG_END "\n"); } -Song * -song_load(TextFile &file, Directory *parent, const char *uri, +DetachedSong * +song_load(TextFile &file, const char *uri, Error &error) { - Song *song = parent != nullptr - ? Song::NewFile(uri, parent) - : Song::NewRemote(uri); - char *line, *colon; - TagType type; - const char *value; + DetachedSong *song = new DetachedSong(uri); TagBuilder tag; + char *line; while ((line = file.ReadLine()) != nullptr && strcmp(line, SONG_END) != 0) { - colon = strchr(line, ':'); + char *colon = strchr(line, ':'); if (colon == nullptr || colon == line) { - song->Free(); + delete song; error.Format(song_save_domain, "unknown line in db: %s", line); @@ -79,8 +94,9 @@ song_load(TextFile &file, Directory *parent, const char *uri, } *colon++ = 0; - value = strchug_fast(colon); + const char *value = StripLeft(colon); + TagType type; if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { tag.AddItem(type, value); } else if (strcmp(line, "Time") == 0) { @@ -88,15 +104,19 @@ song_load(TextFile &file, Directory *parent, const char *uri, } else if (strcmp(line, "Playlist") == 0) { tag.SetHasPlaylist(strcmp(value, "yes") == 0); } else if (strcmp(line, SONG_MTIME) == 0) { - song->mtime = atoi(value); + song->SetLastModified(atoi(value)); } else if (strcmp(line, "Range") == 0) { char *endptr; - song->start_ms = strtoul(value, &endptr, 10); - if (*endptr == '-') - song->end_ms = strtoul(endptr + 1, nullptr, 10); + unsigned start_ms = strtoul(value, &endptr, 10); + unsigned end_ms = *endptr == '-' + ? strtoul(endptr + 1, nullptr, 10) + : 0; + + song->SetStartMS(start_ms); + song->SetEndMS(end_ms); } else { - song->Free(); + delete song; error.Format(song_save_domain, "unknown line in db: %s", line); @@ -104,8 +124,6 @@ song_load(TextFile &file, Directory *parent, const char *uri, } } - if (tag.IsDefined()) - song->tag = tag.Commit(); - + song->SetTag(tag.Commit()); return song; } diff --git a/src/SongSave.hxx b/src/SongSave.hxx index 40fb4abf7..28c217249 100644 --- a/src/SongSave.hxx +++ b/src/SongSave.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 @@ -20,17 +20,20 @@ #ifndef MPD_SONG_SAVE_HXX #define MPD_SONG_SAVE_HXX -#include <stdio.h> - #define SONG_BEGIN "song_begin: " struct Song; struct Directory; +class DetachedSong; +class BufferedOutputStream; class TextFile; class Error; void -song_save(FILE *fp, const Song &song); +song_save(BufferedOutputStream &os, const Song &song); + +void +song_save(BufferedOutputStream &os, const DetachedSong &song); /** * Loads a song from the input file. Reading stops after the @@ -39,8 +42,8 @@ song_save(FILE *fp, const Song &song); * @param error location to store the error occurring * @return true on success, false on error */ -Song * -song_load(TextFile &file, Directory *parent, const char *uri, +DetachedSong * +song_load(TextFile &file, const char *uri, Error &error); #endif diff --git a/src/SongSort.cxx b/src/SongSort.cxx deleted file mode 100644 index 4d422657a..000000000 --- a/src/SongSort.cxx +++ /dev/null @@ -1,124 +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 "SongSort.hxx" -#include "Song.hxx" -#include "util/list.h" -#include "tag/Tag.hxx" - -extern "C" { -#include "util/list_sort.h" -} - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -static const char * -tag_get_value_checked(const Tag *tag, TagType type) -{ - return tag != nullptr - ? tag->GetValue(type) - : nullptr; -} - -static int -compare_utf8_string(const char *a, const char *b) -{ - if (a == nullptr) - return b == nullptr ? 0 : -1; - - if (b == nullptr) - return 1; - - return g_utf8_collate(a, b); -} - -/** - * Compare two string tag values, ignoring case. Either one may be - * nullptr. - */ -static int -compare_string_tag_item(const Tag *a, const Tag *b, - TagType type) -{ - return compare_utf8_string(tag_get_value_checked(a, type), - tag_get_value_checked(b, type)); -} - -/** - * Compare two tag values which should contain an integer value - * (e.g. disc or track number). Either one may be nullptr. - */ -static int -compare_number_string(const char *a, const char *b) -{ - long ai = a == nullptr ? 0 : strtol(a, nullptr, 10); - long bi = b == nullptr ? 0 : strtol(b, nullptr, 10); - - if (ai <= 0) - return bi <= 0 ? 0 : -1; - - if (bi <= 0) - return 1; - - return ai - bi; -} - -static int -compare_tag_item(const Tag *a, const Tag *b, TagType type) -{ - return compare_number_string(tag_get_value_checked(a, type), - tag_get_value_checked(b, type)); -} - -/* Only used for sorting/searchin a songvec, not general purpose compares */ -static int -song_cmp(gcc_unused void *priv, struct list_head *_a, struct list_head *_b) -{ - const Song *a = (const Song *)_a; - const Song *b = (const Song *)_b; - int ret; - - /* first sort by album */ - ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM); - if (ret != 0) - return ret; - - /* then sort by disc */ - ret = compare_tag_item(a->tag, b->tag, TAG_DISC); - if (ret != 0) - return ret; - - /* then by track number */ - ret = compare_tag_item(a->tag, b->tag, TAG_TRACK); - if (ret != 0) - return ret; - - /* still no difference? compare file name */ - return g_utf8_collate(a->uri, b->uri); -} - -void -song_list_sort(struct list_head *songs) -{ - list_sort(nullptr, songs, song_cmp); -} diff --git a/src/SongSort.hxx b/src/SongSort.hxx deleted file mode 100644 index b3b67b0c0..000000000 --- a/src/SongSort.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_SORT_HXX -#define MPD_SONG_SORT_HXX - -struct list_head; - -void -song_list_sort(struct list_head *songs); - -#endif diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx deleted file mode 100644 index a0c4d3585..000000000 --- a/src/SongSticker.cxx +++ /dev/null @@ -1,135 +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 "SongSticker.hxx" -#include "StickerDatabase.hxx" -#include "Song.hxx" -#include "Directory.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -std::string -sticker_song_get_value(const Song *song, const char *name) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_load_value("song", uri.c_str(), name); -} - -bool -sticker_song_set_value(const Song *song, - const char *name, const char *value) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_store_value("song", uri.c_str(), name, value); -} - -bool -sticker_song_delete(const Song *song) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_delete("song", uri.c_str()); -} - -bool -sticker_song_delete_value(const Song *song, const char *name) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_delete_value("song", uri.c_str(), name); -} - -struct sticker * -sticker_song_get(const Song *song) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_load("song", uri.c_str()); -} - -struct sticker_song_find_data { - Directory *directory; - const char *base_uri; - size_t base_uri_length; - - void (*func)(Song &song, const char *value, - void *user_data); - void *user_data; -}; - -static void -sticker_song_find_cb(const char *uri, const char *value, void *user_data) -{ - struct sticker_song_find_data *data = - (struct sticker_song_find_data *)user_data; - - if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) - /* should not happen, ignore silently */ - return; - - Song *song = data->directory->LookupSong(uri + data->base_uri_length); - if (song != nullptr) - data->func(*song, value, data->user_data); -} - -bool -sticker_song_find(Directory &directory, const char *name, - void (*func)(Song &song, const char *value, - void *user_data), - void *user_data) -{ - struct sticker_song_find_data data; - data.directory = &directory; - data.func = func; - data.user_data = user_data; - - char *allocated; - data.base_uri = directory.GetPath(); - if (*data.base_uri != 0) - /* append slash to base_uri */ - data.base_uri = allocated = - g_strconcat(data.base_uri, "/", nullptr); - else - /* searching in root directory - no trailing slash */ - allocated = nullptr; - - data.base_uri_length = strlen(data.base_uri); - - bool success = sticker_find("song", data.base_uri, name, - sticker_song_find_cb, &data); - g_free(allocated); - - return success; -} diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx deleted file mode 100644 index 0923f0c3a..000000000 --- a/src/SongSticker.hxx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SONG_STICKER_HXX -#define MPD_SONG_STICKER_HXX - -#include "Compiler.h" - -#include <string> - -struct Song; -struct Directory; -struct sticker; - -/** - * Returns one value from a song's sticker record. The caller must - * free the return value with g_free(). - */ -gcc_pure -std::string -sticker_song_get_value(const Song *song, const char *name); - -/** - * Sets a sticker value in the specified song. Overwrites existing - * values. - */ -bool -sticker_song_set_value(const Song *song, - const char *name, const char *value); - -/** - * Deletes a sticker from the database. All values are deleted. - */ -bool -sticker_song_delete(const Song *song); - -/** - * Deletes a sticker value. Does nothing if the sticker did not - * exist. - */ -bool -sticker_song_delete_value(const Song *song, const char *name); - -/** - * Loads the sticker for the specified song. - * - * @param song the song object - * @return a sticker object, or NULL on error or if there is no sticker - */ -struct sticker * -sticker_song_get(const Song *song); - -/** - * Finds stickers with the specified name below the specified - * directory. - * - * Caller must lock the #db_mutex. - * - * @param directory the base directory to search in - * @param name the name of the sticker - * @return true on success (even if no sticker was found), false on - * failure - */ -bool -sticker_song_find(Directory &directory, const char *name, - void (*func)(Song &song, const char *value, - void *user_data), - void *user_data); - -#endif diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx index 1a873fedc..0245b9117 100644 --- a/src/SongUpdate.cxx +++ b/src/SongUpdate.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 @@ -18,50 +18,44 @@ */ #include "config.h" /* must be first for large file support */ -#include "Song.hxx" +#include "DetachedSong.hxx" +#include "db/plugins/simple/Song.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include "Directory.hxx" -#include "Mapper.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" -#include "InputStream.hxx" -#include "DecoderPlugin.hxx" -#include "DecoderList.hxx" +#include "decoder/DecoderList.hxx" #include "tag/Tag.hxx" #include "tag/TagBuilder.hxx" #include "tag/TagHandler.hxx" #include "tag/TagId3.hxx" #include "tag/ApeTag.hxx" #include "TagFile.hxx" -#include "thread/Cond.hxx" +#include "TagStream.hxx" #include <assert.h> #include <string.h> -#include <sys/types.h> #include <sys/stat.h> -#include <stdio.h> + +#ifdef ENABLE_DATABASE Song * -Song::LoadFile(const char *path_utf8, Directory *parent) +Song::LoadFile(Storage &storage, const char *path_utf8, Directory &parent) { - Song *song; - bool ret; - - assert((parent == nullptr) == PathTraits::IsAbsoluteUTF8(path_utf8)); assert(!uri_has_scheme(path_utf8)); assert(strchr(path_utf8, '\n') == nullptr); - song = NewFile(path_utf8, parent); + Song *song = NewFile(path_utf8, parent); //in archive ? - if (parent != nullptr && parent->device == DEVICE_INARCHIVE) { - ret = song->UpdateFileInArchive(); - } else { - ret = song->UpdateFile(); - } - if (!ret) { + bool success = parent.device == DEVICE_INARCHIVE + ? song->UpdateFileInArchive(storage) + : song->UpdateFile(storage); + if (!success) { song->Free(); return nullptr; } @@ -69,6 +63,8 @@ Song::LoadFile(const char *path_utf8, Directory *parent) return song; } +#endif + /** * Attempts to load APE or ID3 tags from the specified file. */ @@ -80,59 +76,103 @@ tag_scan_fallback(Path path, tag_id3_scan(path, handler, handler_ctx); } +#ifdef ENABLE_DATABASE + bool -Song::UpdateFile() +Song::UpdateFile(Storage &storage) { - assert(IsFile()); + const auto &relative_uri = GetURI(); - const auto path_fs = map_song_fs(*this); - if (path_fs.IsNull()) + FileInfo info; + if (!storage.GetInfo(relative_uri.c_str(), true, info, IgnoreError())) return false; - struct stat st; - if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) + if (!info.IsRegular()) return false; TagBuilder tag_builder; - if (!tag_file_scan(path_fs, - &full_tag_handler, &tag_builder)) - return false; - if (tag_builder.IsEmpty()) - tag_scan_fallback(path_fs, &full_tag_handler, - &tag_builder); + const auto path_fs = storage.MapFS(relative_uri.c_str()); + if (path_fs.IsNull()) { + const auto absolute_uri = + storage.MapUTF8(relative_uri.c_str()); + if (!tag_stream_scan(absolute_uri.c_str(), + full_tag_handler, &tag_builder)) + return false; + } else { + if (!tag_file_scan(path_fs, full_tag_handler, &tag_builder)) + return false; - mtime = st.st_mtime; + if (tag_builder.IsEmpty()) + tag_scan_fallback(path_fs, &full_tag_handler, + &tag_builder); + } - delete tag; - tag = tag_builder.Commit(); + mtime = info.mtime; + tag_builder.Commit(tag); return true; } bool -Song::UpdateFileInArchive() +Song::UpdateFileInArchive(const Storage &storage) { - const char *suffix; - const struct DecoderPlugin *plugin; - - assert(IsFile()); - /* check if there's a suffix and a plugin */ - suffix = uri_get_suffix(uri); + const char *suffix = uri_get_suffix(uri); if (suffix == nullptr) return false; - plugin = decoder_plugin_from_suffix(suffix, nullptr); - if (plugin == nullptr) + if (!decoder_plugins_supports_suffix(suffix)) return false; - delete tag; + const auto path_fs = parent->IsRoot() + ? storage.MapFS(uri) + : storage.MapChildFS(parent->GetPath(), uri); + if (path_fs.IsNull()) + return false; - //accept every file that has music suffix - //because we don't support tag reading through - //input streams - tag = new Tag(); + TagBuilder tag_builder; + if (!tag_stream_scan(path_fs.c_str(), full_tag_handler, &tag_builder)) + return false; + tag_builder.Commit(tag); return true; } + +#endif + +bool +DetachedSong::Update() +{ + if (IsAbsoluteFile()) { + const AllocatedPath path_fs = + AllocatedPath::FromUTF8(GetRealURI()); + + struct stat st; + if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) + return false; + + TagBuilder tag_builder; + if (!tag_file_scan(path_fs, full_tag_handler, &tag_builder)) + return false; + + if (tag_builder.IsEmpty()) + tag_scan_fallback(path_fs, &full_tag_handler, + &tag_builder); + + mtime = st.st_mtime; + tag_builder.Commit(tag); + return true; + } else if (IsRemote()) { + TagBuilder tag_builder; + if (!tag_stream_scan(uri.c_str(), full_tag_handler, + &tag_builder)) + return false; + + mtime = 0; + tag_builder.Commit(tag); + return true; + } else + // TODO: implement + return false; +} diff --git a/src/StateFile.cxx b/src/StateFile.cxx index 75cef2c99..e0f0cedb1 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.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,12 +19,15 @@ #include "config.h" #include "StateFile.hxx" -#include "OutputState.hxx" -#include "PlaylistState.hxx" -#include "TextFile.hxx" +#include "output/OutputState.hxx" +#include "queue/PlaylistState.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/FileOutputStream.hxx" +#include "fs/io/BufferedOutputStream.hxx" #include "Partition.hxx" -#include "Volume.hxx" -#include "event/Loop.hxx" +#include "Instance.hxx" +#include "mixer/Volume.hxx" +#include "SongLoader.hxx" #include "fs/FileSystem.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -61,25 +64,35 @@ StateFile::IsModified() const partition.pc); } +inline void +StateFile::Write(BufferedOutputStream &os) +{ + save_sw_volume_state(os); + audio_output_state_save(os, partition.outputs); + playlist_state_save(os, partition.playlist, partition.pc); +} + +inline bool +StateFile::Write(OutputStream &os, Error &error) +{ + BufferedOutputStream bos(os); + Write(bos); + return bos.Flush(error); +} + void StateFile::Write() { FormatDebug(state_file_domain, "Saving state file %s", path_utf8.c_str()); - FILE *fp = FOpen(path, FOpenMode::WriteText); - if (gcc_unlikely(!fp)) { - FormatErrno(state_file_domain, "failed to create %s", - path_utf8.c_str()); + Error error; + FileOutputStream fos(path, error); + if (!fos.IsDefined() || !Write(fos, error) || !fos.Commit(error)) { + LogError(error); return; } - save_sw_volume_state(fp); - audio_output_state_save(fp); - playlist_state_save(fp, partition.playlist, partition.pc); - - fclose(fp); - RememberVersions(); } @@ -90,18 +103,26 @@ StateFile::Read() FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str()); - TextFile file(path); + Error error; + TextFile file(path, error); if (file.HasFailed()) { - FormatErrno(state_file_domain, "failed to open %s", - path_utf8.c_str()); + LogError(error); return; } +#ifdef ENABLE_DATABASE + const SongLoader song_loader(partition.instance.database, + partition.instance.storage); +#else + const SongLoader song_loader(nullptr, nullptr); +#endif + const char *line; - while ((line = file.ReadLine()) != NULL) { - success = read_sw_volume_state(line) || - audio_output_state_read(line) || - playlist_state_restore(line, file, partition.playlist, + while ((line = file.ReadLine()) != nullptr) { + success = read_sw_volume_state(line, partition.outputs) || + audio_output_state_read(line, partition.outputs) || + playlist_state_restore(line, file, song_loader, + partition.playlist, partition.pc); if (!success) FormatError(state_file_domain, diff --git a/src/StateFile.hxx b/src/StateFile.hxx index 4ec2c4be7..609651c92 100644 --- a/src/StateFile.hxx +++ b/src/StateFile.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 @@ -27,6 +27,8 @@ #include <string> struct Partition; +class OutputStream; +class BufferedOutputStream; class StateFile final : private TimeoutMonitor { AllocatedPath path; @@ -53,6 +55,9 @@ public: void CheckModified(); private: + bool Write(OutputStream &os, Error &error); + void Write(BufferedOutputStream &os); + /** * Save the current state versions for use with IsModified(). */ diff --git a/src/Stats.cxx b/src/Stats.cxx index f224bdf49..8fc626ecb 100644 --- a/src/Stats.cxx +++ b/src/Stats.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 @@ -20,58 +20,84 @@ #include "config.h" #include "Stats.hxx" #include "PlayerControl.hxx" -#include "Client.hxx" -#include "DatabaseSelection.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseSimple.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "db/Selection.hxx" +#include "db/Interface.hxx" +#include "db/Stats.hxx" #include "util/Error.hxx" +#include "system/Clock.hxx" #include "Log.hxx" -#include <glib.h> +#ifndef WIN32 +/** + * The monotonic time stamp when MPD was started. It is used to + * calculate the uptime. + */ +static unsigned start_time; +#endif + +#ifdef ENABLE_DATABASE -static GTimer *uptime; static DatabaseStats stats; +enum class StatsValidity : uint8_t { + INVALID, VALID, FAILED, +}; + +static StatsValidity stats_validity = StatsValidity::INVALID; + +#endif + void stats_global_init(void) { - uptime = g_timer_new(); +#ifndef WIN32 + start_time = MonotonicClockS(); +#endif } -void stats_global_finish(void) +#ifdef ENABLE_DATABASE + +void +stats_invalidate() { - g_timer_destroy(uptime); + stats_validity = StatsValidity::INVALID; } -void stats_update(void) +static bool +stats_update(const Database &db) { - assert(GetDatabase() != nullptr); + switch (stats_validity) { + case StatsValidity::INVALID: + break; - Error error; + case StatsValidity::VALID: + return true; + + case StatsValidity::FAILED: + return false; + } - DatabaseStats stats2; + Error error; const DatabaseSelection selection("", true); - if (GetDatabase()->GetStats(selection, stats2, error)) { - stats = stats2; + if (db.GetStats(selection, stats, error)) { + stats_validity = StatsValidity::VALID; + return true; } else { LogError(error); - stats.Clear(); + stats_validity = StatsValidity::FAILED; + return false; } } static void -db_stats_print(Client &client) +db_stats_print(Client &client, const Database &db) { - assert(GetDatabase() != nullptr); - - if (!db_is_simple()) - /* reload statistics if we're using the "proxy" - database plugin */ - /* TODO: move this into the "proxy" database plugin as - an "idle" handler */ - stats_update(); + if (!stats_update(db)) + return; client_printf(client, "artists: %u\n" @@ -83,22 +109,31 @@ db_stats_print(Client &client) stats.song_count, stats.total_duration); - const time_t update_stamp = GetDatabase()->GetUpdateStamp(); + const time_t update_stamp = db.GetUpdateStamp(); if (update_stamp > 0) client_printf(client, "db_update: %lu\n", (unsigned long)update_stamp); } +#endif + void stats_print(Client &client) { client_printf(client, - "uptime: %lu\n" + "uptime: %u\n" "playtime: %lu\n", - (unsigned long)g_timer_elapsed(uptime, NULL), +#ifdef WIN32 + GetProcessUptimeS(), +#else + MonotonicClockS() - start_time, +#endif (unsigned long)(client.player_control.GetTotalPlayTime() + 0.5)); - if (GetDatabase() != nullptr) - db_stats_print(client); +#ifdef ENABLE_DATABASE + const Database *db = client.partition.instance.database; + if (db != nullptr) + db_stats_print(client, *db); +#endif } diff --git a/src/Stats.hxx b/src/Stats.hxx index dd131ce19..0d36ec0b2 100644 --- a/src/Stats.hxx +++ b/src/Stats.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 @@ -24,9 +24,8 @@ class Client; void stats_global_init(void); -void stats_global_finish(void); - -void stats_update(void); +void +stats_invalidate(); void stats_print(Client &client); diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx deleted file mode 100644 index 869b91474..000000000 --- a/src/StickerDatabase.cxx +++ /dev/null @@ -1,604 +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 "StickerDatabase.hxx" -#include "fs/Path.hxx" -#include "Idle.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <string> -#include <map> - -#include <sqlite3.h> -#include <assert.h> - -#if SQLITE_VERSION_NUMBER < 3003009 -#define sqlite3_prepare_v2 sqlite3_prepare -#endif - -struct sticker { - std::map<std::string, std::string> table; -}; - -enum sticker_sql { - STICKER_SQL_GET, - STICKER_SQL_LIST, - STICKER_SQL_UPDATE, - STICKER_SQL_INSERT, - STICKER_SQL_DELETE, - STICKER_SQL_DELETE_VALUE, - STICKER_SQL_FIND, -}; - -static const char *const sticker_sql[] = { - //[STICKER_SQL_GET] = - "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", - //[STICKER_SQL_LIST] = - "SELECT name,value FROM sticker WHERE type=? AND uri=?", - //[STICKER_SQL_UPDATE] = - "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", - //[STICKER_SQL_INSERT] = - "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", - //[STICKER_SQL_DELETE] = - "DELETE FROM sticker WHERE type=? AND uri=?", - //[STICKER_SQL_DELETE_VALUE] = - "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", - //[STICKER_SQL_FIND] = - "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", -}; - -static const char sticker_sql_create[] = - "CREATE TABLE IF NOT EXISTS sticker(" - " type VARCHAR NOT NULL, " - " uri VARCHAR NOT NULL, " - " name VARCHAR NOT NULL, " - " value VARCHAR NOT NULL" - ");" - "CREATE UNIQUE INDEX IF NOT EXISTS" - " sticker_value ON sticker(type, uri, name);" - ""; - -static sqlite3 *sticker_db; -static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)]; - -static constexpr Domain sticker_domain("sticker"); - -static void -LogError(sqlite3 *db, const char *msg) -{ - FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db)); -} - -static sqlite3_stmt * -sticker_prepare(const char *sql, Error &error) -{ - int ret; - sqlite3_stmt *stmt; - - ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr); - if (ret != SQLITE_OK) { - error.Format(sticker_domain, ret, - "sqlite3_prepare_v2() failed: %s", - sqlite3_errmsg(sticker_db)); - return nullptr; - } - - return stmt; -} - -bool -sticker_global_init(Path path, Error &error) -{ - assert(!path.IsNull()); - - int ret; - - /* open/create the sqlite database */ - - ret = sqlite3_open(path.c_str(), &sticker_db); - if (ret != SQLITE_OK) { - const std::string utf8 = path.ToUTF8(); - error.Format(sticker_domain, ret, - "Failed to open sqlite database '%s': %s", - utf8.c_str(), sqlite3_errmsg(sticker_db)); - return false; - } - - /* create the table and index */ - - ret = sqlite3_exec(sticker_db, sticker_sql_create, - nullptr, nullptr, nullptr); - if (ret != SQLITE_OK) { - error.Format(sticker_domain, ret, - "Failed to create sticker table: %s", - sqlite3_errmsg(sticker_db)); - return false; - } - - /* prepare the statements we're going to use */ - - for (unsigned i = 0; i < ARRAY_SIZE(sticker_sql); ++i) { - assert(sticker_sql[i] != nullptr); - - sticker_stmt[i] = sticker_prepare(sticker_sql[i], error); - if (sticker_stmt[i] == nullptr) - return false; - } - - return true; -} - -void -sticker_global_finish(void) -{ - if (sticker_db == nullptr) - /* not configured */ - return; - - for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) { - assert(sticker_stmt[i] != nullptr); - - sqlite3_finalize(sticker_stmt[i]); - } - - sqlite3_close(sticker_db); -} - -bool -sticker_enabled(void) -{ - return sticker_db != nullptr; -} - -std::string -sticker_load_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; - int ret; - - assert(sticker_enabled()); - assert(type != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - - if (*name == 0) - return std::string(); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - std::string value; - if (ret == SQLITE_ROW) { - /* record found */ - value = (const char*)sqlite3_column_text(stmt, 0); - } else if (ret == SQLITE_DONE) { - /* no record found */ - } else { - /* error */ - LogError(sticker_db, "sqlite3_step() failed"); - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return value; -} - -static bool -sticker_list_values(std::map<std::string, std::string> &table, - const char *type, const char *uri) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; - int ret; - - assert(type != nullptr); - assert(uri != nullptr); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - const char *name, *value; - - case SQLITE_ROW: - name = (const char*)sqlite3_column_text(stmt, 0); - value = (const char*)sqlite3_column_text(stmt, 1); - - table.insert(std::make_pair(name, value)); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - } while (ret != SQLITE_DONE); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return true; -} - -static bool -sticker_update_value(const char *type, const char *uri, - const char *name, const char *value) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; - int ret; - - assert(type != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - assert(*name != 0); - assert(value != nullptr); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - - ret = sqlite3_changes(sticker_db); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return ret > 0; -} - -static bool -sticker_insert_value(const char *type, const char *uri, - const char *name, const char *value) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; - int ret; - - assert(type != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - assert(*name != 0); - assert(value != nullptr); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - - idle_add(IDLE_STICKER); - return true; -} - -bool -sticker_store_value(const char *type, const char *uri, - const char *name, const char *value) -{ - assert(sticker_enabled()); - assert(type != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - assert(value != nullptr); - - if (*name == 0) - return false; - - return sticker_update_value(type, uri, name, value) || - sticker_insert_value(type, uri, name, value); -} - -bool -sticker_delete(const char *type, const char *uri) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; - int ret; - - assert(sticker_enabled()); - assert(type != nullptr); - assert(uri != nullptr); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return true; -} - -bool -sticker_delete_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; - int ret; - - assert(sticker_enabled()); - assert(type != nullptr); - assert(uri != nullptr); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - - ret = sqlite3_changes(sticker_db); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return ret > 0; -} - -void -sticker_free(struct sticker *sticker) -{ - delete sticker; -} - -const char * -sticker_get_value(const struct sticker &sticker, const char *name) -{ - auto i = sticker.table.find(name); - if (i == sticker.table.end()) - return nullptr; - - return i->second.c_str(); -} - -void -sticker_foreach(const sticker &sticker, - void (*func)(const char *name, const char *value, - void *user_data), - void *user_data) -{ - for (const auto &i : sticker.table) - func(i.first.c_str(), i.second.c_str(), user_data); -} - -struct sticker * -sticker_load(const char *type, const char *uri) -{ - sticker s; - - if (!sticker_list_values(s.table, type, uri)) - return nullptr; - - if (s.table.empty()) - /* don't return empty sticker objects */ - return nullptr; - - return new sticker(std::move(s)); -} - -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - void *user_data), - void *user_data) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; - int ret; - - assert(type != nullptr); - assert(name != nullptr); - assert(func != nullptr); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - if (base_uri == nullptr) - base_uri = ""; - - ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - case SQLITE_ROW: - func((const char*)sqlite3_column_text(stmt, 0), - (const char*)sqlite3_column_text(stmt, 1), - user_data); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - } while (ret != SQLITE_DONE); - - sqlite3_reset(stmt); - sqlite3_clear_bindings(stmt); - - return true; -} diff --git a/src/StickerDatabase.hxx b/src/StickerDatabase.hxx deleted file mode 100644 index 42522b7b4..000000000 --- a/src/StickerDatabase.hxx +++ /dev/null @@ -1,161 +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. - */ - -/* - * This is the sticker database library. It is the backend of all the - * sticker code in MPD. - * - * "Stickers" are pieces of information attached to existing MPD - * objects (e.g. song files, directories, albums). Clients can create - * arbitrary name/value pairs. MPD itself does not assume any special - * meaning in them. - * - * The goal is to allow clients to share additional (possibly dynamic) - * information about songs, which is neither stored on the client (not - * available to other clients), nor stored in the song files (MPD has - * no write access). - * - * Client developers should create a standard for common sticker - * names, to ensure interoperability. - * - * Examples: song ratings; statistics; deferred tag writes; lyrics; - * ... - * - */ - -#ifndef MPD_STICKER_DATABASE_HXX -#define MPD_STICKER_DATABASE_HXX - -#include "Compiler.h" - -#include <string> - -class Error; -class Path; -struct sticker; - -/** - * Opens the sticker database. - * - * @return true on success, false on error - */ -bool -sticker_global_init(Path path, Error &error); - -/** - * Close the sticker database. - */ -void -sticker_global_finish(void); - -/** - * Returns true if the sticker database is configured and available. - */ -gcc_const -bool -sticker_enabled(void); - -/** - * Returns one value from an object's sticker record. Returns an - * empty string if the value doesn't exist. - */ -std::string -sticker_load_value(const char *type, const char *uri, const char *name); - -/** - * Sets a sticker value in the specified object. Overwrites existing - * values. - */ -bool -sticker_store_value(const char *type, const char *uri, - const char *name, const char *value); - -/** - * Deletes a sticker from the database. All sticker values of the - * specified object are deleted. - */ -bool -sticker_delete(const char *type, const char *uri); - -/** - * Deletes a sticker value. Fails if no sticker with this name - * exists. - */ -bool -sticker_delete_value(const char *type, const char *uri, const char *name); - -/** - * Frees resources held by the sticker object. - * - * @param sticker the sticker object to be freed - */ -void -sticker_free(struct sticker *sticker); - -/** - * Determines a single value in a sticker. - * - * @param sticker the sticker object - * @param name the name of the sticker - * @return the sticker value, or nullptr if none was found - */ -gcc_pure -const char * -sticker_get_value(const struct sticker &sticker, const char *name); - -/** - * Iterates over all sticker items in a sticker. - * - * @param sticker the sticker object - * @param func a callback function - * @param user_data an opaque pointer for the callback function - */ -void -sticker_foreach(const struct sticker &sticker, - void (*func)(const char *name, const char *value, - void *user_data), - void *user_data); - -/** - * Loads the sticker for the specified resource. - * - * @param type the resource type, e.g. "song" - * @param uri the URI of the resource, e.g. the song path - * @return a sticker object, or nullptr on error or if there is no sticker - */ -struct sticker * -sticker_load(const char *type, const char *uri); - -/** - * Finds stickers with the specified name below the specified URI. - * - * @param type the resource type, e.g. "song" - * @param base_uri the URI prefix of the resources, or nullptr if all - * resources should be searched - * @param name the name of the sticker - * @return true on success (even if no sticker was found), false on - * failure - */ -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - void *user_data), - void *user_data); - -#endif diff --git a/src/StickerPrint.cxx b/src/StickerPrint.cxx deleted file mode 100644 index 364d41356..000000000 --- a/src/StickerPrint.cxx +++ /dev/null @@ -1,44 +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 "StickerPrint.hxx" -#include "StickerDatabase.hxx" -#include "Client.hxx" - -void -sticker_print_value(Client &client, - const char *name, const char *value) -{ - client_printf(client, "sticker: %s=%s\n", name, value); -} - -static void -print_sticker_cb(const char *name, const char *value, void *data) -{ - Client &client = *(Client *)data; - - sticker_print_value(client, name, value); -} - -void -sticker_print(Client &client, const sticker &sticker) -{ - sticker_foreach(sticker, print_sticker_cb, &client); -} diff --git a/src/StickerPrint.hxx b/src/StickerPrint.hxx deleted file mode 100644 index be6708486..000000000 --- a/src/StickerPrint.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_STICKER_PRINT_HXX -#define MPD_STICKER_PRINT_HXX - -struct sticker; -class Client; - -/** - * Sends one sticker value to the client. - */ -void -sticker_print_value(Client &client, const char *name, const char *value); - -/** - * Sends all sticker values to the client. - */ -void -sticker_print(Client &client, const sticker &sticker); - -#endif diff --git a/src/TagFile.cxx b/src/TagFile.cxx index 785a74987..84faa848a 100644 --- a/src/TagFile.cxx +++ b/src/TagFile.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,65 +22,76 @@ #include "fs/Path.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include "DecoderList.hxx" -#include "DecoderPlugin.hxx" -#include "InputStream.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "input/InputStream.hxx" #include "thread/Cond.hxx" #include <assert.h> -bool -tag_file_scan(Path path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - assert(!path_fs.IsNull()); - assert(handler != nullptr); +class TagFileScan { + const Path path_fs; + const char *const suffix; - /* check if there's a suffix and a plugin */ + const tag_handler &handler; + void *handler_ctx; - const char *suffix = uri_get_suffix(path_fs.c_str()); - if (suffix == nullptr) - return false; - - const struct DecoderPlugin *plugin = - decoder_plugin_from_suffix(suffix, nullptr); - if (plugin == nullptr) - return false; - - InputStream *is = nullptr; Mutex mutex; Cond cond; + InputStream *is; + +public: + TagFileScan(Path _path_fs, const char *_suffix, + const tag_handler &_handler, void *_handler_ctx) + :path_fs(_path_fs), suffix(_suffix), + handler(_handler), handler_ctx(_handler_ctx) , + is(nullptr) {} - do { - /* load file tag */ - if (plugin->ScanFile(path_fs.c_str(), - *handler, handler_ctx)) - break; + ~TagFileScan() { + delete is; + } - /* fall back to stream tag */ - if (plugin->scan_stream != nullptr) { - /* open the InputStream (if not already - open) */ + bool ScanFile(const DecoderPlugin &plugin) { + return plugin.ScanFile(path_fs, handler, handler_ctx); + } + + bool ScanStream(const DecoderPlugin &plugin) { + if (plugin.scan_stream == nullptr) + return false; + + /* open the InputStream (if not already open) */ + if (is == nullptr) { + is = InputStream::OpenReady(path_fs.c_str(), + mutex, cond, + IgnoreError()); if (is == nullptr) - is = InputStream::Open(path_fs.c_str(), - mutex, cond, - IgnoreError()); + return false; + } else + is->LockRewind(IgnoreError()); + + /* now try the stream_tag() method */ + return plugin.ScanStream(*is, handler, handler_ctx); + } - /* now try the stream_tag() method */ - if (is != nullptr) { - if (plugin->ScanStream(*is, - *handler, handler_ctx)) - break; + bool Scan(const DecoderPlugin &plugin) { + return plugin.SupportsSuffix(suffix) && + (ScanFile(plugin) || ScanStream(plugin)); + } +}; - is->LockRewind(IgnoreError()); - } - } +bool +tag_file_scan(Path path_fs, const tag_handler &handler, void *handler_ctx) +{ + assert(!path_fs.IsNull()); - plugin = decoder_plugin_from_suffix(suffix, plugin); - } while (plugin != nullptr); + /* check if there's a suffix and a plugin */ - if (is != nullptr) - is->Close(); + const char *suffix = uri_get_suffix(path_fs.c_str()); + if (suffix == nullptr) + return false; - return plugin != nullptr; + TagFileScan tfs(path_fs, suffix, handler, handler_ctx); + return decoder_plugins_try([&](const DecoderPlugin &plugin){ + return tfs.Scan(plugin); + }); } diff --git a/src/TagFile.hxx b/src/TagFile.hxx index 078abebd9..b11a8ac1c 100644 --- a/src/TagFile.hxx +++ b/src/TagFile.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 @@ -33,7 +33,6 @@ struct tag_handler; * found) */ bool -tag_file_scan(Path path, - const struct tag_handler *handler, void *handler_ctx); +tag_file_scan(Path path, const tag_handler &handler, void *handler_ctx); #endif diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx index 1191bd37c..636eedf4e 100644 --- a/src/TagPrint.cxx +++ b/src/TagPrint.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 @@ -21,8 +21,9 @@ #include "TagPrint.hxx" #include "tag/Tag.hxx" #include "tag/TagSettings.h" -#include "Song.hxx" -#include "Client.hxx" +#include "client/Client.hxx" + +#define SONG_TIME "Time: " void tag_print_types(Client &client) { @@ -35,14 +36,24 @@ void tag_print_types(Client &client) } } +void +tag_print(Client &client, TagType type, const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[type], value); +} + +void +tag_print_values(Client &client, const Tag &tag) +{ + for (const auto &i : tag) + client_printf(client, "%s: %s\n", + tag_item_names[i.type], i.value); +} + void tag_print(Client &client, const Tag &tag) { if (tag.time >= 0) client_printf(client, SONG_TIME "%i\n", tag.time); - for (unsigned i = 0; i < tag.num_items; i++) { - client_printf(client, "%s: %s\n", - tag_item_names[tag.items[i]->type], - tag.items[i]->value); - } + tag_print_values(client, tag); } diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx index ccc0c9aa4..6675bb7d8 100644 --- a/src/TagPrint.hxx +++ b/src/TagPrint.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 @@ -20,12 +20,22 @@ #ifndef MPD_TAG_PRINT_HXX #define MPD_TAG_PRINT_HXX +#include <stdint.h> + +enum TagType : uint8_t; + struct Tag; class Client; void tag_print_types(Client &client); void +tag_print(Client &client, TagType type, const char *value); + +void +tag_print_values(Client &client, const Tag &tag); + +void tag_print(Client &client, const Tag &tag); #endif diff --git a/src/TagSave.cxx b/src/TagSave.cxx index b20d986c2..c47d778d3 100644 --- a/src/TagSave.cxx +++ b/src/TagSave.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 @@ -20,19 +20,19 @@ #include "config.h" #include "TagSave.hxx" #include "tag/Tag.hxx" -#include "Song.hxx" +#include "fs/io/BufferedOutputStream.hxx" + +#define SONG_TIME "Time: " void -tag_save(FILE *file, const Tag &tag) +tag_save(BufferedOutputStream &os, const Tag &tag) { if (tag.time >= 0) - fprintf(file, SONG_TIME "%i\n", tag.time); + os.Format(SONG_TIME "%i\n", tag.time); if (tag.has_playlist) - fprintf(file, "Playlist: yes\n"); + os.Format("Playlist: yes\n"); - for (unsigned i = 0; i < tag.num_items; i++) - fprintf(file, "%s: %s\n", - tag_item_names[tag.items[i]->type], - tag.items[i]->value); + for (const auto &i : tag) + os.Format("%s: %s\n", tag_item_names[i.type], i.value); } diff --git a/src/TagSave.hxx b/src/TagSave.hxx index 0b1359c89..fd4b91f98 100644 --- a/src/TagSave.hxx +++ b/src/TagSave.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 @@ -20,11 +20,10 @@ #ifndef MPD_TAG_SAVE_HXX #define MPD_TAG_SAVE_HXX -#include <stdio.h> - struct Tag; +class BufferedOutputStream; void -tag_save(FILE *file, const Tag &tag); +tag_save(BufferedOutputStream &os, const Tag &tag); #endif diff --git a/src/TagStream.cxx b/src/TagStream.cxx new file mode 100644 index 000000000..639763373 --- /dev/null +++ b/src/TagStream.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 "TagStream.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "input/InputStream.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <assert.h> + +/** + * Does the #DecoderPlugin support either the suffix or the MIME type? + */ +gcc_pure +static bool +CheckDecoderPlugin(const DecoderPlugin &plugin, + const char *suffix, const char *mime) +{ + return (mime != nullptr && plugin.SupportsMimeType(mime)) || + (suffix != nullptr && plugin.SupportsSuffix(suffix)); +} + +bool +tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx) +{ + assert(is.IsReady()); + + const char *const suffix = uri_get_suffix(is.GetURI()); + const char *const mime = is.GetMimeType(); + + if (suffix == nullptr && mime == nullptr) + return false; + + return decoder_plugins_try([suffix, mime, &is, + &handler, ctx](const DecoderPlugin &plugin){ + is.LockRewind(IgnoreError()); + + return CheckDecoderPlugin(plugin, suffix, mime) && + plugin.ScanStream(is, handler, ctx); + }); +} + +bool +tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx) +{ + Mutex mutex; + Cond cond; + + InputStream *is = InputStream::OpenReady(uri, mutex, cond, + IgnoreError()); + if (is == nullptr) + return false; + + bool success = tag_stream_scan(*is, handler, ctx); + delete is; + return success; +} diff --git a/src/TagStream.hxx b/src/TagStream.hxx new file mode 100644 index 000000000..71dd71ff7 --- /dev/null +++ b/src/TagStream.hxx @@ -0,0 +1,41 @@ +/* + * 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_TAG_STREAM_HXX +#define MPD_TAG_STREAM_HXX + +#include "check.h" + +class InputStream; +struct tag_handler; + +/** + * Scan the tags of an #InputStream. Invokes matching decoder + * plugins, but does not invoke the special "APE" and "ID3" scanners. + * + * @return true if the file was recognized (even if no metadata was + * found) + */ +bool +tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx); + +bool +tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx); + +#endif diff --git a/src/TextFile.cxx b/src/TextFile.cxx deleted file mode 100644 index 4a64ee963..000000000 --- a/src/TextFile.cxx +++ /dev/null @@ -1,77 +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 "TextFile.hxx" -#include "fs/Path.hxx" -#include "fs/FileSystem.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -TextFile::TextFile(Path path_fs) - :file(FOpen(path_fs, FOpenMode::ReadText)), - buffer(g_string_sized_new(step)) {} - -TextFile::~TextFile() -{ - if (file != nullptr) - fclose(file); - - g_string_free(buffer, true); -} - -char * -TextFile::ReadLine() -{ - gsize length = 0, i; - char *p; - - assert(file != nullptr); - assert(buffer != nullptr); - assert(buffer->allocated_len >= step); - - while (buffer->len < max_length) { - p = fgets(buffer->str + length, - buffer->allocated_len - length, file); - if (p == nullptr) { - if (length == 0 || ferror(file)) - return nullptr; - break; - } - - i = strlen(buffer->str + length); - length += i; - if (i < step - 1 || buffer->str[length - 1] == '\n') - break; - - g_string_set_size(buffer, length + step); - } - - /* remove the newline characters */ - if (buffer->str[length - 1] == '\n') - --length; - if (buffer->str[length - 1] == '\r') - --length; - - g_string_set_size(buffer, length); - return buffer->str; -} diff --git a/src/TextFile.hxx b/src/TextFile.hxx deleted file mode 100644 index 9d8608711..000000000 --- a/src/TextFile.hxx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEXT_FILE_HXX -#define MPD_TEXT_FILE_HXX - -#include "Compiler.h" - -#include <stdio.h> - -class Path; -typedef struct _GString GString; - -class TextFile { - static constexpr size_t max_length = 512 * 1024; - static constexpr size_t step = 1024; - - FILE *const file; - - GString *const buffer; - -public: - TextFile(Path path_fs); - - TextFile(const TextFile &other) = delete; - - ~TextFile(); - - bool HasFailed() const { - return gcc_unlikely(file == nullptr); - } - - /** - * Reads a line from the input file, and strips trailing - * space. There is a reasonable maximum line length, only to - * prevent denial of service. - * - * @param file the source file, opened in text mode - * @param buffer an allocator for the buffer - * @return a pointer to the line, or nullptr on end-of-file or error - */ - char *ReadLine(); -}; - -#endif diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx deleted file mode 100644 index 36a726aa6..000000000 --- a/src/TextInputStream.cxx +++ /dev/null @@ -1,79 +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 "TextInputStream.hxx" -#include "InputStream.hxx" -#include "util/CharUtil.hxx" -#include "util/fifo_buffer.h" -#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/TextInputStream.hxx b/src/TextInputStream.hxx deleted file mode 100644 index a6c15f670..000000000 --- a/src/TextInputStream.hxx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TEXT_INPUT_STREAM_HXX -#define MPD_TEXT_INPUT_STREAM_HXX - -#include "util/FifoBuffer.hxx" - -#include <string> - -struct InputStream; -struct fifo_buffer; - -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/TimePrint.cxx b/src/TimePrint.cxx index ee165d8e9..5526ec7d6 100644 --- a/src/TimePrint.cxx +++ b/src/TimePrint.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,7 +19,7 @@ #include "config.h" #include "TimePrint.hxx" -#include "Client.hxx" +#include "client/Client.hxx" void time_print(Client &client, const char *name, time_t t) diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx index 55e235b66..afdb3c1c9 100644 --- a/src/TimePrint.hxx +++ b/src/TimePrint.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/Timer.cxx b/src/Timer.cxx deleted file mode 100644 index 661aa29ee..000000000 --- a/src/Timer.cxx +++ /dev/null @@ -1,82 +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 "Timer.hxx" -#include "AudioFormat.hxx" -#include "system/Clock.hxx" - -#include <glib.h> - -#include <limits> - -#include <assert.h> -#include <limits.h> -#include <stddef.h> - -Timer::Timer(const AudioFormat af) - : time(0), - started(false), - rate(af.sample_rate * af.GetFrameSize()) -{ -} - -void Timer::Start() -{ - time = MonotonicClockUS(); - started = true; -} - -void Timer::Reset() -{ - time = 0; - started = false; -} - -void Timer::Add(int size) -{ - assert(started); - - // (size samples) / (rate samples per second) = duration seconds - // duration seconds * 1000000 = duration us - time += ((uint64_t)size * 1000000) / rate; -} - -unsigned Timer::GetDelay() const -{ - int64_t delay = (int64_t)(time - MonotonicClockUS()) / 1000; - if (delay < 0) - return 0; - - if (delay > std::numeric_limits<int>::max()) - delay = std::numeric_limits<int>::max(); - - return delay; -} - -void Timer::Synchronize() const -{ - int64_t sleep_duration; - - assert(started); - - sleep_duration = time - MonotonicClockUS(); - if (sleep_duration > 0) - g_usleep(sleep_duration); -} diff --git a/src/Timer.hxx b/src/Timer.hxx deleted file mode 100644 index c8b756e9c..000000000 --- a/src/Timer.hxx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_TIMER_HXX -#define MPD_TIMER_HXX - -#include <stdint.h> - -struct AudioFormat; - -class Timer { - uint64_t time; - bool started; - const int rate; -public: - explicit Timer(AudioFormat af); - - bool IsStarted() const { return started; } - - void Start(); - void Reset(); - - void Add(int size); - - /** - * Returns the number of milliseconds to sleep to get back to sync. - */ - unsigned GetDelay() const; - - void Synchronize() const; -}; - -#endif diff --git a/src/UpdateArchive.cxx b/src/UpdateArchive.cxx deleted file mode 100644 index 3139a5926..000000000 --- a/src/UpdateArchive.cxx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateArchive.hxx" -#include "UpdateInternal.hxx" -#include "UpdateDomain.hxx" -#include "DatabaseLock.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "Mapper.hxx" -#include "fs/AllocatedPath.hxx" -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "ArchiveVisitor.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <string> - -#include <string.h> - -static void -update_archive_tree(Directory &directory, const char *name) -{ - const char *tmp = strchr(name, '/'); - if (tmp) { - const std::string child_name(name, tmp); - //add dir is not there already - db_lock(); - Directory *subdir = - directory.MakeChild(child_name.c_str()); - subdir->device = DEVICE_INARCHIVE; - db_unlock(); - - //create directories first - update_archive_tree(*subdir, tmp+1); - } else { - if (strlen(name) == 0) { - LogWarning(update_domain, - "archive returned directory only"); - return; - } - - //add file - db_lock(); - Song *song = directory.FindSong(name); - db_unlock(); - if (song == nullptr) { - song = Song::LoadFile(name, directory); - if (song != nullptr) { - db_lock(); - directory.AddSong(song); - db_unlock(); - - modified = true; - FormatDefault(update_domain, "added %s/%s", - directory.GetPath(), name); - } - } - } -} - -/** - * Updates the file listing from an archive file. - * - * @param parent the parent directory the archive file resides in - * @param name the UTF-8 encoded base name of the archive file - * @param st stat() information on the archive file - * @param plugin the archive plugin which fits this archive type - */ -static void -update_archive_file2(Directory &parent, const char *name, - const struct stat *st, - const struct archive_plugin *plugin) -{ - db_lock(); - Directory *directory = parent.FindChild(name); - db_unlock(); - - if (directory != nullptr && directory->mtime == st->st_mtime && - !walk_discard) - /* MPD has already scanned the archive, and it hasn't - changed since - don't consider updating it */ - return; - - const auto path_fs = map_directory_child_fs(parent, name); - - /* open archive */ - Error error; - ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), error); - if (file == nullptr) { - LogError(error); - return; - } - - FormatDebug(update_domain, "archive %s opened", path_fs.c_str()); - - if (directory == nullptr) { - FormatDebug(update_domain, - "creating archive directory: %s", name); - db_lock(); - directory = parent.CreateChild(name); - /* mark this directory as archive (we use device for - this) */ - directory->device = DEVICE_INARCHIVE; - db_unlock(); - } - - directory->mtime = st->st_mtime; - - class UpdateArchiveVisitor final : public ArchiveVisitor { - Directory *directory; - - public: - UpdateArchiveVisitor(Directory *_directory) - :directory(_directory) {} - - virtual void VisitArchiveEntry(const char *path_utf8) override { - FormatDebug(update_domain, - "adding archive file: %s", path_utf8); - update_archive_tree(*directory, path_utf8); - } - }; - - UpdateArchiveVisitor visitor(directory); - file->Visit(visitor); - file->Close(); -} - -bool -update_archive_file(Directory &directory, - const char *name, const char *suffix, - const struct stat *st) -{ -#ifdef ENABLE_ARCHIVE - const struct archive_plugin *plugin = - archive_plugin_from_suffix(suffix); - if (plugin == nullptr) - return false; - - update_archive_file2(directory, name, st, plugin); - return true; -#else - (void)directory; - (void)name; - (void)suffix; - (void)st; - - return false; -#endif -} diff --git a/src/UpdateArchive.hxx b/src/UpdateArchive.hxx deleted file mode 100644 index 8f52ca0b6..000000000 --- a/src/UpdateArchive.hxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_ARCHIVE_HXX -#define MPD_UPDATE_ARCHIVE_HXX - -#include "check.h" -#include "Compiler.h" - -#include <sys/stat.h> - -struct Directory; -struct archive_plugin; - -#ifdef ENABLE_ARCHIVE - -bool -update_archive_file(Directory &directory, - const char *name, const char *suffix, - const struct stat *st); - -#else - -static inline bool -update_archive_file(gcc_unused Directory &directory, - gcc_unused const char *name, - gcc_unused const char *suffix, - gcc_unused const struct stat *st) -{ - return false; -} - -#endif - -#endif diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx deleted file mode 100644 index 80f059734..000000000 --- a/src/UpdateContainer.cxx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateContainer.hxx" -#include "UpdateInternal.hxx" -#include "UpdateDatabase.hxx" -#include "UpdateDomain.hxx" -#include "DatabaseLock.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "DecoderPlugin.hxx" -#include "Mapper.hxx" -#include "fs/AllocatedPath.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagBuilder.hxx" -#include "Log.hxx" - -#include <glib.h> - -/** - * Create the specified directory object if it does not exist already - * or if the #stat object indicates that it has been modified since - * the last update. Returns nullptr when it exists already and is - * unmodified. - * - * The caller must lock the database. - */ -static Directory * -make_directory_if_modified(Directory &parent, const char *name, - const struct stat *st) -{ - Directory *directory = parent.FindChild(name); - - // directory exists already - if (directory != nullptr) { - if (directory->mtime == st->st_mtime && !walk_discard) { - /* not modified */ - return nullptr; - } - - delete_directory(directory); - modified = true; - } - - directory = parent.MakeChild(name); - directory->mtime = st->st_mtime; - return directory; -} - -bool -update_container_file(Directory &directory, - const char *name, - const struct stat *st, - const DecoderPlugin &plugin) -{ - if (plugin.container_scan == nullptr) - return false; - - db_lock(); - Directory *contdir = make_directory_if_modified(directory, name, st); - if (contdir == nullptr) { - /* not modified */ - db_unlock(); - return true; - } - - contdir->device = DEVICE_CONTAINER; - db_unlock(); - - const auto pathname = map_directory_child_fs(directory, name); - - char *vtrack; - unsigned int tnum = 0; - TagBuilder tag_builder; - while ((vtrack = plugin.container_scan(pathname.c_str(), ++tnum)) != nullptr) { - Song *song = Song::NewFile(vtrack, contdir); - - // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - const auto child_path_fs = - map_directory_child_fs(*contdir, vtrack); - - plugin.ScanFile(child_path_fs.c_str(), - add_tag_handler, &tag_builder); - - if (tag_builder.IsDefined()) - song->tag = tag_builder.Commit(); - else - tag_builder.Clear(); - - db_lock(); - contdir->AddSong(song); - db_unlock(); - - modified = true; - - FormatDefault(update_domain, "added %s/%s", - directory.GetPath(), vtrack); - g_free(vtrack); - } - - if (tnum == 1) { - db_lock(); - delete_directory(contdir); - db_unlock(); - return false; - } else - return true; -} diff --git a/src/UpdateContainer.hxx b/src/UpdateContainer.hxx deleted file mode 100644 index 3b54fb39f..000000000 --- a/src/UpdateContainer.hxx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_CONTAINER_HXX -#define MPD_UPDATE_CONTAINER_HXX - -#include "check.h" - -#include <sys/stat.h> - -struct Directory; -struct DecoderPlugin; - -bool -update_container_file(Directory &directory, - const char *name, - const struct stat *st, - const DecoderPlugin &plugin); - -#endif diff --git a/src/UpdateDatabase.cxx b/src/UpdateDatabase.cxx deleted file mode 100644 index 5dd2bb496..000000000 --- a/src/UpdateDatabase.cxx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateDatabase.hxx" -#include "UpdateRemove.hxx" -#include "PlaylistVector.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "DatabaseLock.hxx" - -#include <assert.h> -#include <stddef.h> - -void -delete_song(Directory &dir, Song *del) -{ - assert(del->parent == &dir); - - /* first, prevent traversers in main task from getting this */ - dir.RemoveSong(del); - - db_unlock(); /* temporary unlock, because update_remove_song() blocks */ - - /* now take it out of the playlist (in the main_task) */ - update_remove_song(del); - - /* finally, all possible references gone, free it */ - del->Free(); - - db_lock(); -} - -/** - * Recursively remove all sub directories and songs from a directory, - * leaving an empty directory. - * - * Caller must lock the #db_mutex. - */ -static void -clear_directory(Directory &directory) -{ - Directory *child, *n; - directory_for_each_child_safe(child, n, directory) - delete_directory(child); - - Song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - assert(song->parent == &directory); - delete_song(directory, song); - } -} - -void -delete_directory(Directory *directory) -{ - assert(directory->parent != nullptr); - - clear_directory(*directory); - - directory->Delete(); -} - -bool -delete_name_in(Directory &parent, const char *name) -{ - bool modified = false; - - db_lock(); - Directory *directory = parent.FindChild(name); - - if (directory != nullptr) { - delete_directory(directory); - modified = true; - } - - Song *song = parent.FindSong(name); - if (song != nullptr) { - delete_song(parent, song); - modified = true; - } - - parent.playlists.erase(name); - - db_unlock(); - - return modified; -} diff --git a/src/UpdateDatabase.hxx b/src/UpdateDatabase.hxx deleted file mode 100644 index 0462dc778..000000000 --- a/src/UpdateDatabase.hxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_DATABASE_HXX -#define MPD_UPDATE_DATABASE_HXX - -#include "check.h" - -struct Directory; -struct Song; - -/** - * Caller must lock the #db_mutex. - */ -void -delete_song(Directory &parent, Song *song); - -/** - * Recursively free a directory and all its contents. - * - * Caller must lock the #db_mutex. - */ -void -delete_directory(Directory *directory); - -/** - * Caller must NOT lock the #db_mutex. - * - * @return true if the database was modified - */ -bool -delete_name_in(Directory &parent, const char *name); - -#endif diff --git a/src/UpdateDomain.cxx b/src/UpdateDomain.cxx deleted file mode 100644 index a2bbd5b70..000000000 --- a/src/UpdateDomain.cxx +++ /dev/null @@ -1,23 +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 "UpdateDomain.hxx" -#include "util/Domain.hxx" - -const Domain update_domain("update"); diff --git a/src/UpdateDomain.hxx b/src/UpdateDomain.hxx deleted file mode 100644 index e7528a57e..000000000 --- a/src/UpdateDomain.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_DOMAIN_HXX -#define MPD_UPDATE_DOMAIN_HXX - -extern const class Domain update_domain; - -#endif diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx deleted file mode 100644 index 12ea126a9..000000000 --- a/src/UpdateGlue.cxx +++ /dev/null @@ -1,181 +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 "UpdateGlue.hxx" -#include "UpdateQueue.hxx" -#include "UpdateWalk.hxx" -#include "UpdateRemove.hxx" -#include "UpdateDomain.hxx" -#include "Mapper.hxx" -#include "DatabaseSimple.hxx" -#include "Idle.hxx" -#include "GlobalEvents.hxx" -#include "util/Error.hxx" -#include "Log.hxx" -#include "Stats.hxx" -#include "Main.hxx" -#include "Instance.hxx" -#include "system/FatalError.hxx" -#include "thread/Id.hxx" -#include "thread/Thread.hxx" - -#include <assert.h> - -static enum update_progress { - UPDATE_PROGRESS_IDLE = 0, - UPDATE_PROGRESS_RUNNING = 1, - UPDATE_PROGRESS_DONE = 2 -} progress; - -static bool modified; - -static Thread update_thread; - -static const unsigned update_task_id_max = 1 << 15; - -static unsigned update_task_id; - -static UpdateQueueItem next; - -unsigned -isUpdatingDB(void) -{ - return next.id; -} - -static void -update_task(gcc_unused void *ctx) -{ - if (!next.path_utf8.empty()) - FormatDebug(update_domain, "starting: %s", - next.path_utf8.c_str()); - else - LogDebug(update_domain, "starting"); - - modified = update_walk(next.path_utf8.c_str(), next.discard); - - if (modified || !db_exists()) { - Error error; - if (!db_save(error)) - LogError(error, "Failed to save database"); - } - - if (!next.path_utf8.empty()) - FormatDebug(update_domain, "finished: %s", - next.path_utf8.c_str()); - else - LogDebug(update_domain, "finished"); - - progress = UPDATE_PROGRESS_DONE; - GlobalEvents::Emit(GlobalEvents::UPDATE); -} - -static void -spawn_update_task(UpdateQueueItem &&i) -{ - assert(main_thread.IsInside()); - - progress = UPDATE_PROGRESS_RUNNING; - modified = false; - - next = std::move(i); - - Error error; - if (!update_thread.Start(update_task, nullptr, error)) - FatalError(error); - - FormatDebug(update_domain, - "spawned thread for update job id %i", next.id); -} - -static unsigned -generate_update_id() -{ - unsigned id = update_task_id + 1; - if (id > update_task_id_max) - id = 1; - return id; -} - -unsigned -update_enqueue(const char *path, bool discard) -{ - assert(main_thread.IsInside()); - - if (!db_is_simple() || !mapper_has_music_directory()) - return 0; - - if (progress != UPDATE_PROGRESS_IDLE) { - const unsigned id = generate_update_id(); - if (!update_queue_push(path, discard, id)) - return 0; - - update_task_id = id; - return id; - } - - const unsigned id = update_task_id = generate_update_id(); - spawn_update_task(UpdateQueueItem(path, discard, id)); - - idle_add(IDLE_UPDATE); - - return id; -} - -/** - * Called in the main thread after the database update is finished. - */ -static void update_finished_event(void) -{ - assert(progress == UPDATE_PROGRESS_DONE); - assert(next.IsDefined()); - - update_thread.Join(); - next = UpdateQueueItem(); - - idle_add(IDLE_UPDATE); - - if (modified) - /* send "idle" events */ - instance->DatabaseModified(); - - auto i = update_queue_shift(); - if (i.IsDefined()) { - /* schedule the next path */ - spawn_update_task(std::move(i)); - } else { - progress = UPDATE_PROGRESS_IDLE; - - stats_update(); - } -} - -void update_global_init(void) -{ - GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event); - - update_remove_global_init(); - update_walk_global_init(); -} - -void update_global_finish(void) -{ - update_walk_global_finish(); -} diff --git a/src/UpdateGlue.hxx b/src/UpdateGlue.hxx deleted file mode 100644 index c9fc0f9a1..000000000 --- a/src/UpdateGlue.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_GLUE_HXX -#define MPD_UPDATE_GLUE_HXX - -#include "Compiler.h" - -void update_global_init(void); - -void update_global_finish(void); - -unsigned -isUpdatingDB(void); - -/** - * Add this path to the database update queue. - * - * @param path a path to update; if an empty string, - * the whole music directory is updated - * @return the job id, or 0 on error - */ -gcc_nonnull_all -unsigned -update_enqueue(const char *path, bool discard); - -#endif diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx deleted file mode 100644 index e70e07db0..000000000 --- a/src/UpdateIO.cxx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateIO.hxx" -#include "src/UpdateDomain.hxx" -#include "Directory.hxx" -#include "Mapper.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "Log.hxx" - -#include <errno.h> -#include <unistd.h> - -int -stat_directory(const Directory &directory, struct stat *st) -{ - const auto path_fs = map_directory_fs(directory); - if (path_fs.IsNull()) - return -1; - - if (!StatFile(path_fs, *st)) { - int error = errno; - const std::string path_utf8 = path_fs.ToUTF8(); - FormatErrno(update_domain, error, - "Failed to stat %s", path_utf8.c_str()); - return -1; - } - - return 0; -} - -int -stat_directory_child(const Directory &parent, const char *name, - struct stat *st) -{ - const auto path_fs = map_directory_child_fs(parent, name); - if (path_fs.IsNull()) - return -1; - - if (!StatFile(path_fs, *st)) { - int error = errno; - const std::string path_utf8 = path_fs.ToUTF8(); - FormatErrno(update_domain, error, - "Failed to stat %s", path_utf8.c_str()); - return -1; - } - - return 0; -} - -bool -directory_exists(const Directory &directory) -{ - const auto path_fs = map_directory_fs(directory); - if (path_fs.IsNull()) - /* invalid path: cannot exist */ - return false; - - return directory.device == DEVICE_INARCHIVE || - directory.device == DEVICE_CONTAINER - ? FileExists(path_fs) - : DirectoryExists(path_fs); -} - -bool -directory_child_is_regular(const Directory &directory, - const char *name_utf8) -{ - const auto path_fs = map_directory_child_fs(directory, name_utf8); - if (path_fs.IsNull()) - return false; - - return FileExists(path_fs); -} - -bool -directory_child_access(const Directory &directory, - const char *name, int mode) -{ -#ifdef WIN32 - /* CheckAccess() is useless on WIN32 */ - (void)directory; - (void)name; - (void)mode; - return true; -#else - const auto path = map_directory_child_fs(directory, name); - if (path.IsNull()) - /* something went wrong, but that isn't a permission - problem */ - return true; - - return CheckAccess(path, mode) || errno != EACCES; -#endif -} diff --git a/src/UpdateIO.hxx b/src/UpdateIO.hxx deleted file mode 100644 index 9d6c197f2..000000000 --- a/src/UpdateIO.hxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_IO_HXX -#define MPD_UPDATE_IO_HXX - -#include "check.h" - -#include <sys/stat.h> - -struct Directory; - -int -stat_directory(const Directory &directory, struct stat *st); - -int -stat_directory_child(const Directory &parent, const char *name, - struct stat *st); - -bool -directory_exists(const Directory &directory); - -bool -directory_child_is_regular(const Directory &directory, - const char *name_utf8); - -/** - * Checks if the given permissions on the mapped file are given. - */ -bool -directory_child_access(const Directory &directory, - const char *name, int mode); - -#endif diff --git a/src/UpdateInternal.hxx b/src/UpdateInternal.hxx deleted file mode 100644 index de0850ece..000000000 --- a/src/UpdateInternal.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_INTERNAL_H -#define MPD_UPDATE_INTERNAL_H - -#include "check.h" - -extern bool walk_discard; -extern bool modified; - -#endif diff --git a/src/UpdateQueue.cxx b/src/UpdateQueue.cxx deleted file mode 100644 index 2a30e5d5f..000000000 --- a/src/UpdateQueue.cxx +++ /dev/null @@ -1,49 +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 "UpdateQueue.hxx" - -#include <queue> -#include <list> - -static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32; - -static std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue; - -bool -update_queue_push(const char *path, bool discard, unsigned id) -{ - if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE) - return false; - - update_queue.emplace(path, discard, id); - return true; -} - -UpdateQueueItem -update_queue_shift() -{ - if (update_queue.empty()) - return UpdateQueueItem(); - - auto i = std::move(update_queue.front()); - update_queue.pop(); - return i; -} diff --git a/src/UpdateQueue.hxx b/src/UpdateQueue.hxx deleted file mode 100644 index 2769cc589..000000000 --- a/src/UpdateQueue.hxx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_QUEUE_HXX -#define MPD_UPDATE_QUEUE_HXX - -#include "check.h" - -#include <string> - -struct UpdateQueueItem { - std::string path_utf8; - unsigned id; - bool discard; - - UpdateQueueItem():id(0) {} - UpdateQueueItem(const char *_path, bool _discard, - unsigned _id) - :path_utf8(_path), id(_id), discard(_discard) {} - - bool IsDefined() const { - return id != 0; - } -}; - -bool -update_queue_push(const char *path, bool discard, unsigned id); - -UpdateQueueItem -update_queue_shift(); - -#endif diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx deleted file mode 100644 index f4043b2f3..000000000 --- a/src/UpdateRemove.cxx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateRemove.hxx" -#include "UpdateDomain.hxx" -#include "Playlist.hxx" -#include "GlobalEvents.hxx" -#include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "Song.hxx" -#include "Main.hxx" -#include "Instance.hxx" -#include "Log.hxx" - -#ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" -#include "SongSticker.hxx" -#endif - -#include <assert.h> - -static const Song *removed_song; - -static Mutex remove_mutex; -static Cond remove_cond; - -/** - * Safely remove a song from the database. This must be done in the - * main task, to be sure that there is no pointer left to it. - */ -static void -song_remove_event(void) -{ - assert(removed_song != nullptr); - - { - const auto uri = removed_song->GetURI(); - FormatDefault(update_domain, "removing %s", uri.c_str()); - } - -#ifdef ENABLE_SQLITE - /* if the song has a sticker, remove it */ - if (sticker_enabled()) - sticker_song_delete(removed_song); -#endif - - instance->DeleteSong(*removed_song); - - /* clear "removed_song" and send signal to update thread */ - remove_mutex.lock(); - removed_song = nullptr; - remove_cond.signal(); - remove_mutex.unlock(); -} - -void -update_remove_global_init(void) -{ - GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event); -} - -void -update_remove_song(const Song *song) -{ - assert(removed_song == nullptr); - - removed_song = song; - - GlobalEvents::Emit(GlobalEvents::DELETE); - - remove_mutex.lock(); - - while (removed_song != nullptr) - remove_cond.wait(remove_mutex); - - remove_mutex.unlock(); -} diff --git a/src/UpdateRemove.hxx b/src/UpdateRemove.hxx deleted file mode 100644 index bef27d766..000000000 --- a/src/UpdateRemove.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_REMOVE_HXX -#define MPD_UPDATE_REMOVE_HXX - -#include "check.h" - -struct Song; - -void -update_remove_global_init(void); - -/** - * Sends a signal to the main thread which will in turn remove the - * song: from the sticker database and from the playlist. This - * serialized access is implemented to avoid excessive locking. - */ -void -update_remove_song(const Song *song); - -#endif diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx deleted file mode 100644 index bfab5c4a0..000000000 --- a/src/UpdateSong.cxx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateSong.hxx" -#include "UpdateInternal.hxx" -#include "UpdateIO.hxx" -#include "UpdateDatabase.hxx" -#include "UpdateContainer.hxx" -#include "UpdateDomain.hxx" -#include "DatabaseLock.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "DecoderPlugin.hxx" -#include "DecoderList.hxx" -#include "Log.hxx" - -#include <unistd.h> - -static void -update_song_file2(Directory &directory, - const char *name, const struct stat *st, - const DecoderPlugin &plugin) -{ - db_lock(); - Song *song = directory.FindSong(name); - db_unlock(); - - if (!directory_child_access(directory, name, R_OK)) { - FormatError(update_domain, - "no read permissions on %s/%s", - directory.GetPath(), name); - if (song != nullptr) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (!(song != nullptr && st->st_mtime == song->mtime && - !walk_discard) && - update_container_file(directory, name, st, plugin)) { - if (song != nullptr) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (song == nullptr) { - FormatDebug(update_domain, "reading %s/%s", - directory.GetPath(), name); - song = Song::LoadFile(name, &directory); - if (song == nullptr) { - FormatDebug(update_domain, - "ignoring unrecognized file %s/%s", - directory.GetPath(), name); - return; - } - - db_lock(); - directory.AddSong(song); - db_unlock(); - - modified = true; - FormatDefault(update_domain, "added %s/%s", - directory.GetPath(), name); - } else if (st->st_mtime != song->mtime || walk_discard) { - FormatDefault(update_domain, "updating %s/%s", - directory.GetPath(), name); - if (!song->UpdateFile()) { - FormatDebug(update_domain, - "deleting unrecognized file %s/%s", - directory.GetPath(), name); - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - modified = true; - } -} - -bool -update_song_file(Directory &directory, - const char *name, const char *suffix, - const struct stat *st) -{ - const struct DecoderPlugin *plugin = - decoder_plugin_from_suffix(suffix, nullptr); - if (plugin == nullptr) - return false; - - update_song_file2(directory, name, st, *plugin); - return true; -} diff --git a/src/UpdateSong.hxx b/src/UpdateSong.hxx deleted file mode 100644 index 00a7bfd27..000000000 --- a/src/UpdateSong.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_SONG_HXX -#define MPD_UPDATE_SONG_HXX - -#include "check.h" - -#include <sys/stat.h> - -struct Directory; - -bool -update_song_file(Directory &directory, - const char *name, const char *suffix, - const struct stat *st); - -#endif diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx deleted file mode 100644 index d4586456b..000000000 --- a/src/UpdateWalk.cxx +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "UpdateWalk.hxx" -#include "UpdateIO.hxx" -#include "UpdateDatabase.hxx" -#include "UpdateSong.hxx" -#include "UpdateArchive.hxx" -#include "UpdateDomain.hxx" -#include "DatabaseLock.hxx" -#include "DatabaseSimple.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "PlaylistVector.hxx" -#include "PlaylistRegistry.hxx" -#include "Mapper.hxx" -#include "ExcludeList.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/Traits.hxx" -#include "fs/FileSystem.hxx" -#include "fs/DirectoryReader.hxx" -#include "util/UriUtil.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> - -bool walk_discard; -bool modified; - -#ifndef WIN32 - -static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true; -static constexpr bool DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true; - -static bool follow_inside_symlinks; -static bool follow_outside_symlinks; - -#endif - -void -update_walk_global_init(void) -{ -#ifndef WIN32 - follow_inside_symlinks = - config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, - DEFAULT_FOLLOW_INSIDE_SYMLINKS); - - follow_outside_symlinks = - config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); -#endif -} - -void -update_walk_global_finish(void) -{ -} - -static void -directory_set_stat(Directory &dir, const struct stat *st) -{ - dir.inode = st->st_ino; - dir.device = st->st_dev; - dir.have_stat = true; -} - -static void -remove_excluded_from_directory(Directory &directory, - const ExcludeList &exclude_list) -{ - db_lock(); - - Directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - const auto name_fs = AllocatedPath::FromUTF8(child->GetName()); - - if (name_fs.IsNull() || exclude_list.Check(name_fs)) { - delete_directory(child); - modified = true; - } - } - - Song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - assert(song->parent == &directory); - - const auto name_fs = AllocatedPath::FromUTF8(song->uri); - if (name_fs.IsNull() || exclude_list.Check(name_fs)) { - delete_song(directory, song); - modified = true; - } - } - - db_unlock(); -} - -static void -purge_deleted_from_directory(Directory &directory) -{ - Directory *child, *n; - directory_for_each_child_safe(child, n, directory) { - if (directory_exists(*child)) - continue; - - db_lock(); - delete_directory(child); - db_unlock(); - - modified = true; - } - - Song *song, *ns; - directory_for_each_song_safe(song, ns, directory) { - const auto path = map_song_fs(*song); - if (path.IsNull() || !FileExists(path)) { - db_lock(); - delete_song(directory, song); - db_unlock(); - - modified = true; - } - } - - for (auto i = directory.playlists.begin(), - end = directory.playlists.end(); - i != end;) { - if (!directory_child_is_regular(directory, i->name.c_str())) { - db_lock(); - i = directory.playlists.erase(i); - db_unlock(); - } else - ++i; - } -} - -#ifndef WIN32 -static int -update_directory_stat(Directory &directory) -{ - struct stat st; - if (stat_directory(directory, &st) < 0) - return -1; - - directory_set_stat(directory, &st); - return 0; -} -#endif - -static int -find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) -{ -#ifndef WIN32 - while (parent) { - if (!parent->have_stat && update_directory_stat(*parent) < 0) - return -1; - - if (parent->inode == inode && parent->device == device) { - LogDebug(update_domain, "recursive directory found"); - return 1; - } - - parent = parent->parent; - } -#else - (void)parent; - (void)inode; - (void)device; -#endif - - return 0; -} - -static bool -update_playlist_file2(Directory &directory, - const char *name, const char *suffix, - const struct stat *st) -{ - if (!playlist_suffix_supported(suffix)) - return false; - - PlaylistInfo pi(name, st->st_mtime); - - db_lock(); - if (directory.playlists.UpdateOrInsert(std::move(pi))) - modified = true; - db_unlock(); - return true; -} - -static bool -update_regular_file(Directory &directory, - const char *name, const struct stat *st) -{ - const char *suffix = uri_get_suffix(name); - if (suffix == nullptr) - return false; - - return update_song_file(directory, name, suffix, st) || - update_archive_file(directory, name, suffix, st) || - update_playlist_file2(directory, name, suffix, st); -} - -static bool -update_directory(Directory &directory, const struct stat *st); - -static void -update_directory_child(Directory &directory, - const char *name, const struct stat *st) -{ - assert(strchr(name, '/') == nullptr); - - if (S_ISREG(st->st_mode)) { - update_regular_file(directory, name, st); - } else if (S_ISDIR(st->st_mode)) { - if (find_inode_ancestor(&directory, st->st_ino, st->st_dev)) - return; - - db_lock(); - Directory *subdir = directory.MakeChild(name); - db_unlock(); - - assert(&directory == subdir->parent); - - if (!update_directory(*subdir, st)) { - db_lock(); - delete_directory(subdir); - db_unlock(); - } - } else { - FormatDebug(update_domain, - "%s is not a directory, archive or music", name); - } -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -gcc_pure -static bool skip_path(Path path_fs) -{ - const char *path = path_fs.c_str(); - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != nullptr; -} - -gcc_pure -static bool -skip_symlink(const Directory *directory, const char *utf8_name) -{ -#ifndef WIN32 - const auto path_fs = map_directory_child_fs(*directory, utf8_name); - if (path_fs.IsNull()) - return true; - - const auto target = ReadLink(path_fs); - if (target.IsNull()) - /* don't skip if this is not a symlink */ - return errno != EINVAL; - - if (!follow_inside_symlinks && !follow_outside_symlinks) { - /* ignore all symlinks */ - return true; - } else if (follow_inside_symlinks && follow_outside_symlinks) { - /* consider all symlinks */ - return false; - } - - const char *target_str = target.c_str(); - - if (PathTraits::IsAbsoluteFS(target_str)) { - /* if the symlink points to an absolute path, see if - that path is inside the music directory */ - const char *relative = map_to_relative_path(target_str); - return relative > target_str - ? !follow_inside_symlinks - : !follow_outside_symlinks; - } - - const char *p = target_str; - while (*p == '.') { - if (p[1] == '.' && PathTraits::IsSeparatorFS(p[2])) { - /* "../" moves to parent directory */ - directory = directory->parent; - if (directory == nullptr) { - /* we have moved outside the music - directory - skip this symlink - if such symlinks are not allowed */ - return !follow_outside_symlinks; - } - p += 3; - } else if (PathTraits::IsSeparatorFS(p[1])) - /* eliminate "./" */ - p += 2; - else - break; - } - - /* we are still in the music directory, so this symlink points - to a song which is already in the database - skip according - to the follow_inside_symlinks param*/ - return !follow_inside_symlinks; -#else - /* no symlink checking on WIN32 */ - - (void)directory; - (void)utf8_name; - - return false; -#endif -} - -static bool -update_directory(Directory &directory, const struct stat *st) -{ - assert(S_ISDIR(st->st_mode)); - - directory_set_stat(directory, st); - - const auto path_fs = map_directory_fs(directory); - if (path_fs.IsNull()) - return false; - - DirectoryReader reader(path_fs); - if (reader.HasFailed()) { - int error = errno; - const auto path_utf8 = path_fs.ToUTF8(); - FormatErrno(update_domain, error, - "Failed to open directory %s", - path_utf8.c_str()); - return false; - } - - ExcludeList exclude_list; - exclude_list.LoadFile(AllocatedPath::Build(path_fs, ".mpdignore")); - - if (!exclude_list.IsEmpty()) - remove_excluded_from_directory(directory, exclude_list); - - purge_deleted_from_directory(directory); - - while (reader.ReadEntry()) { - std::string utf8; - struct stat st2; - - const auto entry = reader.GetEntry(); - - if (skip_path(entry) || exclude_list.Check(entry)) - continue; - - utf8 = entry.ToUTF8(); - if (utf8.empty()) - continue; - - if (skip_symlink(&directory, utf8.c_str())) { - modified |= delete_name_in(directory, utf8.c_str()); - continue; - } - - if (stat_directory_child(directory, utf8.c_str(), &st2) == 0) - update_directory_child(directory, utf8.c_str(), &st2); - else - modified |= delete_name_in(directory, utf8.c_str()); - } - - directory.mtime = st->st_mtime; - - return true; -} - -static Directory * -directory_make_child_checked(Directory &parent, const char *name_utf8) -{ - db_lock(); - Directory *directory = parent.FindChild(name_utf8); - db_unlock(); - - if (directory != nullptr) - return directory; - - struct stat st; - if (stat_directory_child(parent, name_utf8, &st) < 0 || - find_inode_ancestor(&parent, st.st_ino, st.st_dev)) - return nullptr; - - if (skip_symlink(&parent, name_utf8)) - return nullptr; - - /* if we're adding directory paths, make sure to delete filenames - with potentially the same name */ - db_lock(); - Song *conflicting = parent.FindSong(name_utf8); - if (conflicting) - delete_song(parent, conflicting); - - directory = parent.CreateChild(name_utf8); - db_unlock(); - - directory_set_stat(*directory, &st); - return directory; -} - -static Directory * -directory_make_uri_parent_checked(const char *uri) -{ - Directory *directory = db_get_root(); - char *duplicated = g_strdup(uri); - char *name_utf8 = duplicated, *slash; - - while ((slash = strchr(name_utf8, '/')) != nullptr) { - *slash = 0; - - if (*name_utf8 == 0) - continue; - - directory = directory_make_child_checked(*directory, - name_utf8); - if (directory == nullptr) - break; - - name_utf8 = slash + 1; - } - - g_free(duplicated); - return directory; -} - -static void -update_uri(const char *uri) -{ - Directory *parent = directory_make_uri_parent_checked(uri); - if (parent == nullptr) - return; - - const char *name = PathTraits::GetBaseUTF8(uri); - - struct stat st; - if (!skip_symlink(parent, name) && - stat_directory_child(*parent, name, &st) == 0) - update_directory_child(*parent, name, &st); - else - modified |= delete_name_in(*parent, name); -} - -bool -update_walk(const char *path, bool discard) -{ - walk_discard = discard; - modified = false; - - if (path != nullptr && !isRootDirectory(path)) { - update_uri(path); - } else { - Directory *directory = db_get_root(); - struct stat st; - - if (stat_directory(*directory, &st) == 0) - update_directory(*directory, &st); - } - - return modified; -} diff --git a/src/UpdateWalk.hxx b/src/UpdateWalk.hxx deleted file mode 100644 index 62c0d0a8e..000000000 --- a/src/UpdateWalk.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_UPDATE_WALK_HXX -#define MPD_UPDATE_WALK_HXX - -#include "check.h" - -void -update_walk_global_init(void); - -void -update_walk_global_finish(void); - -/** - * Returns true if the database was modified. - */ -bool -update_walk(const char *path, bool discard); - -#endif diff --git a/src/Volume.cxx b/src/Volume.cxx deleted file mode 100644 index 6c5f8dc4d..000000000 --- a/src/Volume.cxx +++ /dev/null @@ -1,140 +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 "Volume.hxx" -#include "MixerAll.hxx" -#include "Idle.hxx" -#include "GlobalEvents.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -#define SW_VOLUME_STATE "sw_volume: " - -static constexpr Domain volume_domain("volume"); - -static unsigned volume_software_set = 100; - -/** the cached hardware mixer value; invalid if negative */ -static int last_hardware_volume = -1; -/** the age of #last_hardware_volume */ -static GTimer *hardware_volume_timer; - -/** - * Handler for #GlobalEvents::MIXER. - */ -static void -mixer_event_callback(void) -{ - /* flush the hardware volume cache */ - last_hardware_volume = -1; - - /* notify clients */ - idle_add(IDLE_MIXER); -} - -void volume_finish(void) -{ - g_timer_destroy(hardware_volume_timer); -} - -void volume_init(void) -{ - hardware_volume_timer = g_timer_new(); - - GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); -} - -int volume_level_get(void) -{ - assert(hardware_volume_timer != nullptr); - - if (last_hardware_volume >= 0 && - g_timer_elapsed(hardware_volume_timer, nullptr) < 1.0) - /* throttle access to hardware mixers */ - return last_hardware_volume; - - last_hardware_volume = mixer_all_get_volume(); - g_timer_start(hardware_volume_timer); - return last_hardware_volume; -} - -static bool software_volume_change(unsigned volume) -{ - assert(volume <= 100); - - volume_software_set = volume; - mixer_all_set_software_volume(volume); - - return true; -} - -static bool hardware_volume_change(unsigned volume) -{ - /* reset the cache */ - last_hardware_volume = -1; - - return mixer_all_set_volume(volume); -} - -bool volume_level_change(unsigned volume) -{ - assert(volume <= 100); - - volume_software_set = volume; - - idle_add(IDLE_MIXER); - - return hardware_volume_change(volume); -} - -bool -read_sw_volume_state(const char *line) -{ - char *end = nullptr; - long int sv; - - if (!g_str_has_prefix(line, SW_VOLUME_STATE)) - return false; - - line += sizeof(SW_VOLUME_STATE) - 1; - sv = strtol(line, &end, 10); - if (*end == 0 && sv >= 0 && sv <= 100) - software_volume_change(sv); - else - FormatWarning(volume_domain, - "Can't parse software volume: %s", line); - return true; -} - -void save_sw_volume_state(FILE *fp) -{ - fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set); -} - -unsigned -sw_volume_state_get_hash(void) -{ - return volume_software_set; -} diff --git a/src/Volume.hxx b/src/Volume.hxx deleted file mode 100644 index 6b937aca3..000000000 --- a/src/Volume.hxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VOLUME_HXX -#define MPD_VOLUME_HXX - -#include "Compiler.h" - -#include <stdio.h> - -void volume_init(void); - -void volume_finish(void); - -gcc_pure -int volume_level_get(void); - -bool volume_level_change(unsigned volume); - -bool -read_sw_volume_state(const char *line); - -void save_sw_volume_state(FILE *fp); - -/** - * Generates a hash number for the current state of the software - * volume control. This is used by timer_save_state_file() to - * determine whether the state has changed and the state file should - * be saved. - */ -gcc_pure -unsigned -sw_volume_state_get_hash(void); - -#endif diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx deleted file mode 100644 index 083647b42..000000000 --- a/src/ZeroconfAvahi.cxx +++ /dev/null @@ -1,278 +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 "ZeroconfAvahi.hxx" -#include "AvahiPoll.hxx" -#include "ZeroconfInternal.hxx" -#include "Listen.hxx" -#include "system/FatalError.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <avahi-client/client.h> -#include <avahi-client/publish.h> - -#include <avahi-common/watch.h> -#include <avahi-common/alternative.h> -#include <avahi-common/domain.h> -#include <avahi-common/malloc.h> -#include <avahi-common/error.h> - -#include <stddef.h> - -static constexpr Domain avahi_domain("avahi"); - -static char *avahiName; -static bool avahi_running; -static MyAvahiPoll *avahi_poll; -static AvahiClient *avahiClient; -static AvahiEntryGroup *avahiGroup; - -static void avahiRegisterService(AvahiClient * c); - -/* Callback when the EntryGroup changes state */ -static void avahiGroupCallback(AvahiEntryGroup * g, - AvahiEntryGroupState state, - gcc_unused void *userdata) -{ - char *n; - assert(g); - - FormatDebug(avahi_domain, - "Service group changed to state %d", state); - - switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - /* The entry group has been established successfully */ - FormatDefault(avahi_domain, - "Service '%s' successfully established.", - avahiName); - break; - - case AVAHI_ENTRY_GROUP_COLLISION: - /* A service name collision happened. Let's pick a new name */ - n = avahi_alternative_service_name(avahiName); - avahi_free(avahiName); - avahiName = n; - - FormatDefault(avahi_domain, - "Service name collision, renaming service to '%s'", - avahiName); - - /* And recreate the services */ - avahiRegisterService(avahi_entry_group_get_client(g)); - break; - - case AVAHI_ENTRY_GROUP_FAILURE: - FormatError(avahi_domain, - "Entry group failure: %s", - avahi_strerror(avahi_client_errno - (avahi_entry_group_get_client(g)))); - /* Some kind of failure happened while we were registering our services */ - avahi_running = false; - break; - - case AVAHI_ENTRY_GROUP_UNCOMMITED: - LogDebug(avahi_domain, "Service group is UNCOMMITED"); - break; - case AVAHI_ENTRY_GROUP_REGISTERING: - LogDebug(avahi_domain, "Service group is REGISTERING"); - } -} - -/* Registers a new service with avahi */ -static void avahiRegisterService(AvahiClient * c) -{ - FormatDebug(avahi_domain, "Registering service %s/%s", - SERVICE_TYPE, avahiName); - - int ret; - assert(c); - - /* If this is the first time we're called, - * let's create a new entry group */ - if (!avahiGroup) { - avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, nullptr); - if (!avahiGroup) { - FormatError(avahi_domain, - "Failed to create avahi EntryGroup: %s", - avahi_strerror(avahi_client_errno(c))); - goto fail; - } - } - - /* Add the service */ - /* TODO: This currently binds to ALL interfaces. - * We could maybe add a service per actual bound interface, - * if that's better. */ - ret = avahi_entry_group_add_service(avahiGroup, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - AvahiPublishFlags(0), - avahiName, SERVICE_TYPE, nullptr, - nullptr, listen_port, nullptr); - if (ret < 0) { - FormatError(avahi_domain, "Failed to add service %s: %s", - SERVICE_TYPE, avahi_strerror(ret)); - goto fail; - } - - /* Tell the server to register the service group */ - ret = avahi_entry_group_commit(avahiGroup); - if (ret < 0) { - FormatError(avahi_domain, "Failed to commit service group: %s", - avahi_strerror(ret)); - goto fail; - } - return; - -fail: - avahi_running = false; -} - -/* Callback when avahi changes state */ -static void avahiClientCallback(AvahiClient * c, AvahiClientState state, - gcc_unused void *userdata) -{ - int reason; - assert(c); - - /* Called whenever the client or server state changes */ - FormatDebug(avahi_domain, "Client changed to state %d", state); - - switch (state) { - case AVAHI_CLIENT_S_RUNNING: - LogDebug(avahi_domain, "Client is RUNNING"); - - /* The server has startup successfully and registered its host - * name on the network, so it's time to create our services */ - if (!avahiGroup) - avahiRegisterService(c); - break; - - case AVAHI_CLIENT_FAILURE: - reason = avahi_client_errno(c); - if (reason == AVAHI_ERR_DISCONNECTED) { - LogDefault(avahi_domain, - "Client Disconnected, will reconnect shortly"); - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = nullptr; - } - if (avahiClient) - avahi_client_free(avahiClient); - avahiClient = - avahi_client_new(avahi_poll, - AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, nullptr, - &reason); - if (!avahiClient) { - FormatWarning(avahi_domain, - "Could not reconnect: %s", - avahi_strerror(reason)); - avahi_running = false; - } - } else { - FormatWarning(avahi_domain, - "Client failure: %s (terminal)", - avahi_strerror(reason)); - avahi_running = false; - } - break; - - case AVAHI_CLIENT_S_COLLISION: - LogDebug(avahi_domain, "Client is COLLISION"); - - /* Let's drop our registered services. When the server is back - * in AVAHI_SERVER_RUNNING state we will register them - * again with the new host name. */ - if (avahiGroup) { - LogDebug(avahi_domain, "Resetting group"); - avahi_entry_group_reset(avahiGroup); - } - - break; - - case AVAHI_CLIENT_S_REGISTERING: - LogDebug(avahi_domain, "Client is REGISTERING"); - - /* The server records are now being established. This - * might be caused by a host name change. We need to wait - * for our own records to register until the host name is - * properly esatblished. */ - - if (avahiGroup) { - LogDebug(avahi_domain, "Resetting group"); - avahi_entry_group_reset(avahiGroup); - } - - break; - - case AVAHI_CLIENT_CONNECTING: - LogDebug(avahi_domain, "Client is CONNECTING"); - break; - } -} - -void -AvahiInit(EventLoop &loop, const char *serviceName) -{ - LogDebug(avahi_domain, "Initializing interface"); - - if (!avahi_is_valid_service_name(serviceName)) - FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName); - - avahiName = avahi_strdup(serviceName); - - avahi_running = true; - - avahi_poll = new MyAvahiPoll(loop); - - int error; - avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, nullptr, &error); - - if (!avahiClient) { - FormatError(avahi_domain, "Failed to create client: %s", - avahi_strerror(error)); - AvahiDeinit(); - } -} - -void -AvahiDeinit(void) -{ - LogDebug(avahi_domain, "Shutting down interface"); - - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = nullptr; - } - - if (avahiClient) { - avahi_client_free(avahiClient); - avahiClient = nullptr; - } - - delete avahi_poll; - avahi_poll = nullptr; - - avahi_free(avahiName); - avahiName = nullptr; -} diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx deleted file mode 100644 index bb046350a..000000000 --- a/src/ZeroconfAvahi.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ZEROCONF_AVAHI_HXX -#define MPD_ZEROCONF_AVAHI_HXX - -class EventLoop; - -void -AvahiInit(EventLoop &loop, const char *service_name); - -void -AvahiDeinit(); - -#endif diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx deleted file mode 100644 index 73e84fbc2..000000000 --- a/src/ZeroconfBonjour.cxx +++ /dev/null @@ -1,109 +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 "ZeroconfBonjour.hxx" -#include "ZeroconfInternal.hxx" -#include "Listen.hxx" -#include "event/SocketMonitor.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" -#include "Compiler.h" - -#include <glib.h> - -#include <dns_sd.h> - -static constexpr Domain bonjour_domain("bonjour"); - -class BonjourMonitor final : public SocketMonitor { - DNSServiceRef service_ref; - -public: - BonjourMonitor(EventLoop &_loop, DNSServiceRef _service_ref) - :SocketMonitor(DNSServiceRefSockFD(_service_ref), _loop), - service_ref(_service_ref) { - ScheduleRead(); - } - - ~BonjourMonitor() { - Steal(); - DNSServiceRefDeallocate(service_ref); - } - -protected: - virtual bool OnSocketReady(gcc_unused unsigned flags) override { - DNSServiceProcessResult(service_ref); - return false; - } -}; - -static BonjourMonitor *bonjour_monitor; - -static void -dnsRegisterCallback(gcc_unused DNSServiceRef sdRef, - gcc_unused DNSServiceFlags flags, - DNSServiceErrorType errorCode, const char *name, - gcc_unused const char *regtype, - gcc_unused const char *domain, - gcc_unused void *context) -{ - if (errorCode != kDNSServiceErr_NoError) { - LogError(bonjour_domain, - "Failed to register zeroconf service"); - - bonjour_monitor->Cancel(); - } else { - FormatDebug(bonjour_domain, - "Registered zeroconf service with name '%s'", - name); - } -} - -void -BonjourInit(EventLoop &loop, const char *service_name) -{ - DNSServiceRef dnsReference; - DNSServiceErrorType error = DNSServiceRegister(&dnsReference, - 0, 0, service_name, - SERVICE_TYPE, nullptr, nullptr, - g_htons(listen_port), 0, - nullptr, - dnsRegisterCallback, - nullptr); - - if (error != kDNSServiceErr_NoError) { - LogError(bonjour_domain, - "Failed to register zeroconf service"); - - if (dnsReference) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = nullptr; - } - return; - } - - bonjour_monitor = new BonjourMonitor(loop, dnsReference); -} - -void -BonjourDeinit() -{ - delete bonjour_monitor; -} diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx deleted file mode 100644 index d91fe9a0d..000000000 --- a/src/ZeroconfBonjour.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ZEROCONF_BONJOUR_HXX -#define MPD_ZEROCONF_BONJOUR_HXX - -class EventLoop; - -void -BonjourInit(EventLoop &loop, const char *service_name); - -void -BonjourDeinit(); - -#endif diff --git a/src/ZeroconfGlue.cxx b/src/ZeroconfGlue.cxx deleted file mode 100644 index 7c9c973cc..000000000 --- a/src/ZeroconfGlue.cxx +++ /dev/null @@ -1,83 +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 "ZeroconfGlue.hxx" -#include "ZeroconfAvahi.hxx" -#include "ZeroconfBonjour.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "Listen.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" -#include "Compiler.h" - -static constexpr Domain zeroconf_domain("zeroconf"); - -/* The default service name to publish - * (overridden by 'zeroconf_name' config parameter) - */ -#define SERVICE_NAME "Music Player" - -#define DEFAULT_ZEROCONF_ENABLED 1 - -static int zeroconfEnabled; - -void -ZeroconfInit(gcc_unused EventLoop &loop) -{ - const char *serviceName; - - zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED, - DEFAULT_ZEROCONF_ENABLED); - if (!zeroconfEnabled) - return; - - if (listen_port <= 0) { - LogWarning(zeroconf_domain, - "No global port, disabling zeroconf"); - zeroconfEnabled = false; - return; - } - - serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); - -#ifdef HAVE_AVAHI - AvahiInit(loop, serviceName); -#endif - -#ifdef HAVE_BONJOUR - BonjourInit(loop, serviceName); -#endif -} - -void -ZeroconfDeinit() -{ - if (!zeroconfEnabled) - return; - -#ifdef HAVE_AVAHI - AvahiDeinit(); -#endif /* HAVE_AVAHI */ - -#ifdef HAVE_BONJOUR - BonjourDeinit(); -#endif -} diff --git a/src/ZeroconfGlue.hxx b/src/ZeroconfGlue.hxx deleted file mode 100644 index 2a291ce29..000000000 --- a/src/ZeroconfGlue.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ZEROCONF_GLUE_HXX -#define MPD_ZEROCONF_GLUE_HXX - -#include "check.h" - -class EventLoop; - -#ifdef HAVE_ZEROCONF - -void -ZeroconfInit(EventLoop &loop); - -void -ZeroconfDeinit(); - -#else /* ! HAVE_ZEROCONF */ - -static inline void -ZeroconfInit(EventLoop &) -{} - -static inline void -ZeroconfDeinit() -{} - -#endif /* ! HAVE_ZEROCONF */ - -#endif diff --git a/src/ZeroconfInternal.hxx b/src/ZeroconfInternal.hxx deleted file mode 100644 index 775e2dda2..000000000 --- a/src/ZeroconfInternal.hxx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef ZEROCONF_INTERNAL_H -#define ZEROCONF_INTERNAL_H - -/* The dns-sd service type qualifier to publish */ -#define SERVICE_TYPE "_mpd._tcp" - -#endif diff --git a/src/android/Context.cxx b/src/android/Context.cxx new file mode 100644 index 000000000..f75e1503e --- /dev/null +++ b/src/android/Context.cxx @@ -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. + */ + +#include "config.h" +#include "Context.hxx" +#include "java/Class.hxx" +#include "java/File.hxx" +#include "fs/AllocatedPath.hxx" + +AllocatedPath +Context::GetCacheDir(JNIEnv *env) const +{ + assert(env != nullptr); + + Java::Class cls(env, env->GetObjectClass(Get())); + jmethodID method = env->GetMethodID(cls, "getCacheDir", + "()Ljava/io/File;"); + assert(method); + + jobject file = env->CallObjectMethod(Get(), method); + if (file == nullptr) { + env->ExceptionClear(); + return AllocatedPath::Null(); + } + + return Java::File::ToAbsolutePath(env, file); +} diff --git a/src/android/Context.hxx b/src/android/Context.hxx new file mode 100644 index 000000000..b8a47777d --- /dev/null +++ b/src/android/Context.hxx @@ -0,0 +1,35 @@ +/* + * 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_ANDROID_CONTEXT_HXX +#define MPD_ANDROID_CONTEXT_HXX + +#include "java/Object.hxx" + +class AllocatedPath; + +class Context : public Java::Object { +public: + Context(JNIEnv *env, jobject obj):Java::Object(env, obj) {} + + gcc_pure + AllocatedPath GetCacheDir(JNIEnv *env) const; +}; + +#endif diff --git a/src/android/Environment.cxx b/src/android/Environment.cxx new file mode 100644 index 000000000..9813b0b79 --- /dev/null +++ b/src/android/Environment.cxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Environment.hxx" +#include "java/Class.hxx" +#include "java/String.hxx" +#include "java/File.hxx" +#include "util/StringUtil.hxx" +#include "fs/AllocatedPath.hxx" + +namespace Environment { + static Java::TrivialClass cls; + static jmethodID getExternalStorageDirectory_method; + static jmethodID getExternalStoragePublicDirectory_method; +}; + +void +Environment::Initialise(JNIEnv *env) +{ + cls.Find(env, "android/os/Environment"); + + getExternalStorageDirectory_method = + env->GetStaticMethodID(cls, "getExternalStorageDirectory", + "()Ljava/io/File;"); + + getExternalStoragePublicDirectory_method = + env->GetStaticMethodID(cls, "getExternalStoragePublicDirectory", + "(Ljava/lang/String;)Ljava/io/File;"); +} + +void +Environment::Deinitialise(JNIEnv *env) +{ + cls.Clear(env); +} + +AllocatedPath +Environment::getExternalStorageDirectory() +{ + JNIEnv *env = Java::GetEnv(); + + jobject file = + env->CallStaticObjectMethod(cls, + getExternalStorageDirectory_method); + if (file == nullptr) + return AllocatedPath::Null(); + + return Java::File::ToAbsolutePath(env, file); +} + +AllocatedPath +Environment::getExternalStoragePublicDirectory(const char *type) +{ + if (getExternalStoragePublicDirectory_method == nullptr) + /* needs API level 8 */ + return AllocatedPath::Null(); + + JNIEnv *env = Java::GetEnv(); + + Java::String type2(env, type); + jobject file = env->CallStaticObjectMethod(Environment::cls, + Environment::getExternalStoragePublicDirectory_method, + type2.Get()); + if (file == nullptr) + return AllocatedPath::Null(); + + return Java::File::ToAbsolutePath(env, file); +} diff --git a/src/android/Environment.hxx b/src/android/Environment.hxx new file mode 100644 index 000000000..5a54ea361 --- /dev/null +++ b/src/android/Environment.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_ANDROID_ENVIRONMENT_HXX +#define MPD_ANDROID_ENVIRONMENT_HXX + +#include "Compiler.h" + +#include <jni.h> + +class AllocatedPath; + +namespace Environment { + void Initialise(JNIEnv *env); + void Deinitialise(JNIEnv *env); + + /** + * Determine the mount point of the external SD card. + */ + gcc_pure + AllocatedPath getExternalStorageDirectory(); + + gcc_pure + AllocatedPath getExternalStoragePublicDirectory(const char *type); +}; + +#endif diff --git a/src/archive/ArchiveDomain.cxx b/src/archive/ArchiveDomain.cxx new file mode 100644 index 000000000..4adf4a886 --- /dev/null +++ b/src/archive/ArchiveDomain.cxx @@ -0,0 +1,23 @@ +/* + * 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 "ArchiveDomain.hxx" +#include "util/Domain.hxx" + +const Domain archive_domain("archive"); diff --git a/src/archive/ArchiveDomain.hxx b/src/archive/ArchiveDomain.hxx new file mode 100644 index 000000000..817ae5835 --- /dev/null +++ b/src/archive/ArchiveDomain.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_ARCHIVE_DOMAIN_HXX +#define MPD_ARCHIVE_DOMAIN_HXX + +extern const class Domain archive_domain; + +#endif diff --git a/src/archive/ArchiveFile.hxx b/src/archive/ArchiveFile.hxx new file mode 100644 index 000000000..473eef70b --- /dev/null +++ b/src/archive/ArchiveFile.hxx @@ -0,0 +1,61 @@ +/* + * 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_ARCHIVE_FILE_HXX +#define MPD_ARCHIVE_FILE_HXX + +class Mutex; +class Cond; +class Error; +struct ArchivePlugin; +class ArchiveVisitor; +class InputStream; + +class ArchiveFile { +public: + const ArchivePlugin &plugin; + + ArchiveFile(const ArchivePlugin &_plugin) + :plugin(_plugin) {} + +protected: + /** + * Use Close() instead of delete. + */ + ~ArchiveFile() {} + +public: + virtual void Close() = 0; + + /** + * Visit all entries inside this archive. + */ + virtual void Visit(ArchiveVisitor &visitor) = 0; + + /** + * Opens an InputStream of a file within the archive. + * + * @param path the path within the archive + */ + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) = 0; +}; + +#endif diff --git a/src/archive/ArchiveList.cxx b/src/archive/ArchiveList.cxx new file mode 100644 index 000000000..79c3a16fe --- /dev/null +++ b/src/archive/ArchiveList.cxx @@ -0,0 +1,90 @@ +/* + * 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 "ArchiveList.hxx" +#include "ArchivePlugin.hxx" +#include "util/StringUtil.hxx" +#include "plugins/Bzip2ArchivePlugin.hxx" +#include "plugins/Iso9660ArchivePlugin.hxx" +#include "plugins/ZzipArchivePlugin.hxx" +#include "util/Macros.hxx" + +#include <string.h> + +const ArchivePlugin *const archive_plugins[] = { +#ifdef HAVE_BZ2 + &bz2_archive_plugin, +#endif +#ifdef HAVE_ZZIP + &zzip_archive_plugin, +#endif +#ifdef HAVE_ISO9660 + &iso9660_archive_plugin, +#endif + nullptr +}; + +/** which plugins have been initialized successfully? */ +static bool archive_plugins_enabled[ARRAY_SIZE(archive_plugins) - 1]; + +#define archive_plugins_for_each_enabled(plugin) \ + archive_plugins_for_each(plugin) \ + if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins]) + +const ArchivePlugin * +archive_plugin_from_suffix(const char *suffix) +{ + if (suffix == nullptr) + return nullptr; + + archive_plugins_for_each_enabled(plugin) + if (plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) + return plugin; + + return nullptr; +} + +const ArchivePlugin * +archive_plugin_from_name(const char *name) +{ + archive_plugins_for_each_enabled(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return nullptr; +} + +void archive_plugin_init_all(void) +{ + for (unsigned i = 0; archive_plugins[i] != nullptr; ++i) { + const ArchivePlugin *plugin = archive_plugins[i]; + if (plugin->init == nullptr || archive_plugins[i]->init()) + archive_plugins_enabled[i] = true; + } +} + +void archive_plugin_deinit_all(void) +{ + archive_plugins_for_each_enabled(plugin) + if (plugin->finish != nullptr) + plugin->finish(); +} + diff --git a/src/archive/ArchiveList.hxx b/src/archive/ArchiveList.hxx new file mode 100644 index 000000000..1f1b0ae96 --- /dev/null +++ b/src/archive/ArchiveList.hxx @@ -0,0 +1,47 @@ +/* + * 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_ARCHIVE_LIST_HXX +#define MPD_ARCHIVE_LIST_HXX + +struct ArchivePlugin; + +extern const ArchivePlugin *const archive_plugins[]; + +#define archive_plugins_for_each(plugin) \ + for (const ArchivePlugin *plugin, \ + *const*archive_plugin_iterator = &archive_plugins[0]; \ + (plugin = *archive_plugin_iterator) != nullptr; \ + ++archive_plugin_iterator) + +/* interface for using plugins */ + +const ArchivePlugin * +archive_plugin_from_suffix(const char *suffix); + +const ArchivePlugin * +archive_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void archive_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void archive_plugin_deinit_all(void); + +#endif diff --git a/src/archive/ArchiveLookup.cxx b/src/archive/ArchiveLookup.cxx new file mode 100644 index 000000000..53730c504 --- /dev/null +++ b/src/archive/ArchiveLookup.cxx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "ArchiveLookup.hxx" +#include "ArchiveDomain.hxx" +#include "Log.hxx" + +#include <string.h> +#include <sys/stat.h> +#include <errno.h> + +gcc_pure +static char * +FindSlash(char *p, size_t i) +{ + for (; i > 0; --i) + if (p[i] == '/') + return p + i; + + return nullptr; +} + +gcc_pure +static const char * +FindSuffix(const char *p, const char *i) +{ + for (; i > p; --i) { + if (*i == '.') + return i + 1; + } + + return nullptr; +} + +bool +archive_lookup(char *pathname, const char **archive, + const char **inpath, const char **suffix) +{ + size_t idx = strlen(pathname); + + char *slash = nullptr; + + while (true) { + //try to stat if its real directory + struct stat st_info; + if (stat(pathname, &st_info) == -1) { + if (errno != ENOTDIR) { + FormatErrno(archive_domain, + "Failed to stat %s", pathname); + return false; + } + } else { + //is something found ins original path (is not an archive) + if (slash == nullptr) + return false; + + //its a file ? + if (S_ISREG(st_info.st_mode)) { + //so the upper should be file + *archive = pathname; + *inpath = slash + 1; + + //try to get suffix + *suffix = FindSuffix(pathname, slash - 1); + return true; + } else { + FormatError(archive_domain, + "Not a regular file: %s", + pathname); + return false; + } + } + + //find one dir up + if (slash != nullptr) + *slash = '/'; + + slash = FindSlash(pathname, idx - 1); + if (slash == nullptr) + return false; + + *slash = 0; + idx = slash - pathname; + } +} + diff --git a/src/archive/ArchiveLookup.hxx b/src/archive/ArchiveLookup.hxx new file mode 100644 index 000000000..0c08951a9 --- /dev/null +++ b/src/archive/ArchiveLookup.hxx @@ -0,0 +1,45 @@ +/* + * 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_ARCHIVE_LOOKUP_HXX +#define MPD_ARCHIVE_LOOKUP_HXX + +/** + * + * archive_lookup is used to determine if part of pathname refers to an regular + * file (archive). If so then its also used to split pathname into archive file + * and path used to locate file in archive. It also returns suffix of the file. + * How it works: + * We do stat of the parent of input pathname as long as we find an regular file + * Normally this should never happen. When routine returns true pathname modified + * and split into archive, inpath and suffix. Otherwise nothing happens + * + * For example: + * + * /music/path/Talco.zip/Talco - Combat Circus/12 - A la pachenka.mp3 + * is split into archive: /music/path/Talco.zip + * inarchive pathname: Talco - Combat Circus/12 - A la pachenka.mp3 + * and suffix: zip + */ +bool +archive_lookup(char *pathname, const char **archive, + const char **inpath, const char **suffix); + +#endif + diff --git a/src/archive/ArchivePlugin.cxx b/src/archive/ArchivePlugin.cxx new file mode 100644 index 000000000..67f469e08 --- /dev/null +++ b/src/archive/ArchivePlugin.cxx @@ -0,0 +1,40 @@ +/* + * 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 "ArchivePlugin.hxx" +#include "ArchiveFile.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +ArchiveFile * +archive_file_open(const ArchivePlugin *plugin, Path path, + Error &error) +{ + assert(plugin != nullptr); + assert(plugin->open != nullptr); + assert(!path.IsNull()); + + ArchiveFile *file = plugin->open(path, error); + assert((file == nullptr) == error.IsDefined()); + + return file; +} diff --git a/src/archive/ArchivePlugin.hxx b/src/archive/ArchivePlugin.hxx new file mode 100644 index 000000000..eb24bbdf9 --- /dev/null +++ b/src/archive/ArchivePlugin.hxx @@ -0,0 +1,61 @@ +/* + * 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_ARCHIVE_PLUGIN_HXX +#define MPD_ARCHIVE_PLUGIN_HXX + +class ArchiveFile; +class Path; +class Error; + +struct ArchivePlugin { + const char *name; + + /** + * optional, set this to nullptr if the archive plugin doesn't + * have/need one this must false if there is an error and + * true otherwise + */ + bool (*init)(void); + + /** + * optional, set this to nullptr if the archive plugin doesn't + * have/need one + */ + void (*finish)(void); + + /** + * tryes to open archive file and associates handle with archive + * returns pointer to handle used is all operations with this archive + * or nullptr when opening fails + */ + ArchiveFile *(*open)(Path path_fs, Error &error); + + /** + * suffixes handled by this plugin. + * last element in these arrays must always be a nullptr + */ + const char *const*suffixes; +}; + +ArchiveFile * +archive_file_open(const ArchivePlugin *plugin, Path path, + Error &error); + +#endif diff --git a/src/archive/ArchiveVisitor.hxx b/src/archive/ArchiveVisitor.hxx new file mode 100644 index 000000000..6759695ca --- /dev/null +++ b/src/archive/ArchiveVisitor.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_ARCHIVE_VISITOR_HXX +#define MPD_ARCHIVE_VISITOR_HXX + +class ArchiveVisitor { +public: + virtual void VisitArchiveEntry(const char *path_utf8) = 0; +}; + +#endif diff --git a/src/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx deleted file mode 100644 index d1e6b51af..000000000 --- a/src/archive/Bzip2ArchivePlugin.cxx +++ /dev/null @@ -1,294 +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. - */ - -/** - * single bz2 archive handling (requires libbz2) - */ - -#include "config.h" -#include "Bzip2ArchivePlugin.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "ArchiveVisitor.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/RefCount.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/Traits.hxx" - -#include <bzlib.h> - -#include <stdint.h> -#include <stddef.h> -#include <string.h> - -#ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress -#endif - -class Bzip2ArchiveFile final : public ArchiveFile { -public: - RefCount ref; - - std::string name; - InputStream *const istream; - - Bzip2ArchiveFile(const char *path, InputStream *_is) - :ArchiveFile(bz2_archive_plugin), - name(PathTraits::GetBaseUTF8(path)), - istream(_is) { - // remove .bz2 suffix - const size_t len = name.length(); - if (len > 4) - name.erase(len - 4); - } - - ~Bzip2ArchiveFile() { - istream->Close(); - } - - void Ref() { - ref.Increment(); - } - - void Unref() { - if (!ref.Decrement()) - return; - - delete this; - } - - virtual void Close() override { - Unref(); - } - - virtual void Visit(ArchiveVisitor &visitor) override { - visitor.VisitArchiveEntry(name.c_str()); - } - - virtual InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) override; -}; - -struct Bzip2InputStream { - InputStream base; - - Bzip2ArchiveFile *archive; - - bool eof; - - bz_stream bzstream; - - char buffer[5000]; - - Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri, - Mutex &mutex, Cond &cond); - ~Bzip2InputStream(); - - bool Open(Error &error); - void Close(); -}; - -extern const InputPlugin bz2_inputplugin; - -static constexpr Domain bz2_domain("bz2"); - -/* single archive handling allocation helpers */ - -inline bool -Bzip2InputStream::Open(Error &error) -{ - bzstream.bzalloc = nullptr; - bzstream.bzfree = nullptr; - bzstream.opaque = nullptr; - - bzstream.next_in = (char *)buffer; - bzstream.avail_in = 0; - - int ret = BZ2_bzDecompressInit(&bzstream, 0, 0); - if (ret != BZ_OK) { - error.Set(bz2_domain, ret, - "BZ2_bzDecompressInit() has failed"); - return false; - } - - base.ready = true; - return true; -} - -inline void -Bzip2InputStream::Close() -{ - BZ2_bzDecompressEnd(&bzstream); -} - -/* archive open && listing routine */ - -static ArchiveFile * -bz2_open(const char *pathname, Error &error) -{ - static Mutex mutex; - static Cond cond; - InputStream *is = InputStream::Open(pathname, mutex, cond, error); - if (is == nullptr) - return nullptr; - - return new Bzip2ArchiveFile(pathname, is); -} - -/* single archive handling */ - -Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context, const char *uri, - Mutex &mutex, Cond &cond) - :base(bz2_inputplugin, uri, mutex, cond), - archive(&_context), eof(false) -{ - archive->Ref(); -} - -Bzip2InputStream::~Bzip2InputStream() -{ - archive->Unref(); -} - -InputStream * -Bzip2ArchiveFile::OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) -{ - Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond); - if (!bis->Open(error)) { - delete bis; - return nullptr; - } - - return &bis->base; -} - -static void -bz2_is_close(InputStream *is) -{ - Bzip2InputStream *bis = (Bzip2InputStream *)is; - - bis->Close(); - delete bis; -} - -static bool -bz2_fillbuffer(Bzip2InputStream *bis, Error &error) -{ - size_t count; - bz_stream *bzstream; - - bzstream = &bis->bzstream; - - if (bzstream->avail_in > 0) - return true; - - count = bis->archive->istream->Read(bis->buffer, sizeof(bis->buffer), - error); - if (count == 0) - return false; - - bzstream->next_in = bis->buffer; - bzstream->avail_in = count; - return true; -} - -static size_t -bz2_is_read(InputStream *is, void *ptr, size_t length, - Error &error) -{ - Bzip2InputStream *bis = (Bzip2InputStream *)is; - bz_stream *bzstream; - int bz_result; - size_t nbytes = 0; - - if (bis->eof) - return 0; - - bzstream = &bis->bzstream; - bzstream->next_out = (char *)ptr; - bzstream->avail_out = length; - - do { - if (!bz2_fillbuffer(bis, error)) - return 0; - - bz_result = BZ2_bzDecompress(bzstream); - - if (bz_result == BZ_STREAM_END) { - bis->eof = true; - break; - } - - if (bz_result != BZ_OK) { - error.Set(bz2_domain, bz_result, - "BZ2_bzDecompress() has failed"); - return 0; - } - } while (bzstream->avail_out == length); - - nbytes = length - bzstream->avail_out; - is->offset += nbytes; - - return nbytes; -} - -static bool -bz2_is_eof(InputStream *is) -{ - Bzip2InputStream *bis = (Bzip2InputStream *)is; - - return bis->eof; -} - -/* exported structures */ - -static const char *const bz2_extensions[] = { - "bz2", - nullptr -}; - -const InputPlugin bz2_inputplugin = { - nullptr, - nullptr, - nullptr, - nullptr, - bz2_is_close, - nullptr, - nullptr, - nullptr, - nullptr, - bz2_is_read, - bz2_is_eof, - nullptr, -}; - -const struct archive_plugin bz2_archive_plugin = { - "bz2", - nullptr, - nullptr, - bz2_open, - bz2_extensions, -}; - diff --git a/src/archive/Bzip2ArchivePlugin.hxx b/src/archive/Bzip2ArchivePlugin.hxx deleted file mode 100644 index a7933a7a7..000000000 --- a/src/archive/Bzip2ArchivePlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_BZ2_HXX -#define MPD_ARCHIVE_BZ2_HXX - -extern const struct archive_plugin bz2_archive_plugin; - -#endif diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx deleted file mode 100644 index ad21d4a3d..000000000 --- a/src/archive/Iso9660ArchivePlugin.cxx +++ /dev/null @@ -1,260 +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. - */ - -/** - * iso archive handling (requires cdio, and iso9660) - */ - -#include "config.h" -#include "Iso9660ArchivePlugin.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "ArchiveVisitor.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/RefCount.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <cdio/cdio.h> -#include <cdio/iso9660.h> - -#include <stdlib.h> -#include <string.h> - -#define CEILING(x, y) ((x+(y-1))/y) - -class Iso9660ArchiveFile final : public ArchiveFile { -public: - RefCount ref; - - iso9660_t *iso; - - Iso9660ArchiveFile(iso9660_t *_iso) - :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} - - ~Iso9660ArchiveFile() { - iso9660_close(iso); - } - - void Unref() { - if (ref.Decrement()) - delete this; - } - - void Visit(const char *path, ArchiveVisitor &visitor); - - virtual void Close() override { - Unref(); - } - - virtual void Visit(ArchiveVisitor &visitor) override; - - virtual InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) override; -}; - -extern const InputPlugin iso9660_input_plugin; - -static constexpr Domain iso9660_domain("iso9660"); - -/* archive open && listing routine */ - -inline void -Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor) -{ - CdioList_t *entlist; - CdioListNode_t *entnode; - iso9660_stat_t *statbuf; - char pathname[4096]; - - entlist = iso9660_ifs_readdir (iso, psz_path); - if (!entlist) { - return; - } - /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ - _CDIO_LIST_FOREACH (entnode, entlist) { - statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); - - strcpy(pathname, psz_path); - strcat(pathname, statbuf->filename); - - if (iso9660_stat_s::_STAT_DIR == statbuf->type ) { - if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { - strcat(pathname, "/"); - Visit(pathname, visitor); - } - } else { - //remove leading / - visitor.VisitArchiveEntry(pathname + 1); - } - } - _cdio_list_free (entlist, true); -} - -static ArchiveFile * -iso9660_archive_open(const char *pathname, Error &error) -{ - /* open archive */ - auto iso = iso9660_open(pathname); - if (iso == nullptr) { - error.Format(iso9660_domain, - "Failed to open ISO9660 file %s", pathname); - return nullptr; - } - - return new Iso9660ArchiveFile(iso); -} - -void -Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) -{ - Visit("/", visitor); -} - -/* single archive handling */ - -struct Iso9660InputStream { - InputStream base; - - Iso9660ArchiveFile *archive; - - iso9660_stat_t *statbuf; - size_t max_blocks; - - Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *uri, - Mutex &mutex, Cond &cond, - iso9660_stat_t *_statbuf) - :base(iso9660_input_plugin, uri, mutex, cond), - archive(&_archive), statbuf(_statbuf), - max_blocks(CEILING(statbuf->size, ISO_BLOCKSIZE)) { - - base.ready = true; - base.size = statbuf->size; - - archive->ref.Increment(); - } - - ~Iso9660InputStream() { - free(statbuf); - archive->Unref(); - } -}; - -InputStream * -Iso9660ArchiveFile::OpenStream(const char *pathname, - Mutex &mutex, Cond &cond, - Error &error) -{ - auto statbuf = iso9660_ifs_stat_translate(iso, pathname); - if (statbuf == nullptr) { - error.Format(iso9660_domain, - "not found in the ISO file: %s", pathname); - return nullptr; - } - - Iso9660InputStream *iis = - new Iso9660InputStream(*this, pathname, mutex, cond, - statbuf); - return &iis->base; -} - -static void -iso9660_input_close(InputStream *is) -{ - Iso9660InputStream *iis = (Iso9660InputStream *)is; - - delete iis; -} - - -static size_t -iso9660_input_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - Iso9660InputStream *iis = (Iso9660InputStream *)is; - int readed = 0; - int no_blocks, cur_block; - size_t left_bytes = iis->statbuf->size - is->offset; - - size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; - - if (left_bytes < size) { - no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); - } else { - no_blocks = size / ISO_BLOCKSIZE; - } - if (no_blocks > 0) { - - cur_block = is->offset / ISO_BLOCKSIZE; - - readed = iso9660_iso_seek_read (iis->archive->iso, ptr, - iis->statbuf->lsn + cur_block, no_blocks); - - if (readed != no_blocks * ISO_BLOCKSIZE) { - error.Format(iso9660_domain, - "error reading ISO file at lsn %lu", - (unsigned long)cur_block); - return 0; - } - if (left_bytes < size) { - readed = left_bytes; - } - - is->offset += readed; - } - return readed; -} - -static bool -iso9660_input_eof(InputStream *is) -{ - return is->offset == is->size; -} - -/* exported structures */ - -static const char *const iso9660_archive_extensions[] = { - "iso", - nullptr -}; - -const InputPlugin iso9660_input_plugin = { - nullptr, - nullptr, - nullptr, - nullptr, - iso9660_input_close, - nullptr, - nullptr, - nullptr, - nullptr, - iso9660_input_read, - iso9660_input_eof, - nullptr, -}; - -const struct archive_plugin iso9660_archive_plugin = { - "iso", - nullptr, - nullptr, - iso9660_archive_open, - iso9660_archive_extensions, -}; diff --git a/src/archive/Iso9660ArchivePlugin.hxx b/src/archive/Iso9660ArchivePlugin.hxx deleted file mode 100644 index 6fbab6159..000000000 --- a/src/archive/Iso9660ArchivePlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_ISO9660_HXX -#define MPD_ARCHIVE_ISO9660_HXX - -extern const struct archive_plugin iso9660_archive_plugin; - -#endif diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx deleted file mode 100644 index 973fe91dc..000000000 --- a/src/archive/ZzipArchivePlugin.cxx +++ /dev/null @@ -1,225 +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. - */ - -/** - * zip archive handling (requires zziplib) - */ - -#include "config.h" -#include "ZzipArchivePlugin.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "ArchiveVisitor.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/RefCount.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <zzip/zzip.h> - -#include <string.h> - -class ZzipArchiveFile final : public ArchiveFile { -public: - RefCount ref; - - ZZIP_DIR *const dir; - - ZzipArchiveFile(ZZIP_DIR *_dir) - :ArchiveFile(zzip_archive_plugin), dir(_dir) {} - - ~ZzipArchiveFile() { - zzip_dir_close(dir); - } - - void Unref() { - if (ref.Decrement()) - delete this; - } - - virtual void Close() override { - Unref(); - } - - virtual void Visit(ArchiveVisitor &visitor) override; - - virtual InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) override; -}; - -extern const InputPlugin zzip_input_plugin; - -static constexpr Domain zzip_domain("zzip"); - -/* archive open && listing routine */ - -static ArchiveFile * -zzip_archive_open(const char *pathname, Error &error) -{ - ZZIP_DIR *dir = zzip_dir_open(pathname, nullptr); - if (dir == nullptr) { - error.Format(zzip_domain, "Failed to open ZIP file %s", - pathname); - return nullptr; - } - - return new ZzipArchiveFile(dir); -} - -inline void -ZzipArchiveFile::Visit(ArchiveVisitor &visitor) -{ - zzip_rewinddir(dir); - - ZZIP_DIRENT dirent; - while (zzip_dir_read(dir, &dirent)) - //add only files - if (dirent.st_size > 0) - visitor.VisitArchiveEntry(dirent.d_name); -} - -/* single archive handling */ - -struct ZzipInputStream { - InputStream base; - - ZzipArchiveFile *archive; - - ZZIP_FILE *file; - - ZzipInputStream(ZzipArchiveFile &_archive, const char *uri, - Mutex &mutex, Cond &cond, - ZZIP_FILE *_file) - :base(zzip_input_plugin, uri, mutex, cond), - archive(&_archive), file(_file) { - base.ready = true; - //we are seekable (but its not recommendent to do so) - base.seekable = true; - - ZZIP_STAT z_stat; - zzip_file_stat(file, &z_stat); - base.size = z_stat.st_size; - - archive->ref.Increment(); - } - - ~ZzipInputStream() { - zzip_file_close(file); - archive->Unref(); - } -}; - -InputStream * -ZzipArchiveFile::OpenStream(const char *pathname, - Mutex &mutex, Cond &cond, - Error &error) -{ - ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0); - if (_file == nullptr) { - error.Format(zzip_domain, "not found in the ZIP file: %s", - pathname); - return nullptr; - } - - ZzipInputStream *zis = - new ZzipInputStream(*this, pathname, - mutex, cond, - _file); - return &zis->base; -} - -static void -zzip_input_close(InputStream *is) -{ - ZzipInputStream *zis = (ZzipInputStream *)is; - - delete zis; -} - -static size_t -zzip_input_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - ZzipInputStream *zis = (ZzipInputStream *)is; - int ret; - - ret = zzip_file_read(zis->file, ptr, size); - if (ret < 0) { - error.Set(zzip_domain, "zzip_file_read() has failed"); - return 0; - } - - is->offset = zzip_tell(zis->file); - - return ret; -} - -static bool -zzip_input_eof(InputStream *is) -{ - ZzipInputStream *zis = (ZzipInputStream *)is; - - return (InputPlugin::offset_type)zzip_tell(zis->file) == is->size; -} - -static bool -zzip_input_seek(InputStream *is, InputPlugin::offset_type offset, - int whence, Error &error) -{ - ZzipInputStream *zis = (ZzipInputStream *)is; - zzip_off_t ofs = zzip_seek(zis->file, offset, whence); - if (ofs != -1) { - error.Set(zzip_domain, "zzip_seek() has failed"); - is->offset = ofs; - return true; - } - return false; -} - -/* exported structures */ - -static const char *const zzip_archive_extensions[] = { - "zip", - nullptr -}; - -const InputPlugin zzip_input_plugin = { - nullptr, - nullptr, - nullptr, - nullptr, - zzip_input_close, - nullptr, - nullptr, - nullptr, - nullptr, - zzip_input_read, - zzip_input_eof, - zzip_input_seek, -}; - -const struct archive_plugin zzip_archive_plugin = { - "zzip", - nullptr, - nullptr, - zzip_archive_open, - zzip_archive_extensions, -}; diff --git a/src/archive/ZzipArchivePlugin.hxx b/src/archive/ZzipArchivePlugin.hxx deleted file mode 100644 index 4ba16849b..000000000 --- a/src/archive/ZzipArchivePlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ARCHIVE_ZZIP_HXX -#define MPD_ARCHIVE_ZZIP_HXX - -extern const struct archive_plugin zzip_archive_plugin; - -#endif diff --git a/src/archive/plugins/Bzip2ArchivePlugin.cxx b/src/archive/plugins/Bzip2ArchivePlugin.cxx new file mode 100644 index 000000000..14a68fdb5 --- /dev/null +++ b/src/archive/plugins/Bzip2ArchivePlugin.cxx @@ -0,0 +1,259 @@ +/* + * 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. + */ + +/** + * single bz2 archive handling (requires libbz2) + */ + +#include "config.h" +#include "Bzip2ArchivePlugin.hxx" +#include "../ArchivePlugin.hxx" +#include "../ArchiveFile.hxx" +#include "../ArchiveVisitor.hxx" +#include "input/InputStream.hxx" +#include "input/InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Traits.hxx" +#include "fs/Path.hxx" + +#include <bzlib.h> + +#include <stddef.h> + +#ifdef HAVE_OLDER_BZIP2 +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress +#endif + +class Bzip2ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + std::string name; + InputStream *const istream; + + Bzip2ArchiveFile(Path path, InputStream *_is) + :ArchiveFile(bz2_archive_plugin), + name(PathTraitsFS::GetBase(path.c_str())), + istream(_is) { + // remove .bz2 suffix + const size_t len = name.length(); + if (len > 4) + name.erase(len - 4); + } + + ~Bzip2ArchiveFile() { + delete istream; + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (!ref.Decrement()) + return; + + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override { + visitor.VisitArchiveEntry(name.c_str()); + } + + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +struct Bzip2InputStream final : public InputStream { + Bzip2ArchiveFile *archive; + + bool eof; + + bz_stream bzstream; + + char buffer[5000]; + + Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri, + Mutex &mutex, Cond &cond); + ~Bzip2InputStream(); + + bool Open(Error &error); + + /* virtual methods from InputStream */ + bool IsEOF() override; + size_t Read(void *ptr, size_t size, Error &error) override; +}; + +static constexpr Domain bz2_domain("bz2"); + +/* single archive handling allocation helpers */ + +inline bool +Bzip2InputStream::Open(Error &error) +{ + bzstream.bzalloc = nullptr; + bzstream.bzfree = nullptr; + bzstream.opaque = nullptr; + + bzstream.next_in = (char *)buffer; + bzstream.avail_in = 0; + + int ret = BZ2_bzDecompressInit(&bzstream, 0, 0); + if (ret != BZ_OK) { + error.Set(bz2_domain, ret, + "BZ2_bzDecompressInit() has failed"); + return false; + } + + SetReady(); + return true; +} + +/* archive open && listing routine */ + +static ArchiveFile * +bz2_open(Path pathname, Error &error) +{ + static Mutex mutex; + static Cond cond; + InputStream *is = InputStream::OpenReady(pathname.c_str(), mutex, cond, + error); + if (is == nullptr) + return nullptr; + + return new Bzip2ArchiveFile(pathname, is); +} + +/* single archive handling */ + +Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context, + const char *_uri, + Mutex &_mutex, Cond &_cond) + :InputStream(_uri, _mutex, _cond), + archive(&_context), eof(false) +{ + archive->Ref(); +} + +Bzip2InputStream::~Bzip2InputStream() +{ + BZ2_bzDecompressEnd(&bzstream); + archive->Unref(); +} + +InputStream * +Bzip2ArchiveFile::OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) +{ + Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond); + if (!bis->Open(error)) { + delete bis; + return nullptr; + } + + return bis; +} + +static bool +bz2_fillbuffer(Bzip2InputStream *bis, Error &error) +{ + size_t count; + bz_stream *bzstream; + + bzstream = &bis->bzstream; + + if (bzstream->avail_in > 0) + return true; + + count = bis->archive->istream->Read(bis->buffer, sizeof(bis->buffer), + error); + if (count == 0) + return false; + + bzstream->next_in = bis->buffer; + bzstream->avail_in = count; + return true; +} + +size_t +Bzip2InputStream::Read(void *ptr, size_t length, Error &error) +{ + int bz_result; + size_t nbytes = 0; + + if (eof) + return 0; + + bzstream.next_out = (char *)ptr; + bzstream.avail_out = length; + + do { + if (!bz2_fillbuffer(this, error)) + return 0; + + bz_result = BZ2_bzDecompress(&bzstream); + + if (bz_result == BZ_STREAM_END) { + eof = true; + break; + } + + if (bz_result != BZ_OK) { + error.Set(bz2_domain, bz_result, + "BZ2_bzDecompress() has failed"); + return 0; + } + } while (bzstream.avail_out == length); + + nbytes = length - bzstream.avail_out; + offset += nbytes; + + return nbytes; +} + +bool +Bzip2InputStream::IsEOF() +{ + return eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + nullptr +}; + +const ArchivePlugin bz2_archive_plugin = { + "bz2", + nullptr, + nullptr, + bz2_open, + bz2_extensions, +}; + diff --git a/src/archive/plugins/Bzip2ArchivePlugin.hxx b/src/archive/plugins/Bzip2ArchivePlugin.hxx new file mode 100644 index 000000000..1a0a578d1 --- /dev/null +++ b/src/archive/plugins/Bzip2ArchivePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_BZ2_HXX +#define MPD_ARCHIVE_BZ2_HXX + +struct ArchivePlugin; + +extern const ArchivePlugin bz2_archive_plugin; + +#endif diff --git a/src/archive/plugins/Iso9660ArchivePlugin.cxx b/src/archive/plugins/Iso9660ArchivePlugin.cxx new file mode 100644 index 000000000..ba415d3c5 --- /dev/null +++ b/src/archive/plugins/Iso9660ArchivePlugin.cxx @@ -0,0 +1,238 @@ +/* + * 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. + */ + +/** + * iso archive handling (requires cdio, and iso9660) + */ + +#include "config.h" +#include "Iso9660ArchivePlugin.hxx" +#include "../ArchivePlugin.hxx" +#include "../ArchiveFile.hxx" +#include "../ArchiveVisitor.hxx" +#include "input/InputStream.hxx" +#include "input/InputPlugin.hxx" +#include "fs/Path.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <cdio/iso9660.h> + +#include <stdlib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +class Iso9660ArchiveFile final : public ArchiveFile { + RefCount ref; + + iso9660_t *iso; + +public: + Iso9660ArchiveFile(iso9660_t *_iso) + :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} + + ~Iso9660ArchiveFile() { + iso9660_close(iso); + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + long SeekRead(void *ptr, lsn_t start, long int i_size) const { + return iso9660_iso_seek_read(iso, ptr, start, i_size); + } + + void Visit(const char *path, ArchiveVisitor &visitor); + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +static constexpr Domain iso9660_domain("iso9660"); + +/* archive open && listing routine */ + +inline void +Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor) +{ + CdioList_t *entlist; + CdioListNode_t *entnode; + iso9660_stat_t *statbuf; + char pathname[4096]; + + entlist = iso9660_ifs_readdir (iso, psz_path); + if (!entlist) { + return; + } + /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ + _CDIO_LIST_FOREACH (entnode, entlist) { + statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); + + strcpy(pathname, psz_path); + strcat(pathname, statbuf->filename); + + if (iso9660_stat_s::_STAT_DIR == statbuf->type ) { + if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { + strcat(pathname, "/"); + Visit(pathname, visitor); + } + } else { + //remove leading / + visitor.VisitArchiveEntry(pathname + 1); + } + } + _cdio_list_free (entlist, true); +} + +static ArchiveFile * +iso9660_archive_open(Path pathname, Error &error) +{ + /* open archive */ + auto iso = iso9660_open(pathname.c_str()); + if (iso == nullptr) { + error.Format(iso9660_domain, + "Failed to open ISO9660 file %s", + pathname.c_str()); + return nullptr; + } + + return new Iso9660ArchiveFile(iso); +} + +void +Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) +{ + Visit("/", visitor); +} + +/* single archive handling */ + +class Iso9660InputStream final : public InputStream { + Iso9660ArchiveFile &archive; + + iso9660_stat_t *statbuf; + +public: + Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *_uri, + Mutex &_mutex, Cond &_cond, + iso9660_stat_t *_statbuf) + :InputStream(_uri, _mutex, _cond), + archive(_archive), statbuf(_statbuf) { + size = statbuf->size; + SetReady(); + + archive.Ref(); + } + + ~Iso9660InputStream() { + free(statbuf); + archive.Unref(); + } + + /* virtual methods from InputStream */ + bool IsEOF() override; + size_t Read(void *ptr, size_t size, Error &error) override; +}; + +InputStream * +Iso9660ArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + auto statbuf = iso9660_ifs_stat_translate(iso, pathname); + if (statbuf == nullptr) { + error.Format(iso9660_domain, + "not found in the ISO file: %s", pathname); + return nullptr; + } + + return new Iso9660InputStream(*this, pathname, mutex, cond, + statbuf); +} + +size_t +Iso9660InputStream::Read(void *ptr, size_t read_size, Error &error) +{ + int readed = 0; + int no_blocks, cur_block; + size_t left_bytes = statbuf->size - offset; + + if (left_bytes < read_size) { + no_blocks = CEILING(left_bytes, ISO_BLOCKSIZE); + } else { + no_blocks = read_size / ISO_BLOCKSIZE; + } + + if (no_blocks == 0) + return 0; + + cur_block = offset / ISO_BLOCKSIZE; + + readed = archive.SeekRead(ptr, statbuf->lsn + cur_block, + no_blocks); + + if (readed != no_blocks * ISO_BLOCKSIZE) { + error.Format(iso9660_domain, + "error reading ISO file at lsn %lu", + (unsigned long)cur_block); + return 0; + } + if (left_bytes < read_size) { + readed = left_bytes; + } + + offset += readed; + return readed; +} + +bool +Iso9660InputStream::IsEOF() +{ + return offset == size; +} + +/* exported structures */ + +static const char *const iso9660_archive_extensions[] = { + "iso", + nullptr +}; + +const ArchivePlugin iso9660_archive_plugin = { + "iso", + nullptr, + nullptr, + iso9660_archive_open, + iso9660_archive_extensions, +}; diff --git a/src/archive/plugins/Iso9660ArchivePlugin.hxx b/src/archive/plugins/Iso9660ArchivePlugin.hxx new file mode 100644 index 000000000..9335e83b3 --- /dev/null +++ b/src/archive/plugins/Iso9660ArchivePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ISO9660_HXX +#define MPD_ARCHIVE_ISO9660_HXX + +struct ArchivePlugin; + +extern const ArchivePlugin iso9660_archive_plugin; + +#endif diff --git a/src/archive/plugins/ZzipArchivePlugin.cxx b/src/archive/plugins/ZzipArchivePlugin.cxx new file mode 100644 index 000000000..396055c71 --- /dev/null +++ b/src/archive/plugins/ZzipArchivePlugin.cxx @@ -0,0 +1,192 @@ +/* + * 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. + */ + +/** + * zip archive handling (requires zziplib) + */ + +#include "config.h" +#include "ZzipArchivePlugin.hxx" +#include "../ArchivePlugin.hxx" +#include "../ArchiveFile.hxx" +#include "../ArchiveVisitor.hxx" +#include "input/InputStream.hxx" +#include "input/InputPlugin.hxx" +#include "fs/Path.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <zzip/zzip.h> + +class ZzipArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + ZZIP_DIR *const dir; + + ZzipArchiveFile(ZZIP_DIR *_dir) + :ArchiveFile(zzip_archive_plugin), dir(_dir) {} + + ~ZzipArchiveFile() { + zzip_dir_close(dir); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +static constexpr Domain zzip_domain("zzip"); + +/* archive open && listing routine */ + +static ArchiveFile * +zzip_archive_open(Path pathname, Error &error) +{ + ZZIP_DIR *dir = zzip_dir_open(pathname.c_str(), nullptr); + if (dir == nullptr) { + error.Format(zzip_domain, "Failed to open ZIP file %s", + pathname.c_str()); + return nullptr; + } + + return new ZzipArchiveFile(dir); +} + +inline void +ZzipArchiveFile::Visit(ArchiveVisitor &visitor) +{ + zzip_rewinddir(dir); + + ZZIP_DIRENT dirent; + while (zzip_dir_read(dir, &dirent)) + //add only files + if (dirent.st_size > 0) + visitor.VisitArchiveEntry(dirent.d_name); +} + +/* single archive handling */ + +struct ZzipInputStream final : public InputStream { + ZzipArchiveFile *archive; + + ZZIP_FILE *file; + + ZzipInputStream(ZzipArchiveFile &_archive, const char *_uri, + Mutex &_mutex, Cond &_cond, + ZZIP_FILE *_file) + :InputStream(_uri, _mutex, _cond), + archive(&_archive), file(_file) { + //we are seekable (but its not recommendent to do so) + seekable = true; + + ZZIP_STAT z_stat; + zzip_file_stat(file, &z_stat); + size = z_stat.st_size; + + SetReady(); + + archive->ref.Increment(); + } + + ~ZzipInputStream() { + zzip_file_close(file); + archive->Unref(); + } + + /* virtual methods from InputStream */ + bool IsEOF() override; + size_t Read(void *ptr, size_t size, Error &error) override; + bool Seek(offset_type offset, Error &error) override; +}; + +InputStream * +ZzipArchiveFile::OpenStream(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0); + if (_file == nullptr) { + error.Format(zzip_domain, "not found in the ZIP file: %s", + pathname); + return nullptr; + } + + return new ZzipInputStream(*this, pathname, + mutex, cond, + _file); +} + +size_t +ZzipInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + int ret = zzip_file_read(file, ptr, read_size); + if (ret < 0) { + error.Set(zzip_domain, "zzip_file_read() has failed"); + return 0; + } + + offset = zzip_tell(file); + return ret; +} + +bool +ZzipInputStream::IsEOF() +{ + return (InputPlugin::offset_type)zzip_tell(file) == size; +} + +bool +ZzipInputStream::Seek(offset_type new_offset, Error &error) +{ + zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET); + if (ofs != -1) { + error.Set(zzip_domain, "zzip_seek() has failed"); + offset = ofs; + return true; + } + return false; +} + +/* exported structures */ + +static const char *const zzip_archive_extensions[] = { + "zip", + nullptr +}; + +const ArchivePlugin zzip_archive_plugin = { + "zzip", + nullptr, + nullptr, + zzip_archive_open, + zzip_archive_extensions, +}; diff --git a/src/archive/plugins/ZzipArchivePlugin.hxx b/src/archive/plugins/ZzipArchivePlugin.hxx new file mode 100644 index 000000000..cc92b7c52 --- /dev/null +++ b/src/archive/plugins/ZzipArchivePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ZZIP_HXX +#define MPD_ARCHIVE_ZZIP_HXX + +struct ArchivePlugin; + +extern const ArchivePlugin zzip_archive_plugin; + +#endif diff --git a/src/check.h b/src/check.h index 0642a4b91..efb4556d1 100644 --- a/src/check.h +++ b/src/check.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 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/client/Client.cxx b/src/client/Client.cxx new file mode 100644 index 000000000..01ead4645 --- /dev/null +++ b/src/client/Client.cxx @@ -0,0 +1,42 @@ +/* + * 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 "ClientInternal.hxx" +#include "util/Domain.hxx" +#include "Partition.hxx" +#include "Instance.hxx" + +const Domain client_domain("client"); + +#ifdef ENABLE_DATABASE + +const Database * +Client::GetDatabase(Error &error) const +{ + return partition.instance.GetDatabase(error); +} + +const Storage * +Client::GetStorage() const +{ + return partition.instance.storage; +} + +#endif diff --git a/src/client/Client.hxx b/src/client/Client.hxx new file mode 100644 index 000000000..849a11ed4 --- /dev/null +++ b/src/client/Client.hxx @@ -0,0 +1,226 @@ +/* + * 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_CLIENT_H +#define MPD_CLIENT_H + +#include "check.h" +#include "ClientMessage.hxx" +#include "command/CommandListBuilder.hxx" +#include "event/FullyBufferedSocket.hxx" +#include "event/TimeoutMonitor.hxx" +#include "Compiler.h" + +#include <boost/intrusive/list.hpp> + +#include <set> +#include <string> +#include <list> + +#include <stddef.h> +#include <stdarg.h> + +struct sockaddr; +class EventLoop; +class Path; +struct Partition; +class Database; +class Storage; + +class Client final + : FullyBufferedSocket, TimeoutMonitor, + public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> { +public: + Partition &partition; + struct playlist &playlist; + struct PlayerControl &player_control; + + struct Disposer { + void operator()(Client *client) const { + delete client; + } + }; + + unsigned permission; + + /** the uid of the client process, or -1 if unknown */ + int uid; + + CommandListBuilder cmd_list; + + unsigned int num; /* client number */ + + /** is this client waiting for an "idle" response? */ + bool idle_waiting; + + /** idle flags pending on this client, to be sent as soon as + the client enters "idle" */ + unsigned idle_flags; + + /** idle flags that the client wants to receive */ + unsigned idle_subscriptions; + + /** + * A list of channel names this client is subscribed to. + */ + std::set<std::string> subscriptions; + + /** + * The number of subscriptions in #subscriptions. Used to + * limit the number of subscriptions. + */ + unsigned num_subscriptions; + + /** + * A list of messages this client has received. + */ + std::list<ClientMessage> messages; + + Client(EventLoop &loop, Partition &partition, + int fd, int uid, int num); + + ~Client() { + if (FullyBufferedSocket::IsDefined()) + FullyBufferedSocket::Close(); + } + + bool IsConnected() const { + return FullyBufferedSocket::IsDefined(); + } + + gcc_pure + bool IsExpired() const { + return !FullyBufferedSocket::IsDefined(); + } + + void Close(); + void SetExpired(); + + using FullyBufferedSocket::Write; + + /** + * returns the uid of the client process, or a negative value + * if the uid is unknown + */ + int GetUID() const { + return uid; + } + + /** + * Is this client running on the same machine, connected with + * a local (UNIX domain) socket? + */ + bool IsLocal() const { + return uid > 0; + } + + unsigned GetPermission() const { + return permission; + } + + void SetPermission(unsigned _permission) { + permission = _permission; + } + + /** + * Send "idle" response to this client. + */ + void IdleNotify(); + void IdleAdd(unsigned flags); + bool IdleWait(unsigned flags); + + enum class SubscribeResult { + /** success */ + OK, + + /** invalid channel name */ + INVALID, + + /** already subscribed to this channel */ + ALREADY, + + /** too many subscriptions */ + FULL, + }; + + gcc_pure + bool IsSubscribed(const char *channel_name) const { + return subscriptions.find(channel_name) != subscriptions.end(); + } + + SubscribeResult Subscribe(const char *channel); + bool Unsubscribe(const char *channel); + void UnsubscribeAll(); + bool PushMessage(const ClientMessage &msg); + + /** + * Is this client allowed to use the specified local file? + * + * Note that this function is vulnerable to timing/symlink attacks. + * We cannot fix this as long as there are plugins that open a file by + * its name, and not by file descriptor / callbacks. + * + * @param path_fs the absolute path name in filesystem encoding + * @return true if access is allowed + */ + bool AllowFile(Path path_fs, Error &error) const; + + /** + * Wrapper for Instance::GetDatabase(). + */ + gcc_pure + const Database *GetDatabase(Error &error) const; + + gcc_pure + const Storage *GetStorage() const; + +private: + /* virtual methods from class BufferedSocket */ + virtual InputResult OnSocketInput(void *data, size_t length) override; + virtual void OnSocketError(Error &&error) override; + virtual void OnSocketClosed() override; + + /* virtual methods from class TimeoutMonitor */ + virtual void OnTimeout() override; +}; + +void client_manager_init(void); + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const sockaddr *sa, size_t sa_length, int uid); + +/** + * Write a C string to the client. + */ +void client_puts(Client &client, const char *s); + +/** + * Write a printf-like formatted string to the client. + */ +void client_vprintf(Client &client, const char *fmt, va_list args); + +/** + * Write a printf-like formatted string to the client. + */ +gcc_printf(2,3) +void +client_printf(Client &client, const char *fmt, ...); + +#endif diff --git a/src/client/ClientEvent.cxx b/src/client/ClientEvent.cxx new file mode 100644 index 000000000..fd9f24b0d --- /dev/null +++ b/src/client/ClientEvent.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +void +Client::OnSocketError(Error &&error) +{ + FormatError(error, "error on client %d", num); + + SetExpired(); +} + +void +Client::OnSocketClosed() +{ + SetExpired(); +} diff --git a/src/client/ClientExpire.cxx b/src/client/ClientExpire.cxx new file mode 100644 index 000000000..5891756b6 --- /dev/null +++ b/src/client/ClientExpire.cxx @@ -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. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "Log.hxx" + +void +Client::SetExpired() +{ + if (IsExpired()) + return; + + FullyBufferedSocket::Close(); + TimeoutMonitor::Schedule(0); +} + +void +Client::OnTimeout() +{ + if (!IsExpired()) { + assert(!idle_waiting); + FormatDebug(client_domain, "[%u] timeout", num); + } + + Close(); +} diff --git a/src/client/ClientFile.cxx b/src/client/ClientFile.cxx new file mode 100644 index 000000000..eba64d09c --- /dev/null +++ b/src/client/ClientFile.cxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Client.hxx" +#include "protocol/Ack.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" + +#include <sys/stat.h> +#include <unistd.h> + +bool +Client::AllowFile(Path path_fs, Error &error) const +{ +#ifdef WIN32 + (void)path_fs; + + error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); + return false; +#else + if (uid >= 0 && (uid_t)uid == geteuid()) + /* always allow access if user runs his own MPD + instance */ + return true; + + if (uid <= 0) { + /* unauthenticated client */ + error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); + return false; + } + + struct stat st; + if (!StatFile(path_fs, st)) { + error.SetErrno(); + return false; + } + + if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { + /* client is not owner */ + error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied"); + return false; + } + + return true; +#endif +} diff --git a/src/client/ClientGlobal.cxx b/src/client/ClientGlobal.cxx new file mode 100644 index 000000000..8d90721e9 --- /dev/null +++ b/src/client/ClientGlobal.cxx @@ -0,0 +1,45 @@ +/* + * 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 "ClientInternal.hxx" +#include "config/ConfigGlobal.hxx" + +#define CLIENT_TIMEOUT_DEFAULT (60) +#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +int client_timeout; +size_t client_max_command_list_size; +size_t client_max_output_buffer_size; + +void client_manager_init(void) +{ + client_timeout = config_get_positive(CONF_CONN_TIMEOUT, + CLIENT_TIMEOUT_DEFAULT); + client_max_command_list_size = + config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, + CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) + * 1024; + + client_max_output_buffer_size = + config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, + CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) + * 1024; +} diff --git a/src/client/ClientIdle.cxx b/src/client/ClientIdle.cxx new file mode 100644 index 000000000..0b4fa5751 --- /dev/null +++ b/src/client/ClientIdle.cxx @@ -0,0 +1,75 @@ +/* + * 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 "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> + +void +Client::IdleNotify() +{ + assert(idle_waiting); + assert(idle_flags != 0); + + unsigned flags = idle_flags; + idle_flags = 0; + idle_waiting = false; + + const char *const*idle_names = idle_get_names(); + for (unsigned i = 0; idle_names[i]; ++i) { + if (flags & (1 << i) & idle_subscriptions) + client_printf(*this, "changed: %s\n", + idle_names[i]); + } + + client_puts(*this, "OK\n"); + + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +Client::IdleAdd(unsigned flags) +{ + if (IsExpired()) + return; + + idle_flags |= flags; + if (idle_waiting && (idle_flags & idle_subscriptions)) + IdleNotify(); +} + +bool +Client::IdleWait(unsigned flags) +{ + assert(!idle_waiting); + + idle_waiting = true; + idle_subscriptions = flags; + + if (idle_flags & idle_subscriptions) { + IdleNotify(); + return true; + } else { + /* disable timeouts while in "idle" */ + TimeoutMonitor::Cancel(); + return false; + } +} diff --git a/src/client/ClientInternal.hxx b/src/client/ClientInternal.hxx new file mode 100644 index 000000000..a819d64b8 --- /dev/null +++ b/src/client/ClientInternal.hxx @@ -0,0 +1,39 @@ +/* + * 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_CLIENT_INTERNAL_HXX +#define MPD_CLIENT_INTERNAL_HXX + +#include "check.h" +#include "Client.hxx" +#include "command/CommandResult.hxx" + +static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16; +static constexpr unsigned CLIENT_MAX_MESSAGES = 64; + +extern const class Domain client_domain; + +extern int client_timeout; +extern size_t client_max_command_list_size; +extern size_t client_max_output_buffer_size; + +CommandResult +client_process_line(Client &client, char *line); + +#endif diff --git a/src/client/ClientList.cxx b/src/client/ClientList.cxx new file mode 100644 index 000000000..a1f286928 --- /dev/null +++ b/src/client/ClientList.cxx @@ -0,0 +1,49 @@ +/* + * 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 "ClientList.hxx" +#include "ClientInternal.hxx" + +#include <algorithm> + +#include <assert.h> + +void +ClientList::Remove(Client &client) +{ + assert(!list.empty()); + + list.erase(list.iterator_to(client)); +} + +void +ClientList::CloseAll() +{ + list.clear_and_dispose(Client::Disposer()); +} + +void +ClientList::IdleAdd(unsigned flags) +{ + assert(flags != 0); + + for (auto &client : list) + client.IdleAdd(flags); +} diff --git a/src/client/ClientList.hxx b/src/client/ClientList.hxx new file mode 100644 index 000000000..7d20a8737 --- /dev/null +++ b/src/client/ClientList.hxx @@ -0,0 +1,65 @@ +/* + * 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_CLIENT_LIST_HXX +#define MPD_CLIENT_LIST_HXX + +#include "Client.hxx" + +class Client; + +class ClientList { + typedef boost::intrusive::list<Client, + boost::intrusive::constant_time_size<true>> List; + + const unsigned max_size; + + List list; + +public: + ClientList(unsigned _max_size) + :max_size(_max_size) {} + ~ClientList() { + CloseAll(); + } + + List::iterator begin() { + return list.begin(); + } + + List::iterator end() { + return list.end(); + } + + bool IsFull() const { + return list.size() >= max_size; + } + + void Add(Client &client) { + list.push_front(client); + } + + void Remove(Client &client); + + void CloseAll(); + + void IdleAdd(unsigned flags); +}; + +#endif diff --git a/src/client/ClientMessage.cxx b/src/client/ClientMessage.cxx new file mode 100644 index 000000000..be6d2f007 --- /dev/null +++ b/src/client/ClientMessage.cxx @@ -0,0 +1,41 @@ +/* + * 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 "ClientMessage.hxx" +#include "util/CharUtil.hxx" +#include "Compiler.h" + +gcc_const +static bool +valid_channel_char(const char ch) +{ + return IsAlphaNumericASCII(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} diff --git a/src/client/ClientMessage.hxx b/src/client/ClientMessage.hxx new file mode 100644 index 000000000..bc6a4cd41 --- /dev/null +++ b/src/client/ClientMessage.hxx @@ -0,0 +1,58 @@ +/* + * 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_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H + +#include "Compiler.h" + +#include <string> + +#ifdef WIN32 +/* fuck WIN32! */ +#include <windows.h> +#undef GetMessage +#endif + +/** + * A client-to-client message. + */ +class ClientMessage { + std::string channel, message; + +public: + template<typename T, typename U> + ClientMessage(T &&_channel, U &&_message) + :channel(std::forward<T>(_channel)), + message(std::forward<U>(_message)) {} + + const char *GetChannel() const { + return channel.c_str(); + } + + const char *GetMessage() const { + return message.c_str(); + } +}; + +gcc_pure +bool +client_message_valid_channel_name(const char *name); + +#endif diff --git a/src/client/ClientNew.cxx b/src/client/ClientNew.cxx new file mode 100644 index 000000000..a080e9ec6 --- /dev/null +++ b/src/client/ClientNew.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 "ClientInternal.hxx" +#include "ClientList.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "system/fd_util.h" +#include "system/Resolver.hxx" +#include "Permission.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + +static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; + +Client::Client(EventLoop &_loop, Partition &_partition, + int _fd, int _uid, int _num) + :FullyBufferedSocket(_fd, _loop, 16384, client_max_output_buffer_size), + TimeoutMonitor(_loop), + partition(_partition), + playlist(partition.playlist), player_control(partition.pc), + permission(getDefaultPermissions()), + uid(_uid), + num(_num), + idle_waiting(false), idle_flags(0), + num_subscriptions(0) +{ + TimeoutMonitor::ScheduleSeconds(client_timeout); +} + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const struct sockaddr *sa, size_t sa_length, int uid) +{ + static unsigned int next_client_num; + const auto remote = sockaddr_to_string(sa, sa_length); + + assert(fd >= 0); + +#ifdef HAVE_LIBWRAP + if (sa->sa_family != AF_UNIX) { + // TODO: shall we obtain the program name from argv[0]? + const char *progname = "mpd"; + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + FormatWarning(client_domain, + "libwrap refused connection (libwrap=%s) from %s", + progname, remote.c_str()); + + close_socket(fd); + return; + } + } +#endif /* HAVE_WRAP */ + + ClientList &client_list = *partition.instance.client_list; + if (client_list.IsFull()) { + LogWarning(client_domain, "Max connections reached"); + close_socket(fd); + return; + } + + Client *client = new Client(loop, partition, fd, uid, + next_client_num++); + + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); + + client_list.Add(*client); + + FormatInfo(client_domain, "[%u] opened from %s", + client->num, remote.c_str()); +} + +void +Client::Close() +{ + partition.instance.client_list->Remove(*this); + + SetExpired(); + + FormatInfo(client_domain, "[%u] closed", num); + delete this; +} diff --git a/src/client/ClientProcess.cxx b/src/client/ClientProcess.cxx new file mode 100644 index 000000000..96099a91c --- /dev/null +++ b/src/client/ClientProcess.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" +#include "protocol/Result.hxx" +#include "command/AllCommands.hxx" +#include "Log.hxx" + +#include <string.h> + +#define CLIENT_LIST_MODE_BEGIN "command_list_begin" +#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" +#define CLIENT_LIST_MODE_END "command_list_end" + +static CommandResult +client_process_command_list(Client &client, bool list_ok, + std::list<std::string> &&list) +{ + CommandResult ret = CommandResult::OK; + unsigned num = 0; + + for (auto &&i : list) { + char *cmd = &*i.begin(); + + FormatDebug(client_domain, "process command \"%s\"", cmd); + ret = command_process(client, num++, cmd); + FormatDebug(client_domain, "command returned %i", ret); + if (ret != CommandResult::OK || client.IsExpired()) + break; + else if (list_ok) + client_puts(client, "list_OK\n"); + } + + return ret; +} + +CommandResult +client_process_line(Client &client, char *line) +{ + CommandResult ret; + + if (strcmp(line, "noidle") == 0) { + if (client.idle_waiting) { + /* send empty idle response and leave idle mode */ + client.idle_waiting = false; + command_success(client); + } + + /* do nothing if the client wasn't idling: the client + has already received the full idle response from + client_idle_notify(), which he can now evaluate */ + + return CommandResult::OK; + } else if (client.idle_waiting) { + /* during idle mode, clients must not send anything + except "noidle" */ + FormatWarning(client_domain, + "[%u] command \"%s\" during idle", + client.num, line); + return CommandResult::CLOSE; + } + + if (client.cmd_list.IsActive()) { + if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { + FormatDebug(client_domain, + "[%u] process command list", + client.num); + + auto &&cmd_list = client.cmd_list.Commit(); + + ret = client_process_command_list(client, + client.cmd_list.IsOKMode(), + std::move(cmd_list)); + FormatDebug(client_domain, + "[%u] process command " + "list returned %i", client.num, ret); + + if (ret == CommandResult::CLOSE || + client.IsExpired()) + return CommandResult::CLOSE; + + if (ret == CommandResult::OK) + command_success(client); + + client.cmd_list.Reset(); + } else { + if (!client.cmd_list.Add(line)) { + FormatWarning(client_domain, + "[%u] command list size " + "is larger than the max (%lu)", + client.num, + (unsigned long)client_max_command_list_size); + return CommandResult::CLOSE; + } + + ret = CommandResult::OK; + } + } else { + if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { + client.cmd_list.Begin(false); + ret = CommandResult::OK; + } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { + client.cmd_list.Begin(true); + ret = CommandResult::OK; + } else { + FormatDebug(client_domain, + "[%u] process command \"%s\"", + client.num, line); + ret = command_process(client, 0, line); + FormatDebug(client_domain, + "[%u] command returned %i", + client.num, ret); + + if (ret == CommandResult::CLOSE || + client.IsExpired()) + return CommandResult::CLOSE; + + if (ret == CommandResult::OK) + command_success(client); + } + } + + return ret; +} diff --git a/src/client/ClientRead.cxx b/src/client/ClientRead.cxx new file mode 100644 index 000000000..9cfb1271f --- /dev/null +++ b/src/client/ClientRead.cxx @@ -0,0 +1,75 @@ +/* + * 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 "ClientInternal.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "event/Loop.hxx" +#include "util/StringUtil.hxx" + +#include <string.h> + +BufferedSocket::InputResult +Client::OnSocketInput(void *data, size_t length) +{ + char *p = (char *)data; + char *newline = (char *)memchr(p, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + TimeoutMonitor::ScheduleSeconds(client_timeout); + + BufferedSocket::ConsumeInput(newline + 1 - p); + + /* skip whitespace at the end of the line */ + char *end = StripRight(p, newline); + + /* terminate the string at the end of the line */ + *end = 0; + + CommandResult result = client_process_line(*this, p); + switch (result) { + case CommandResult::OK: + case CommandResult::IDLE: + case CommandResult::ERROR: + break; + + case CommandResult::KILL: + Close(); + partition.instance.event_loop->Break(); + return InputResult::CLOSED; + + case CommandResult::FINISH: + if (Flush()) + Close(); + return InputResult::CLOSED; + + case CommandResult::CLOSE: + Close(); + return InputResult::CLOSED; + } + + if (IsExpired()) { + Close(); + return InputResult::CLOSED; + } + + return InputResult::AGAIN; +} diff --git a/src/client/ClientSubscribe.cxx b/src/client/ClientSubscribe.cxx new file mode 100644 index 000000000..8ea2e363b --- /dev/null +++ b/src/client/ClientSubscribe.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 "ClientInternal.hxx" +#include "Idle.hxx" + +#include <assert.h> + +Client::SubscribeResult +Client::Subscribe(const char *channel) +{ + assert(channel != nullptr); + + if (!client_message_valid_channel_name(channel)) + return Client::SubscribeResult::INVALID; + + if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return Client::SubscribeResult::FULL; + + auto r = subscriptions.insert(channel); + if (!r.second) + return Client::SubscribeResult::ALREADY; + + ++num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return Client::SubscribeResult::OK; +} + +bool +Client::Unsubscribe(const char *channel) +{ + const auto i = subscriptions.find(channel); + if (i == subscriptions.end()) + return false; + + assert(num_subscriptions > 0); + + subscriptions.erase(i); + --num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((num_subscriptions == 0) == + subscriptions.empty()); + + return true; +} + +void +Client::UnsubscribeAll() +{ + subscriptions.clear(); + num_subscriptions = 0; +} + +bool +Client::PushMessage(const ClientMessage &msg) +{ + if (messages.size() >= CLIENT_MAX_MESSAGES || + !IsSubscribed(msg.GetChannel())) + return false; + + if (messages.empty()) + IdleAdd(IDLE_MESSAGE); + + messages.push_back(msg); + return true; +} diff --git a/src/client/ClientWrite.cxx b/src/client/ClientWrite.cxx new file mode 100644 index 000000000..b5d172a8d --- /dev/null +++ b/src/client/ClientWrite.cxx @@ -0,0 +1,61 @@ +/* + * 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 "ClientInternal.hxx" +#include "util/FormatString.hxx" + +#include <string.h> + +/** + * Write a block of data to the client. + */ +static void +client_write(Client &client, const char *data, size_t length) +{ + /* if the client is going to be closed, do nothing */ + if (client.IsExpired() || length == 0) + return; + + client.Write(data, length); +} + +void +client_puts(Client &client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void +client_vprintf(Client &client, const char *fmt, va_list args) +{ + char *p = FormatNewV(fmt, args); + client_write(client, p, strlen(p)); + delete[] p; +} + +void +client_printf(Client &client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 36f6fb97c..6a4b18198 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.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 @@ -20,23 +20,27 @@ #include "config.h" #include "AllCommands.hxx" #include "QueueCommands.hxx" +#include "TagCommands.hxx" #include "PlayerCommands.hxx" #include "PlaylistCommands.hxx" +#include "StorageCommands.hxx" #include "DatabaseCommands.hxx" #include "FileCommands.hxx" #include "OutputCommands.hxx" #include "MessageCommands.hxx" +#include "NeighborCommands.hxx" #include "OtherCommands.hxx" #include "Permission.hxx" #include "tag/TagType.h" #include "protocol/Result.hxx" -#include "Client.hxx" +#include "Partition.hxx" +#include "client/Client.hxx" #include "util/Tokenizer.hxx" #include "util/Error.hxx" #ifdef ENABLE_SQLITE #include "StickerCommands.hxx" -#include "StickerDatabase.hxx" +#include "sticker/StickerDatabase.hxx" #endif #include <assert.h> @@ -56,15 +60,15 @@ struct command { unsigned permission; int min; int max; - CommandResult (*handler)(Client &client, int argc, char **argv); + CommandResult (*handler)(Client &client, unsigned argc, char **argv); }; /* don't be fooled, this is the command handler for "commands" command */ static CommandResult -handle_commands(Client &client, int argc, char *argv[]); +handle_commands(Client &client, unsigned argc, char *argv[]); static CommandResult -handle_not_commands(Client &client, int argc, char *argv[]); +handle_not_commands(Client &client, unsigned argc, char *argv[]); /** * The command registry. @@ -74,14 +78,18 @@ handle_not_commands(Client &client, int argc, char *argv[]); static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid }, { "close", PERMISSION_NONE, -1, -1, handle_close }, { "commands", PERMISSION_NONE, 0, 0, handle_commands }, { "config", PERMISSION_ADMIN, 0, 0, handle_config }, { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, +#ifdef ENABLE_DATABASE { "count", PERMISSION_READ, 2, -1, handle_count }, +#endif { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, @@ -89,13 +97,24 @@ static const struct command commands[] = { { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, +#ifdef ENABLE_DATABASE { "find", PERMISSION_READ, 2, -1, handle_find }, { "findadd", PERMISSION_ADD, 2, -1, handle_findadd}, +#endif { "idle", PERMISSION_READ, 0, -1, handle_idle }, { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, +#ifdef ENABLE_DATABASE { "list", PERMISSION_READ, 1, -1, handle_list }, { "listall", PERMISSION_READ, 0, 1, handle_listall }, { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, +#endif + { "listfiles", PERMISSION_READ, 0, 1, handle_listfiles }, +#ifdef ENABLE_DATABASE + { "listmounts", PERMISSION_READ, 0, 0, handle_listmounts }, +#endif +#ifdef ENABLE_NEIGHBOR_PLUGINS + { "listneighbors", PERMISSION_READ, 0, 0, handle_listneighbors }, +#endif { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, @@ -103,6 +122,9 @@ static const struct command commands[] = { { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, +#ifdef ENABLE_DATABASE + { "mount", PERMISSION_ADMIN, 2, 2, handle_mount }, +#endif { "move", PERMISSION_CONTROL, 2, 2, handle_move }, { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, { "next", PERMISSION_CONTROL, 0, 0, handle_next }, @@ -128,6 +150,7 @@ static const struct command commands[] = { { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid }, { "readcomments", PERMISSION_READ, 1, 1, handle_read_comments }, { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, @@ -139,9 +162,11 @@ static const struct command commands[] = { { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, { "save", PERMISSION_CONTROL, 1, 1, handle_save }, +#ifdef ENABLE_DATABASE { "search", PERMISSION_READ, 2, -1, handle_search }, { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, +#endif { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, @@ -160,6 +185,9 @@ static const struct command commands[] = { { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, { "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput }, +#ifdef ENABLE_DATABASE + { "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount }, +#endif { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, { "update", PERMISSION_CONTROL, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, @@ -169,20 +197,26 @@ static const struct command commands[] = { static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); static bool -command_available(gcc_unused const struct command *cmd) +command_available(gcc_unused const Partition &partition, + gcc_unused const struct command *cmd) { #ifdef ENABLE_SQLITE if (strcmp(cmd->cmd, "sticker") == 0) return sticker_enabled(); #endif +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (strcmp(cmd->cmd, "listneighbors") == 0) + return neighbor_commands_available(partition.instance); +#endif + return true; } /* don't be fooled, this is the command handler for "commands" command */ static CommandResult handle_commands(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { const unsigned permission = client.GetPermission(); const struct command *cmd; @@ -191,7 +225,7 @@ handle_commands(Client &client, cmd = &commands[i]; if (cmd->permission == (permission & cmd->permission) && - command_available(cmd)) + command_available(client.partition, cmd)) client_printf(client, "command: %s\n", cmd->cmd); } @@ -200,7 +234,7 @@ handle_commands(Client &client, static CommandResult handle_not_commands(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { const unsigned permission = client.GetPermission(); const struct command *cmd; @@ -252,10 +286,10 @@ command_lookup(const char *name) static bool command_check_request(const struct command *cmd, Client &client, - unsigned permission, int argc, char *argv[]) + unsigned permission, unsigned argc, char *argv[]) { - int min = cmd->min + 1; - int max = cmd->max + 1; + const unsigned min = cmd->min + 1; + const unsigned max = cmd->max + 1; if (cmd->permission != (permission & cmd->permission)) { command_error(client, ACK_ERROR_PERMISSION, @@ -286,7 +320,7 @@ command_check_request(const struct command *cmd, Client &client, static const struct command * command_checked_lookup(Client &client, unsigned permission, - int argc, char *argv[]) + unsigned argc, char *argv[]) { const struct command *cmd; @@ -335,7 +369,9 @@ command_process(Client &client, unsigned num, char *line) current_command = nullptr; - return CommandResult::ERROR; + /* this client does not speak the MPD protocol; kick + the connection */ + return CommandResult::FINISH; } unsigned argc = 1; diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx index 2be94c136..b7834a8de 100644 --- a/src/command/AllCommands.hxx +++ b/src/command/AllCommands.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/command/CommandError.cxx b/src/command/CommandError.cxx index fc14d4a5d..89085fc68 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.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,14 +19,13 @@ #include "config.h" #include "CommandError.hxx" -#include "DatabaseError.hxx" +#include "db/DatabaseError.hxx" #include "protocol/Result.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> +#include <string.h> #include <errno.h> CommandResult @@ -38,7 +37,7 @@ print_playlist_result(Client &client, PlaylistResult result) case PlaylistResult::ERRNO: command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(errno)); + strerror(errno)); return CommandResult::ERROR; case PlaylistResult::DENIED: @@ -102,6 +101,7 @@ print_error(Client &client, const Error &error) command_error(client, (ack)error.GetCode(), "%s", error.GetMessage()); return CommandResult::ERROR; +#ifdef ENABLE_DATABASE } else if (error.IsDomain(db_domain)) { switch ((enum db_error)error.GetCode()) { case DB_DISABLED: @@ -112,10 +112,15 @@ print_error(Client &client, const Error &error) case DB_NOT_FOUND: command_error(client, ACK_ERROR_NO_EXIST, "Not found"); return CommandResult::ERROR; + + case DB_CONFLICT: + command_error(client, ACK_ERROR_ARG, "Conflict"); + return CommandResult::ERROR; } +#endif } else if (error.IsDomain(errno_domain)) { command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(error.GetCode())); + strerror(error.GetCode())); return CommandResult::ERROR; } diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx index c7d3fff76..b48baa5bf 100644 --- a/src/command/CommandError.hxx +++ b/src/command/CommandError.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/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx index cc10f7205..1dcbf2946 100644 --- a/src/command/CommandListBuilder.cxx +++ b/src/command/CommandListBuilder.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,7 +19,7 @@ #include "config.h" #include "CommandListBuilder.hxx" -#include "ClientInternal.hxx" +#include "client/ClientInternal.hxx" #include <string.h> diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx index a112ac33b..0747c4697 100644 --- a/src/command/CommandListBuilder.hxx +++ b/src/command/CommandListBuilder.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/command/CommandResult.hxx b/src/command/CommandResult.hxx index 9916b70cb..a2e968fb6 100644 --- a/src/command/CommandResult.hxx +++ b/src/command/CommandResult.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/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index a7d2467b8..a3ea8d0ae 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.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,47 +19,59 @@ #include "config.h" #include "DatabaseCommands.hxx" -#include "DatabaseQueue.hxx" -#include "DatabasePlaylist.hxx" -#include "DatabasePrint.hxx" -#include "DatabaseSelection.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabaseQueue.hxx" +#include "db/DatabasePlaylist.hxx" +#include "db/DatabasePrint.hxx" +#include "db/Count.hxx" +#include "db/Selection.hxx" #include "CommandError.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "tag/Tag.hxx" -#include "util/UriUtil.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "SongFilter.hxx" #include "protocol/Result.hxx" #include "BulkEdit.hxx" -#include <assert.h> #include <string.h> CommandResult -handle_lsinfo2(Client &client, int argc, char *argv[]) +handle_listfiles_db(Client &client, const char *uri) { - const char *uri; + const DatabaseSelection selection(uri, false); - if (argc == 2) - uri = argv[1]; - else + Error error; + if (!db_selection_print(client, selection, false, true, error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_lsinfo2(Client &client, unsigned argc, char *argv[]) +{ + const char *const uri = argc == 2 + ? argv[1] /* default is root directory */ - uri = ""; + : ""; const DatabaseSelection selection(uri, false); Error error; - if (!db_selection_print(client, selection, true, error)) + if (!db_selection_print(client, selection, true, false, error)) return print_error(client, error); return CommandResult::OK; } static CommandResult -handle_match(Client &client, int argc, char *argv[], bool fold_case) +handle_match(Client &client, unsigned argc, char *argv[], bool fold_case) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -67,28 +79,30 @@ handle_match(Client &client, int argc, char *argv[], bool fold_case) const DatabaseSelection selection("", true, &filter); Error error; - return db_selection_print(client, selection, true, error) + return db_selection_print(client, selection, true, false, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_find(Client &client, int argc, char *argv[]) +handle_find(Client &client, unsigned argc, char *argv[]) { return handle_match(client, argc, argv, false); } CommandResult -handle_search(Client &client, int argc, char *argv[]) +handle_search(Client &client, unsigned argc, char *argv[]) { return handle_match(client, argc, argv, true); } static CommandResult -handle_match_add(Client &client, int argc, char *argv[], bool fold_case) +handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -103,51 +117,73 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case) } CommandResult -handle_findadd(Client &client, int argc, char *argv[]) +handle_findadd(Client &client, unsigned argc, char *argv[]) { return handle_match_add(client, argc, argv, false); } CommandResult -handle_searchadd(Client &client, int argc, char *argv[]) +handle_searchadd(Client &client, unsigned argc, char *argv[]) { return handle_match_add(client, argc, argv, true); } CommandResult -handle_searchaddpl(Client &client, int argc, char *argv[]) +handle_searchaddpl(Client &client, unsigned argc, char *argv[]) { - const char *playlist = argv[1]; + ConstBuffer<const char *> args(argv + 1, argc - 1); + const char *playlist = args.shift(); SongFilter filter; - if (!filter.Parse(argc - 2, argv + 2, true)) { + if (!filter.Parse(args, true)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } Error error; - return search_add_to_playlist("", playlist, &filter, error) + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return print_error(client, error); + + return search_add_to_playlist(*db, *client.GetStorage(), + "", playlist, &filter, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_count(Client &client, int argc, char *argv[]) +handle_count(Client &client, unsigned argc, char *argv[]) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + + TagType group = TAG_NUM_OF_ITEM_TYPES; + if (args.size >= 2 && strcmp(args[args.size - 2], "group") == 0) { + const char *s = args[args.size - 1]; + group = tag_name_parse_i(s); + if (group == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", s); + return CommandResult::ERROR; + } + + args.pop_back(); + args.pop_back(); + } + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, false)) { + if (!args.IsEmpty() && !filter.Parse(args, false)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } Error error; - return searchStatsForSongsIn(client, "", &filter, error) + return PrintSongCount(client, "", &filter, group, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_listall(Client &client, gcc_unused int argc, char *argv[]) +handle_listall(Client &client, gcc_unused unsigned argc, char *argv[]) { const char *directory = ""; @@ -155,30 +191,31 @@ handle_listall(Client &client, gcc_unused int argc, char *argv[]) directory = argv[1]; Error error; - return printAllIn(client, directory, error) + return db_selection_print(client, DatabaseSelection(directory, true), + false, false, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_list(Client &client, int argc, char *argv[]) +handle_list(Client &client, unsigned argc, char *argv[]) { - unsigned tagType = locate_parse_type(argv[1]); - - if (tagType == TAG_NUM_OF_ITEM_TYPES) { - command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); - return CommandResult::ERROR; - } + ConstBuffer<const char *> args(argv + 1, argc - 1); + const char *tag_name = args.shift(); + unsigned tagType = locate_parse_type(tag_name); - if (tagType == LOCATE_TAG_ANY_TYPE) { + if (tagType >= TAG_NUM_OF_ITEM_TYPES && + tagType != LOCATE_TAG_FILE_TYPE) { command_error(client, ACK_ERROR_ARG, - "\"any\" is not a valid return tag type"); + "Unknown tag type: %s", tag_name); return CommandResult::ERROR; } - /* for compatibility with < 0.12.0 */ - SongFilter *filter; - if (argc == 3) { + SongFilter *filter = nullptr; + uint32_t group_mask = 0; + + if (args.size == 1) { + /* for compatibility with < 0.12.0 */ if (tagType != TAG_ALBUM) { command_error(client, ACK_ERROR_ARG, "should be \"%s\" for 3 arguments", @@ -186,21 +223,45 @@ handle_list(Client &client, int argc, char *argv[]) return CommandResult::ERROR; } - filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); - } else if (argc > 2) { + filter = new SongFilter((unsigned)TAG_ARTIST, args.shift()); + } + + while (args.size >= 2 && + strcmp(args[args.size - 2], "group") == 0) { + const char *s = args[args.size - 1]; + TagType gt = tag_name_parse_i(s); + if (gt == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", s); + return CommandResult::ERROR; + } + + group_mask |= 1u << unsigned(gt); + + args.pop_back(); + args.pop_back(); + } + + if (!args.IsEmpty()) { filter = new SongFilter(); - if (!filter->Parse(argc - 2, argv + 2, false)) { + if (!filter->Parse(args, false)) { delete filter; command_error(client, ACK_ERROR_ARG, "not able to parse args"); return CommandResult::ERROR; } - } else - filter = nullptr; + } + + if (tagType < TAG_NUM_OF_ITEM_TYPES && + group_mask & (1u << tagType)) { + delete filter; + command_error(client, ACK_ERROR_ARG, "Conflicting group"); + return CommandResult::ERROR; + } Error error; CommandResult ret = - listAllUniqueTags(client, tagType, filter, error) + PrintUniqueTags(client, tagType, group_mask, filter, error) ? CommandResult::OK : print_error(client, error); @@ -210,7 +271,7 @@ handle_list(Client &client, int argc, char *argv[]) } CommandResult -handle_listallinfo(Client &client, gcc_unused int argc, char *argv[]) +handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[]) { const char *directory = ""; @@ -218,7 +279,8 @@ handle_listallinfo(Client &client, gcc_unused int argc, char *argv[]) directory = argv[1]; Error error; - return printInfoForAllIn(client, directory, error) + return db_selection_print(client, DatabaseSelection(directory, true), + true, false, error) ? CommandResult::OK : print_error(client, error); } diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx index 76b2ba817..7abf89e0c 100644 --- a/src/command/DatabaseCommands.hxx +++ b/src/command/DatabaseCommands.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 @@ -25,33 +25,36 @@ class Client; CommandResult -handle_lsinfo2(Client &client, int argc, char *argv[]); +handle_listfiles_db(Client &client, const char *uri); CommandResult -handle_find(Client &client, int argc, char *argv[]); +handle_lsinfo2(Client &client, unsigned argc, char *argv[]); CommandResult -handle_findadd(Client &client, int argc, char *argv[]); +handle_find(Client &client, unsigned argc, char *argv[]); CommandResult -handle_search(Client &client, int argc, char *argv[]); +handle_findadd(Client &client, unsigned argc, char *argv[]); CommandResult -handle_searchadd(Client &client, int argc, char *argv[]); +handle_search(Client &client, unsigned argc, char *argv[]); CommandResult -handle_searchaddpl(Client &client, int argc, char *argv[]); +handle_searchadd(Client &client, unsigned argc, char *argv[]); CommandResult -handle_count(Client &client, int argc, char *argv[]); +handle_searchaddpl(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listall(Client &client, int argc, char *argv[]); +handle_count(Client &client, unsigned argc, char *argv[]); CommandResult -handle_list(Client &client, int argc, char *argv[]); +handle_listall(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listallinfo(Client &client, int argc, char *argv[]); +handle_list(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_listallinfo(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index eecc3102f..0a4f9592a 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.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 @@ -17,23 +17,108 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define __STDC_FORMAT_MACROS /* for PRIu64 */ + #include "config.h" #include "FileCommands.hxx" #include "CommandError.hxx" #include "protocol/Ack.hxx" #include "protocol/Result.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "util/CharUtil.hxx" +#include "util/UriUtil.hxx" #include "util/Error.hxx" #include "tag/TagHandler.hxx" #include "tag/ApeTag.hxx" #include "tag/TagId3.hxx" +#include "TagStream.hxx" #include "TagFile.hxx" -#include "Mapper.hxx" +#include "storage/StorageInterface.hxx" #include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "TimePrint.hxx" +#include "ls.hxx" #include <assert.h> +#include <sys/stat.h> +#include <inttypes.h> /* for PRIu64 */ + +gcc_pure +static bool +SkipNameFS(const char *name_fs) +{ + return name_fs[0] == '.' && + (name_fs[1] == 0 || + (name_fs[1] == '.' && name_fs[2] == 0)); +} + +gcc_pure +static bool +skip_path(const char *name_fs) +{ + return strchr(name_fs, '\n') != nullptr; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +/* PRIu64 causes bogus compiler warning */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +#endif + +CommandResult +handle_listfiles_local(Client &client, const char *path_utf8) +{ + const auto path_fs = AllocatedPath::FromUTF8(path_utf8); + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return CommandResult::ERROR; + } + + Error error; + if (!client.AllowFile(path_fs, error)) + return print_error(client, error); + + DirectoryReader reader(path_fs); + if (reader.HasFailed()) { + error.FormatErrno("Failed to open '%s'", path_utf8); + return print_error(client, error); + } + + while (reader.ReadEntry()) { + const Path name_fs = reader.GetEntry(); + if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs.c_str())) + continue; + + std::string name_utf8 = name_fs.ToUTF8(); + if (name_utf8.empty()) + continue; + + const AllocatedPath full_fs = + AllocatedPath::Build(path_fs, name_fs); + struct stat st; + if (!StatFile(full_fs, st, false)) + continue; + + if (S_ISREG(st.st_mode)) { + client_printf(client, "file: %s\n" + "size: %" PRIu64 "\n", + name_utf8.c_str(), + uint64_t(st.st_size)); + } else if (S_ISDIR(st.st_mode)) + client_printf(client, "directory: %s\n", + name_utf8.c_str()); + + time_print(client, "Last-Modified", st.st_mtime); + } + + return CommandResult::OK; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif gcc_pure static bool @@ -80,19 +165,52 @@ static constexpr tag_handler print_comment_handler = { print_pair, }; +static CommandResult +read_stream_comments(Client &client, const char *uri) +{ + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + if (!tag_stream_scan(uri, print_comment_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "Failed to load file"); + return CommandResult::ERROR; + } + + return CommandResult::OK; + +} + +static CommandResult +read_file_comments(Client &client, const Path path_fs) +{ + if (!tag_file_scan(path_fs, print_comment_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "Failed to load file"); + return CommandResult::ERROR; + } + + tag_ape_scan2(path_fs, &print_comment_handler, &client); + tag_id3_scan(path_fs, &print_comment_handler, &client); + + return CommandResult::OK; + +} + CommandResult -handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) +handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[]) { assert(argc == 2); const char *const uri = argv[1]; - AllocatedPath path_fs = AllocatedPath::Null(); - if (memcmp(uri, "file:///", 8) == 0) { /* read comments from arbitrary local file */ const char *path_utf8 = uri + 7; - path_fs = AllocatedPath::FromUTF8(path_utf8); + AllocatedPath path_fs = AllocatedPath::FromUTF8(path_utf8); if (path_fs.IsNull()) { command_error(client, ACK_ERROR_NO_EXIST, "unsupported file name"); @@ -100,28 +218,41 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) } Error error; - if (!client_allow_file(client, path_fs, error)) + if (!client.AllowFile(path_fs, error)) return print_error(client, error); - } else if (*uri != '/') { - path_fs = map_uri_fs(uri); - if (path_fs.IsNull()) { + + return read_file_comments(client, path_fs); + } else if (uri_has_scheme(uri)) { + return read_stream_comments(client, uri); + } else if (!PathTraitsUTF8::IsAbsolute(uri)) { +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage == nullptr) { +#endif command_error(client, ACK_ERROR_NO_EXIST, - "No such file"); + "No database"); return CommandResult::ERROR; +#ifdef ENABLE_DATABASE } - } else { + + { + AllocatedPath path_fs = storage->MapFS(uri); + if (!path_fs.IsNull()) + return read_file_comments(client, path_fs); + } + + { + const std::string uri2 = storage->MapUTF8(uri); + if (uri_has_scheme(uri2.c_str())) + return read_stream_comments(client, + uri2.c_str()); + } + command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; - } - - if (!tag_file_scan(path_fs, &print_comment_handler, &client)) { - command_error(client, ACK_ERROR_NO_EXIST, - "Failed to load file"); +#endif + } else { + command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; } - - tag_ape_scan2(path_fs, &print_comment_handler, &client); - tag_id3_scan(path_fs, &print_comment_handler, &client); - - return CommandResult::OK; } diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx index e184d6e30..62835a82c 100644 --- a/src/command/FileCommands.hxx +++ b/src/command/FileCommands.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 @@ -25,6 +25,9 @@ class Client; CommandResult -handle_read_comments(Client &client, int argc, char *argv[]); +handle_listfiles_local(Client &client, const char *path_utf8); + +CommandResult +handle_read_comments(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx index 7d9893e70..a86bdf30c 100644 --- a/src/command/MessageCommands.cxx +++ b/src/command/MessageCommands.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,12 +19,11 @@ #include "config.h" #include "MessageCommands.hxx" -#include "Client.hxx" -#include "ClientList.hxx" +#include "client/Client.hxx" +#include "client/ClientList.hxx" #include "Instance.hxx" -#include "Main.hxx" +#include "Partition.hxx" #include "protocol/Result.hxx" -#include "protocol/ArgParser.hxx" #include <set> #include <string> @@ -32,7 +31,7 @@ #include <assert.h> CommandResult -handle_subscribe(Client &client, gcc_unused int argc, char *argv[]) +handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[]) { assert(argc == 2); @@ -62,7 +61,7 @@ handle_subscribe(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[]) +handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[]) { assert(argc == 2); @@ -77,14 +76,14 @@ handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_channels(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { assert(argc == 1); std::set<std::string> channels; - for (const auto &c : *instance->client_list) - channels.insert(c->subscriptions.begin(), - c->subscriptions.end()); + for (const auto &c : *client.partition.instance.client_list) + channels.insert(c.subscriptions.begin(), + c.subscriptions.end()); for (const auto &channel : channels) client_printf(client, "channel: %s\n", channel.c_str()); @@ -94,7 +93,7 @@ handle_channels(Client &client, CommandResult handle_read_messages(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { assert(argc == 1); @@ -111,7 +110,7 @@ handle_read_messages(Client &client, CommandResult handle_send_message(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { assert(argc == 3); @@ -123,8 +122,8 @@ handle_send_message(Client &client, bool sent = false; const ClientMessage msg(argv[1], argv[2]); - for (const auto &c : *instance->client_list) - if (c->PushMessage(msg)) + for (auto &c : *client.partition.instance.client_list) + if (c.PushMessage(msg)) sent = true; if (sent) diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx index 6a107f69d..ac8afe2fb 100644 --- a/src/command/MessageCommands.hxx +++ b/src/command/MessageCommands.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 @@ -25,18 +25,18 @@ class Client; CommandResult -handle_subscribe(Client &client, int argc, char *argv[]); +handle_subscribe(Client &client, unsigned argc, char *argv[]); CommandResult -handle_unsubscribe(Client &client, int argc, char *argv[]); +handle_unsubscribe(Client &client, unsigned argc, char *argv[]); CommandResult -handle_channels(Client &client, int argc, char *argv[]); +handle_channels(Client &client, unsigned argc, char *argv[]); CommandResult -handle_read_messages(Client &client, int argc, char *argv[]); +handle_read_messages(Client &client, unsigned argc, char *argv[]); CommandResult -handle_send_message(Client &client, int argc, char *argv[]); +handle_send_message(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx new file mode 100644 index 000000000..22e8adf9e --- /dev/null +++ b/src/command/NeighborCommands.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "NeighborCommands.hxx" +#include "client/Client.hxx" +#include "Instance.hxx" +#include "Partition.hxx" +#include "protocol/Result.hxx" +#include "neighbor/Glue.hxx" +#include "neighbor/Info.hxx" + +#include <set> +#include <string> + +#include <assert.h> + +bool +neighbor_commands_available(const Instance &instance) +{ + return instance.neighbors != nullptr; +} + +CommandResult +handle_listneighbors(Client &client, + gcc_unused unsigned argc, gcc_unused char *argv[]) +{ + const NeighborGlue *const neighbors = + client.partition.instance.neighbors; + if (neighbors == nullptr) { + command_error(client, ACK_ERROR_UNKNOWN, + "No neighbor plugin configured"); + return CommandResult::ERROR; + } + + for (const auto &i : neighbors->GetList()) + client_printf(client, + "neighbor: %s\n" + "name: %s\n", + i.uri.c_str(), + i.display_name.c_str()); + return CommandResult::OK; +} diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx new file mode 100644 index 000000000..7fb309aeb --- /dev/null +++ b/src/command/NeighborCommands.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_NEIGHBOR_COMMANDS_HXX +#define MPD_NEIGHBOR_COMMANDS_HXX + +#include "CommandResult.hxx" +#include "Compiler.h" + +struct Instance; +class Client; + +gcc_pure +bool +neighbor_commands_available(const Instance &instance); + +CommandResult +handle_listneighbors(Client &client, unsigned argc, char *argv[]); + +#endif diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 7b2cb1331..a924f77b5 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.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,33 +19,38 @@ #include "config.h" #include "OtherCommands.hxx" -#include "DatabaseCommands.hxx" +#include "FileCommands.hxx" +#include "StorageCommands.hxx" #include "CommandError.hxx" -#include "UpdateGlue.hxx" -#include "Directory.hxx" -#include "Song.hxx" +#include "db/Uri.hxx" +#include "storage/StorageInterface.hxx" +#include "DetachedSong.hxx" #include "SongPrint.hxx" #include "TagPrint.hxx" +#include "TagStream.hxx" +#include "tag/TagHandler.hxx" #include "TimePrint.hxx" -#include "Mapper.hxx" -#include "DecoderPrint.hxx" +#include "decoder/DecoderPrint.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" -#include "Volume.hxx" -#include "util/ASCII.hxx" +#include "mixer/Volume.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" #include "fs/AllocatedPath.hxx" #include "Stats.hxx" #include "Permission.hxx" #include "PlaylistFile.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "db/PlaylistVector.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" #include "Idle.hxx" -#ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" +#ifdef ENABLE_DATABASE +#include "DatabaseCommands.hxx" +#include "db/Interface.hxx" +#include "db/update/Service.hxx" #endif #include <assert.h> @@ -64,7 +69,7 @@ print_spl_list(Client &client, const PlaylistVector &list) CommandResult handle_urlhandlers(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { if (client.IsLocal()) client_puts(client, "handler: file://\n"); @@ -74,7 +79,7 @@ handle_urlhandlers(Client &client, CommandResult handle_decoders(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { decoder_list_print(client); return CommandResult::OK; @@ -82,7 +87,7 @@ handle_decoders(Client &client, CommandResult handle_tagtypes(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { tag_print_types(client); return CommandResult::OK; @@ -90,28 +95,74 @@ handle_tagtypes(Client &client, CommandResult handle_kill(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { return CommandResult::KILL; } CommandResult handle_close(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { return CommandResult::FINISH; } +static void +print_tag(TagType type, const char *value, void *ctx) +{ + Client &client = *(Client *)ctx; + + tag_print(client, type, value); +} + CommandResult -handle_lsinfo(Client &client, int argc, char *argv[]) +handle_listfiles(Client &client, unsigned argc, char *argv[]) { - const char *uri; + const char *const uri = argc == 2 + ? argv[1] + /* default is root directory */ + : ""; + + if (memcmp(uri, "file:///", 8) == 0) + /* list local directory */ + return handle_listfiles_local(client, uri + 7); + +#ifdef ENABLE_DATABASE + if (uri_has_scheme(uri)) + /* use storage plugin to list remote directory */ + return handle_listfiles_storage(client, uri); + + /* must be a path relative to the configured + music_directory */ + + if (client.partition.instance.storage != nullptr) + /* if we have a storage instance, obtain a list of + files from it */ + return handle_listfiles_storage(client, + *client.partition.instance.storage, + uri); + + /* fall back to entries from database if we have no storage */ + return handle_listfiles_db(client, uri); +#else + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif +} + +static constexpr tag_handler print_tag_handler = { + nullptr, + print_tag, + nullptr, +}; - if (argc == 2) - uri = argv[1]; - else +CommandResult +handle_lsinfo(Client &client, unsigned argc, char *argv[]) +{ + const char *const uri = argc == 2 + ? argv[1] /* default is root directory */ - uri = ""; + : ""; if (memcmp(uri, "file:///", 8) == 0) { /* print information about an arbitrary local file */ @@ -125,55 +176,63 @@ handle_lsinfo(Client &client, int argc, char *argv[]) } Error error; - if (!client_allow_file(client, path_fs, error)) + if (!client.AllowFile(path_fs, error)) return print_error(client, error); - Song *song = Song::LoadFile(path_utf8, nullptr); - if (song == nullptr) { + DetachedSong song(path_utf8); + if (!song.Update()) { command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; } - song_print_info(client, *song); - song->Free(); + song_print_info(client, song); return CommandResult::OK; } + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + if (!tag_stream_scan(uri, print_tag_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return CommandResult::ERROR; + } + + return CommandResult::OK; + } + +#ifdef ENABLE_DATABASE CommandResult result = handle_lsinfo2(client, argc, argv); if (result != CommandResult::OK) return result; +#endif if (isRootDirectory(uri)) { Error error; const auto &list = ListPlaylistFiles(error); print_spl_list(client, list); + } else { +#ifndef ENABLE_DATABASE + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif } return CommandResult::OK; } -CommandResult -handle_update(Client &client, gcc_unused int argc, char *argv[]) -{ - const char *path = ""; - unsigned ret; +#ifdef ENABLE_DATABASE - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; - - if (*path == 0 || strcmp(path, "/") == 0) - /* backwards compatibility with MPD 0.15 */ - path = ""; - else if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); - return CommandResult::ERROR; - } - } - - ret = update_enqueue(path, false); +static CommandResult +handle_update(Client &client, UpdateService &update, + const char *uri_utf8, bool discard) +{ + unsigned ret = update.Enqueue(uri_utf8, discard); if (ret > 0) { client_printf(client, "updating_db: %i\n", ret); return CommandResult::OK; @@ -184,36 +243,78 @@ handle_update(Client &client, gcc_unused int argc, char *argv[]) } } -CommandResult -handle_rescan(Client &client, gcc_unused int argc, char *argv[]) +static CommandResult +handle_update(Client &client, Database &db, + const char *uri_utf8, bool discard) { + Error error; + unsigned id = db.Update(uri_utf8, discard, error); + if (id > 0) { + client_printf(client, "updating_db: %i\n", id); + return CommandResult::OK; + } else if (error.IsDefined()) { + return print_error(client, error); + } else { + /* Database::Update() has returned 0 without setting + the Error: the method is not implemented */ + command_error(client, ACK_ERROR_NO_EXIST, "Not implemented"); + return CommandResult::ERROR; + } +} + +#endif + +static CommandResult +handle_update(Client &client, unsigned argc, char *argv[], bool discard) +{ +#ifdef ENABLE_DATABASE const char *path = ""; - unsigned ret; assert(argc <= 2); if (argc == 2) { path = argv[1]; - if (!uri_safe_local(path)) { + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = ""; + else if (!uri_safe_local(path)) { command_error(client, ACK_ERROR_ARG, "Malformed path"); return CommandResult::ERROR; } } - ret = update_enqueue(path, true); - if (ret > 0) { - client_printf(client, "updating_db: %i\n", ret); - return CommandResult::OK; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return CommandResult::ERROR; - } + UpdateService *update = client.partition.instance.update; + if (update != nullptr) + return handle_update(client, *update, path, discard); + + Database *db = client.partition.instance.database; + if (db != nullptr) + return handle_update(client, *db, path, discard); +#else + (void)argc; + (void)argv; + (void)discard; +#endif + + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +} + +CommandResult +handle_update(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + return handle_update(client, argc, argv, false); } CommandResult -handle_setvol(Client &client, gcc_unused int argc, char *argv[]) +handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + return handle_update(client, argc, argv, true); +} + +CommandResult +handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned level; bool success; @@ -226,7 +327,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - success = volume_level_change(level); + success = volume_level_change(client.partition.outputs, level); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); @@ -237,7 +338,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_volume(Client &client, gcc_unused int argc, char *argv[]) +handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) { int relative; if (!check_int(client, &relative, argv[1])) @@ -248,7 +349,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - const int old_volume = volume_level_get(); + const int old_volume = volume_level_get(client.partition.outputs); if (old_volume < 0) { command_error(client, ACK_ERROR_SYSTEM, "No mixer"); return CommandResult::ERROR; @@ -260,7 +361,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) else if (new_volume > 100) new_volume = 100; - if (new_volume != old_volume && !volume_level_change(new_volume)) { + if (new_volume != old_volume && + !volume_level_change(client.partition.outputs, new_volume)) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); return CommandResult::ERROR; @@ -271,7 +373,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_stats(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { stats_print(client); return CommandResult::OK; @@ -279,13 +381,13 @@ handle_stats(Client &client, CommandResult handle_ping(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { return CommandResult::OK; } CommandResult -handle_password(Client &client, gcc_unused int argc, char *argv[]) +handle_password(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned permission = 0; @@ -301,7 +403,7 @@ handle_password(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_config(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { if (!client.IsLocal()) { command_error(client, ACK_ERROR_PERMISSION, @@ -309,31 +411,33 @@ handle_config(Client &client, return CommandResult::ERROR; } - const char *path = mapper_get_music_directory_utf8(); - if (path != nullptr) - client_printf(client, "music_directory: %s\n", path); +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage != nullptr) { + const auto path = storage->MapUTF8(""); + client_printf(client, "music_directory: %s\n", path.c_str()); + } +#endif return CommandResult::OK; } CommandResult handle_idle(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { - unsigned flags = 0, j; - int i; - const char *const* idle_names; - - idle_names = idle_get_names(); - for (i = 1; i < argc; ++i) { - if (!argv[i]) - continue; - - for (j = 0; idle_names[j]; ++j) { - if (StringEqualsCaseASCII(argv[i], idle_names[j])) { - flags |= (1 << j); - } + unsigned flags = 0; + + for (unsigned i = 1; i < argc; ++i) { + unsigned event = idle_parse_name(argv[i]); + if (event == 0) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized idle event: %s", + argv[i]); + return CommandResult::ERROR; } + + flags |= event; } /* No argument means that the client wants to receive everything */ diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx index 1a0b16ed1..7cfa35dfb 100644 --- a/src/command/OtherCommands.hxx +++ b/src/command/OtherCommands.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 @@ -25,48 +25,51 @@ class Client; CommandResult -handle_urlhandlers(Client &client, int argc, char *argv[]); +handle_urlhandlers(Client &client, unsigned argc, char *argv[]); CommandResult -handle_decoders(Client &client, int argc, char *argv[]); +handle_decoders(Client &client, unsigned argc, char *argv[]); CommandResult -handle_tagtypes(Client &client, int argc, char *argv[]); +handle_tagtypes(Client &client, unsigned argc, char *argv[]); CommandResult -handle_kill(Client &client, int argc, char *argv[]); +handle_kill(Client &client, unsigned argc, char *argv[]); CommandResult -handle_close(Client &client, int argc, char *argv[]); +handle_close(Client &client, unsigned argc, char *argv[]); CommandResult -handle_lsinfo(Client &client, int argc, char *argv[]); +handle_listfiles(Client &client, unsigned argc, char *argv[]); CommandResult -handle_update(Client &client, int argc, char *argv[]); +handle_lsinfo(Client &client, unsigned argc, char *argv[]); CommandResult -handle_rescan(Client &client, int argc, char *argv[]); +handle_update(Client &client, unsigned argc, char *argv[]); CommandResult -handle_setvol(Client &client, int argc, char *argv[]); +handle_rescan(Client &client, unsigned argc, char *argv[]); CommandResult -handle_volume(Client &client, int argc, char *argv[]); +handle_setvol(Client &client, unsigned argc, char *argv[]); CommandResult -handle_stats(Client &client, int argc, char *argv[]); +handle_volume(Client &client, unsigned argc, char *argv[]); CommandResult -handle_ping(Client &client, int argc, char *argv[]); +handle_stats(Client &client, unsigned argc, char *argv[]); CommandResult -handle_password(Client &client, int argc, char *argv[]); +handle_ping(Client &client, unsigned argc, char *argv[]); CommandResult -handle_config(Client &client, int argc, char *argv[]); +handle_password(Client &client, unsigned argc, char *argv[]); CommandResult -handle_idle(Client &client, int argc, char *argv[]); +handle_config(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_idle(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index e949448af..c69a0dd65 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.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,24 +19,21 @@ #include "config.h" #include "OutputCommands.hxx" -#include "OutputPrint.hxx" -#include "OutputCommand.hxx" +#include "output/OutputPrint.hxx" +#include "output/OutputCommand.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" - -#include <string.h> +#include "client/Client.hxx" +#include "Partition.hxx" CommandResult -handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) +handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned device; - bool ret; - if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - ret = audio_output_enable_index(device); - if (!ret) { + if (!audio_output_enable_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -46,16 +43,13 @@ handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) +handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned device; - bool ret; - if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - ret = audio_output_disable_index(device); - if (!ret) { + if (!audio_output_disable_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -65,13 +59,13 @@ handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) +handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned device; if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - if (!audio_output_toggle_index(device)) { + if (!audio_output_toggle_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -82,9 +76,9 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_devices(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { - printAudioDevices(client); + printAudioDevices(client, client.partition.outputs); return CommandResult::OK; } diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx index a5edc22e2..8d6be0511 100644 --- a/src/command/OutputCommands.hxx +++ b/src/command/OutputCommands.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 @@ -25,15 +25,15 @@ class Client; CommandResult -handle_enableoutput(Client &client, int argc, char *argv[]); +handle_enableoutput(Client &client, unsigned argc, char *argv[]); CommandResult -handle_disableoutput(Client &client, int argc, char *argv[]); +handle_disableoutput(Client &client, unsigned argc, char *argv[]); CommandResult -handle_toggleoutput(Client &client, int argc, char *argv[]); +handle_toggleoutput(Client &client, unsigned argc, char *argv[]); CommandResult -handle_devices(Client &client, int argc, char *argv[]); +handle_devices(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 12c71dfd4..f167b0edb 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.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 @@ -20,18 +20,21 @@ #include "config.h" #include "PlayerCommands.hxx" #include "CommandError.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" -#include "UpdateGlue.hxx" -#include "Client.hxx" -#include "Volume.hxx" -#include "OutputAll.hxx" +#include "client/Client.hxx" +#include "mixer/Volume.hxx" #include "Partition.hxx" +#include "Instance.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" #include "AudioFormat.hxx" #include "ReplayGainConfig.hxx" +#ifdef ENABLE_DATABASE +#include "db/update/Service.hxx" +#endif + #define COMMAND_STATUS_STATE "state" #define COMMAND_STATUS_REPEAT "repeat" #define COMMAND_STATUS_SINGLE "single" @@ -53,7 +56,7 @@ #define COMMAND_STATUS_UPDATING_DB "updating_db" CommandResult -handle_play(Client &client, int argc, char *argv[]) +handle_play(Client &client, unsigned argc, char *argv[]) { int song = -1; @@ -64,7 +67,7 @@ handle_play(Client &client, int argc, char *argv[]) } CommandResult -handle_playid(Client &client, int argc, char *argv[]) +handle_playid(Client &client, unsigned argc, char *argv[]) { int id = -1; @@ -77,7 +80,7 @@ handle_playid(Client &client, int argc, char *argv[]) CommandResult handle_stop(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.partition.Stop(); return CommandResult::OK; @@ -85,7 +88,7 @@ handle_stop(Client &client, CommandResult handle_currentsong(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { playlist_print_current(client, client.playlist); return CommandResult::OK; @@ -93,7 +96,7 @@ handle_currentsong(Client &client, CommandResult handle_pause(Client &client, - int argc, char *argv[]) + unsigned argc, char *argv[]) { if (argc == 2) { bool pause_flag; @@ -109,10 +112,9 @@ handle_pause(Client &client, CommandResult handle_status(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { const char *state = nullptr; - int updateJobId; int song; const auto player_status = client.player_control.GetStatus(); @@ -140,7 +142,7 @@ handle_status(Client &client, COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_MIXRAMPDB ": %f\n" COMMAND_STATUS_STATE ": %s\n", - volume_level_get(), + volume_level_get(client.partition.outputs), playlist.GetRepeat(), playlist.GetRandom(), playlist.GetSingle(), @@ -188,11 +190,17 @@ handle_status(Client &client, } } - if ((updateJobId = isUpdatingDB())) { +#ifdef ENABLE_DATABASE + const UpdateService *update_service = client.partition.instance.update; + unsigned updateJobId = update_service != nullptr + ? update_service->GetId() + : 0; + if (updateJobId != 0) { client_printf(client, COMMAND_STATUS_UPDATING_DB ": %i\n", updateJobId); } +#endif Error error = client.player_control.LockGetError(); if (error.IsDefined()) @@ -213,7 +221,7 @@ handle_status(Client &client, CommandResult handle_next(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { playlist &playlist = client.playlist; @@ -230,14 +238,14 @@ handle_next(Client &client, CommandResult handle_previous(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.partition.PlayPrevious(); return CommandResult::OK; } CommandResult -handle_repeat(Client &client, gcc_unused int argc, char *argv[]) +handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) @@ -248,7 +256,7 @@ handle_repeat(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_single(Client &client, gcc_unused int argc, char *argv[]) +handle_single(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) @@ -259,7 +267,7 @@ handle_single(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_consume(Client &client, gcc_unused int argc, char *argv[]) +handle_consume(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) @@ -270,27 +278,27 @@ handle_consume(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_random(Client &client, gcc_unused int argc, char *argv[]) +handle_random(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) return CommandResult::ERROR; client.partition.SetRandom(status); - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.partition.GetRandom())); + client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.partition.GetRandom())); return CommandResult::OK; } CommandResult handle_clearerror(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.player_control.ClearError(); return CommandResult::OK; } CommandResult -handle_seek(Client &client, gcc_unused int argc, char *argv[]) +handle_seek(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned song, seek_time; @@ -305,7 +313,7 @@ handle_seek(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_seekid(Client &client, gcc_unused int argc, char *argv[]) +handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id, seek_time; @@ -320,7 +328,7 @@ handle_seekid(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_seekcur(Client &client, gcc_unused int argc, char *argv[]) +handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[]) { const char *p = argv[1]; bool relative = *p == '+' || *p == '-'; @@ -334,7 +342,7 @@ handle_seekcur(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_crossfade(Client &client, gcc_unused int argc, char *argv[]) +handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned xfade_time; @@ -346,7 +354,7 @@ handle_crossfade(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[]) +handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[]) { float db; @@ -358,7 +366,7 @@ handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[]) +handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[]) { float delay_secs; @@ -371,7 +379,7 @@ handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_replay_gain_mode(Client &client, - gcc_unused int argc, char *argv[]) + gcc_unused unsigned argc, char *argv[]) { if (!replay_gain_set_mode_string(argv[1])) { command_error(client, ACK_ERROR_ARG, @@ -379,14 +387,13 @@ handle_replay_gain_mode(Client &client, return CommandResult::ERROR; } - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.playlist.queue.random)); - + client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.playlist.queue.random)); return CommandResult::OK; } CommandResult handle_replay_gain_status(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client_printf(client, "replay_gain_mode: %s\n", replay_gain_get_mode_string()); diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx index 8dad0855b..da7083f1e 100644 --- a/src/command/PlayerCommands.hxx +++ b/src/command/PlayerCommands.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 @@ -25,66 +25,66 @@ class Client; CommandResult -handle_play(Client &client, int argc, char *argv[]); +handle_play(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playid(Client &client, int argc, char *argv[]); +handle_playid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_stop(Client &client, int argc, char *argv[]); +handle_stop(Client &client, unsigned argc, char *argv[]); CommandResult -handle_currentsong(Client &client, int argc, char *argv[]); +handle_currentsong(Client &client, unsigned argc, char *argv[]); CommandResult -handle_pause(Client &client, int argc, char *argv[]); +handle_pause(Client &client, unsigned argc, char *argv[]); CommandResult -handle_status(Client &client, int argc, char *argv[]); +handle_status(Client &client, unsigned argc, char *argv[]); CommandResult -handle_next(Client &client, int argc, char *argv[]); +handle_next(Client &client, unsigned argc, char *argv[]); CommandResult -handle_previous(Client &client, int argc, char *avg[]); +handle_previous(Client &client, unsigned argc, char *avg[]); CommandResult -handle_repeat(Client &client, int argc, char *argv[]); +handle_repeat(Client &client, unsigned argc, char *argv[]); CommandResult -handle_single(Client &client, int argc, char *argv[]); +handle_single(Client &client, unsigned argc, char *argv[]); CommandResult -handle_consume(Client &client, int argc, char *argv[]); +handle_consume(Client &client, unsigned argc, char *argv[]); CommandResult -handle_random(Client &client, int argc, char *argv[]); +handle_random(Client &client, unsigned argc, char *argv[]); CommandResult -handle_clearerror(Client &client, int argc, char *argv[]); +handle_clearerror(Client &client, unsigned argc, char *argv[]); CommandResult -handle_seek(Client &client, int argc, char *argv[]); +handle_seek(Client &client, unsigned argc, char *argv[]); CommandResult -handle_seekid(Client &client, int argc, char *argv[]); +handle_seekid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_seekcur(Client &client, int argc, char *argv[]); +handle_seekcur(Client &client, unsigned argc, char *argv[]); CommandResult -handle_crossfade(Client &client, int argc, char *argv[]); +handle_crossfade(Client &client, unsigned argc, char *argv[]); CommandResult -handle_mixrampdb(Client &client, int argc, char *argv[]); +handle_mixrampdb(Client &client, unsigned argc, char *argv[]); CommandResult -handle_mixrampdelay(Client &client, int argc, char *argv[]); +handle_mixrampdelay(Client &client, unsigned argc, char *argv[]); CommandResult -handle_replay_gain_mode(Client &client, int argc, char *argv[]); +handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]); CommandResult -handle_replay_gain_status(Client &client, int argc, char *argv[]); +handle_replay_gain_status(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index c4441293e..c2b18064c 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.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,26 +19,25 @@ #include "config.h" #include "PlaylistCommands.hxx" -#include "DatabasePlaylist.hxx" +#include "db/DatabasePlaylist.hxx" #include "CommandError.hxx" #include "PlaylistPrint.hxx" #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" -#include "PlaylistVector.hxx" -#include "PlaylistQueue.hxx" +#include "db/PlaylistVector.hxx" +#include "SongLoader.hxx" #include "BulkEdit.hxx" +#include "playlist/PlaylistQueue.hxx" +#include "playlist/Print.hxx" +#include "queue/Playlist.hxx" #include "TimePrint.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" -#include "Playlist.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include <assert.h> -#include <stdlib.h> - static void print_spl_list(Client &client, const PlaylistVector &list) { @@ -51,14 +50,14 @@ print_spl_list(Client &client, const PlaylistVector &list) } CommandResult -handle_save(Client &client, gcc_unused int argc, char *argv[]) +handle_save(Client &client, gcc_unused unsigned argc, char *argv[]) { PlaylistResult result = spl_save_playlist(argv[1], client.playlist); return print_playlist_result(client, result); } CommandResult -handle_load(Client &client, int argc, char *argv[]) +handle_load(Client &client, unsigned argc, char *argv[]) { unsigned start_index, end_index; @@ -70,36 +69,19 @@ handle_load(Client &client, int argc, char *argv[]) const ScopeBulkEdit bulk_edit(client.partition); - const PlaylistResult result = - playlist_open_into_queue(argv[1], - start_index, end_index, - client.playlist, - client.player_control, true); - if (result != PlaylistResult::NO_SUCH_LIST) - return print_playlist_result(client, result); - Error error; - if (playlist_load_spl(client.playlist, client.player_control, - argv[1], start_index, end_index, - error)) - return CommandResult::OK; - - if (error.IsDomain(playlist_domain) && - PlaylistResult(error.GetCode()) == PlaylistResult::BAD_NAME) { - /* the message for BAD_NAME is confusing when the - client wants to load a playlist file from the music - directory; patch the Error object to show "no such - playlist" instead */ - Error error2(playlist_domain, int(PlaylistResult::NO_SUCH_LIST), - error.GetMessage()); - error = std::move(error2); - } + const SongLoader loader(client); + if (!playlist_open_into_queue(argv[1], + start_index, end_index, + client.playlist, + client.player_control, loader, error)) + return print_error(client, error); - return print_error(client, error); + return CommandResult::OK; } CommandResult -handle_listplaylist(Client &client, gcc_unused int argc, char *argv[]) +handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[]) { if (playlist_file_print(client, argv[1], false)) return CommandResult::OK; @@ -112,7 +94,7 @@ handle_listplaylist(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_listplaylistinfo(Client &client, - gcc_unused int argc, char *argv[]) + gcc_unused unsigned argc, char *argv[]) { if (playlist_file_print(client, argv[1], true)) return CommandResult::OK; @@ -124,7 +106,7 @@ handle_listplaylistinfo(Client &client, } CommandResult -handle_rm(Client &client, gcc_unused int argc, char *argv[]) +handle_rm(Client &client, gcc_unused unsigned argc, char *argv[]) { Error error; return spl_delete(argv[1], error) @@ -133,7 +115,7 @@ handle_rm(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_rename(Client &client, gcc_unused int argc, char *argv[]) +handle_rename(Client &client, gcc_unused unsigned argc, char *argv[]) { Error error; return spl_rename(argv[1], argv[2], error) @@ -143,7 +125,7 @@ handle_rename(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_playlistdelete(Client &client, - gcc_unused int argc, char *argv[]) { + gcc_unused unsigned argc, char *argv[]) { char *playlist = argv[1]; unsigned from; @@ -157,7 +139,7 @@ handle_playlistdelete(Client &client, } CommandResult -handle_playlistmove(Client &client, gcc_unused int argc, char *argv[]) +handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[]) { char *playlist = argv[1]; unsigned from, to; @@ -174,7 +156,7 @@ handle_playlistmove(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_playlistclear(Client &client, gcc_unused int argc, char *argv[]) +handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[]) { Error error; return spl_clear(argv[1], error) @@ -183,7 +165,7 @@ handle_playlistclear(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) +handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) { char *playlist = argv[1]; char *uri = argv[2]; @@ -191,16 +173,21 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) bool success; Error error; if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return CommandResult::ERROR; - } - - success = spl_append_uri(uri, playlist, error); - } else - success = search_add_to_playlist(uri, playlist, nullptr, + const SongLoader loader(client); + success = spl_append_uri(playlist, loader, uri, error); + } else { +#ifdef ENABLE_DATABASE + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return print_error(client, error); + + success = search_add_to_playlist(*db, *client.GetStorage(), + uri, playlist, nullptr, error); +#else + success = false; +#endif + } if (!success && !error.IsDefined()) { command_error(client, ACK_ERROR_NO_EXIST, @@ -213,7 +200,7 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_listplaylists(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { Error error; const auto list = ListPlaylistFiles(error); diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx index 802d6ff2f..fba4e1318 100644 --- a/src/command/PlaylistCommands.hxx +++ b/src/command/PlaylistCommands.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 @@ -25,36 +25,36 @@ class Client; CommandResult -handle_save(Client &client, int argc, char *argv[]); +handle_save(Client &client, unsigned argc, char *argv[]); CommandResult -handle_load(Client &client, int argc, char *argv[]); +handle_load(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listplaylist(Client &client, int argc, char *argv[]); +handle_listplaylist(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listplaylistinfo(Client &client, int argc, char *argv[]); +handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]); CommandResult -handle_rm(Client &client, int argc, char *argv[]); +handle_rm(Client &client, unsigned argc, char *argv[]); CommandResult -handle_rename(Client &client, int argc, char *argv[]); +handle_rename(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistdelete(Client &client, int argc, char *argv[]); +handle_playlistdelete(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistmove(Client &client, int argc, char *argv[]); +handle_playlistmove(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistclear(Client &client, int argc, char *argv[]); +handle_playlistclear(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistadd(Client &client, int argc, char *argv[]); +handle_playlistadd(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listplaylists(Client &client, int argc, char *argv[]); +handle_listplaylists(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index a987e1bc9..c99a6687a 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.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 @@ -20,19 +20,21 @@ #include "config.h" #include "QueueCommands.hxx" #include "CommandError.hxx" -#include "DatabaseQueue.hxx" +#include "db/DatabaseQueue.hxx" +#include "db/Selection.hxx" #include "SongFilter.hxx" -#include "DatabaseSelection.hxx" -#include "Playlist.hxx" +#include "SongLoader.hxx" +#include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "Partition.hxx" #include "BulkEdit.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" +#include "util/ConstBuffer.hxx" #include "util/UriUtil.hxx" +#include "util/NumberParser.hxx" #include "util/Error.hxx" #include "fs/AllocatedPath.hxx" @@ -40,40 +42,40 @@ #include <string.h> -CommandResult -handle_add(Client &client, gcc_unused int argc, char *argv[]) +static const char * +translate_uri(Client &client, const char *uri) { - char *uri = argv[1]; + if (memcmp(uri, "file:///", 8) == 0) + /* drop the "file://", leave only an absolute path + (starting with a slash) */ + return uri + 7; + + if (PathTraitsUTF8::IsAbsolute(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, "Malformed URI"); + return nullptr; + } - if (memcmp(uri, "file:///", 8) == 0) { - const char *path_utf8 = uri + 7; - const auto path_fs = AllocatedPath::FromUTF8(path_utf8); + return uri; +} - if (path_fs.IsNull()) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported file name"); - return CommandResult::ERROR; - } +CommandResult +handle_add(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + const char *const uri = translate_uri(client, argv[1]); + if (uri == nullptr) + return CommandResult::ERROR; + if (uri_has_scheme(uri) || PathTraitsUTF8::IsAbsolute(uri)) { + const SongLoader loader(client); Error error; - if (!client_allow_file(client, path_fs, error)) + unsigned id = client.partition.AppendURI(loader, uri, error); + if (id == 0) return print_error(client, error); - auto result = client.partition.AppendFile(path_utf8); - return print_playlist_result(client, result); - } - - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return CommandResult::ERROR; - } - - auto result = client.partition.AppendURI(uri); - return print_playlist_result(client, result); + return CommandResult::OK; } +#ifdef ENABLE_DATABASE const ScopeBulkEdit bulk_edit(client.partition); const DatabaseSelection selection(uri, true); @@ -81,48 +83,30 @@ handle_add(Client &client, gcc_unused int argc, char *argv[]) return AddFromDatabase(client.partition, selection, error) ? CommandResult::OK : print_error(client, error); +#else + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif } CommandResult -handle_addid(Client &client, int argc, char *argv[]) +handle_addid(Client &client, unsigned argc, char *argv[]) { - char *uri = argv[1]; - unsigned added_id; - PlaylistResult result; - - if (memcmp(uri, "file:///", 8) == 0) { - const char *path_utf8 = uri + 7; - const auto path_fs = AllocatedPath::FromUTF8(path_utf8); - - if (path_fs.IsNull()) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported file name"); - return CommandResult::ERROR; - } - - Error error; - if (!client_allow_file(client, path_fs, error)) - return print_error(client, error); - - result = client.partition.AppendFile(path_utf8, &added_id); - } else { - if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return CommandResult::ERROR; - } - - result = client.partition.AppendURI(uri, &added_id); - } + const char *const uri = translate_uri(client, argv[1]); + if (uri == nullptr) + return CommandResult::ERROR; - if (result != PlaylistResult::SUCCESS) - return print_playlist_result(client, result); + const SongLoader loader(client); + Error error; + unsigned added_id = client.partition.AppendURI(loader, uri, error); + if (added_id == 0) + return print_error(client, error); if (argc == 3) { unsigned to; if (!check_unsigned(client, &to, argv[2])) return CommandResult::ERROR; - result = client.partition.MoveId(added_id, to); + PlaylistResult result = client.partition.MoveId(added_id, to); if (result != PlaylistResult::SUCCESS) { CommandResult ret = print_playlist_result(client, result); @@ -135,8 +119,61 @@ handle_addid(Client &client, int argc, char *argv[]) return CommandResult::OK; } +/** + * Parse a string in the form "START:END", both being (optional) + * fractional non-negative time offsets in seconds. Returns both in + * integer milliseconds. Omitted values are zero. + */ +static bool +parse_time_range(const char *p, unsigned &start_ms, unsigned &end_ms) +{ + char *endptr; + + const float start = ParseFloat(p, &endptr); + if (*endptr != ':' || start < 0) + return false; + + start_ms = endptr > p + ? unsigned(start * 1000u) + : 0u; + + p = endptr + 1; + + const float end = ParseFloat(p, &endptr); + if (*endptr != 0 || end < 0) + return false; + + end_ms = endptr > p + ? unsigned(end * 1000u) + : 0u; + + return end_ms == 0 || end_ms > start_ms; +} + +CommandResult +handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + + unsigned start_ms, end_ms; + if (!parse_time_range(argv[2], start_ms, end_ms)) { + command_error(client, ACK_ERROR_ARG, "Bad range"); + return CommandResult::ERROR; + } + + Error error; + if (!client.partition.playlist.SetSongIdRange(client.partition.pc, + id, start_ms, end_ms, + error)) + return print_error(client, error); + + return CommandResult::OK; +} + CommandResult -handle_delete(Client &client, gcc_unused int argc, char *argv[]) +handle_delete(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned start, end; @@ -148,7 +185,7 @@ handle_delete(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_deleteid(Client &client, gcc_unused int argc, char *argv[]) +handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id; @@ -161,7 +198,7 @@ handle_deleteid(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_playlist(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { playlist_print_uris(client, client.playlist); return CommandResult::OK; @@ -169,7 +206,7 @@ handle_playlist(Client &client, CommandResult handle_shuffle(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { unsigned start = 0, end = client.playlist.queue.GetLength(); if (argc == 2 && !check_range(client, &start, &end, argv[1])) @@ -181,14 +218,14 @@ handle_shuffle(gcc_unused Client &client, CommandResult handle_clear(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.partition.ClearQueue(); return CommandResult::OK; } CommandResult -handle_plchanges(Client &client, gcc_unused int argc, char *argv[]) +handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[]) { uint32_t version; @@ -200,7 +237,7 @@ handle_plchanges(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[]) +handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[]) { uint32_t version; @@ -212,7 +249,7 @@ handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_playlistinfo(Client &client, int argc, char *argv[]) +handle_playlistinfo(Client &client, unsigned argc, char *argv[]) { unsigned start = 0, end = std::numeric_limits<unsigned>::max(); bool ret; @@ -229,7 +266,7 @@ handle_playlistinfo(Client &client, int argc, char *argv[]) } CommandResult -handle_playlistid(Client &client, int argc, char *argv[]) +handle_playlistid(Client &client, unsigned argc, char *argv[]) { if (argc >= 2) { unsigned id; @@ -249,11 +286,13 @@ handle_playlistid(Client &client, int argc, char *argv[]) } static CommandResult -handle_playlist_match(Client &client, int argc, char *argv[], +handle_playlist_match(Client &client, unsigned argc, char *argv[], bool fold_case) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -263,19 +302,19 @@ handle_playlist_match(Client &client, int argc, char *argv[], } CommandResult -handle_playlistfind(Client &client, int argc, char *argv[]) +handle_playlistfind(Client &client, unsigned argc, char *argv[]) { return handle_playlist_match(client, argc, argv, false); } CommandResult -handle_playlistsearch(Client &client, int argc, char *argv[]) +handle_playlistsearch(Client &client, unsigned argc, char *argv[]) { return handle_playlist_match(client, argc, argv, true); } CommandResult -handle_prio(Client &client, int argc, char *argv[]) +handle_prio(Client &client, unsigned argc, char *argv[]) { unsigned priority; @@ -288,7 +327,7 @@ handle_prio(Client &client, int argc, char *argv[]) return CommandResult::ERROR; } - for (int i = 2; i < argc; ++i) { + for (unsigned i = 2; i < argc; ++i) { unsigned start_position, end_position; if (!check_range(client, &start_position, &end_position, argv[i])) @@ -306,7 +345,7 @@ handle_prio(Client &client, int argc, char *argv[]) } CommandResult -handle_prioid(Client &client, int argc, char *argv[]) +handle_prioid(Client &client, unsigned argc, char *argv[]) { unsigned priority; @@ -319,7 +358,7 @@ handle_prioid(Client &client, int argc, char *argv[]) return CommandResult::ERROR; } - for (int i = 2; i < argc; ++i) { + for (unsigned i = 2; i < argc; ++i) { unsigned song_id; if (!check_unsigned(client, &song_id, argv[i])) return CommandResult::ERROR; @@ -334,7 +373,7 @@ handle_prioid(Client &client, int argc, char *argv[]) } CommandResult -handle_move(Client &client, gcc_unused int argc, char *argv[]) +handle_move(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned start, end; int to; @@ -350,7 +389,7 @@ handle_move(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_moveid(Client &client, gcc_unused int argc, char *argv[]) +handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id; int to; @@ -364,7 +403,7 @@ handle_moveid(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_swap(Client &client, gcc_unused int argc, char *argv[]) +handle_swap(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned song1, song2; @@ -379,7 +418,7 @@ handle_swap(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_swapid(Client &client, gcc_unused int argc, char *argv[]) +handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id1, id2; diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx index 90d744447..f98f7bad2 100644 --- a/src/command/QueueCommands.hxx +++ b/src/command/QueueCommands.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 @@ -25,60 +25,63 @@ class Client; CommandResult -handle_add(Client &client, int argc, char *argv[]); +handle_add(Client &client, unsigned argc, char *argv[]); CommandResult -handle_addid(Client &client, int argc, char *argv[]); +handle_addid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_delete(Client &client, int argc, char *argv[]); +handle_rangeid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_deleteid(Client &client, int argc, char *argv[]); +handle_delete(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlist(Client &client, int argc, char *argv[]); +handle_deleteid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_shuffle(Client &client, int argc, char *argv[]); +handle_playlist(Client &client, unsigned argc, char *argv[]); CommandResult -handle_clear(Client &client, int argc, char *argv[]); +handle_shuffle(Client &client, unsigned argc, char *argv[]); CommandResult -handle_plchanges(Client &client, int argc, char *argv[]); +handle_clear(Client &client, unsigned argc, char *argv[]); CommandResult -handle_plchangesposid(Client &client, int argc, char *argv[]); +handle_plchanges(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistinfo(Client &client, int argc, char *argv[]); +handle_plchangesposid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistid(Client &client, int argc, char *argv[]); +handle_playlistinfo(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistfind(Client &client, int argc, char *argv[]); +handle_playlistid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistsearch(Client &client, int argc, char *argv[]); +handle_playlistfind(Client &client, unsigned argc, char *argv[]); CommandResult -handle_prio(Client &client, int argc, char *argv[]); +handle_playlistsearch(Client &client, unsigned argc, char *argv[]); CommandResult -handle_prioid(Client &client, int argc, char *argv[]); +handle_prio(Client &client, unsigned argc, char *argv[]); CommandResult -handle_move(Client &client, int argc, char *argv[]); +handle_prioid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_moveid(Client &client, int argc, char *argv[]); +handle_move(Client &client, unsigned argc, char *argv[]); CommandResult -handle_swap(Client &client, int argc, char *argv[]); +handle_moveid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_swapid(Client &client, int argc, char *argv[]); +handle_swap(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_swapid(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index b65e6f607..37506d51b 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.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 @@ -20,19 +20,18 @@ #include "config.h" #include "StickerCommands.hxx" #include "SongPrint.hxx" -#include "DatabaseLock.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseGlue.hxx" -#include "DatabaseSimple.hxx" -#include "SongSticker.hxx" -#include "StickerPrint.hxx" -#include "StickerDatabase.hxx" +#include "db/Interface.hxx" +#include "db/DatabaseGlue.hxx" +#include "sticker/SongSticker.hxx" +#include "sticker/StickerPrint.hxx" +#include "sticker/StickerDatabase.hxx" #include "CommandError.hxx" #include "protocol/Result.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" #include "util/Error.hxx" -#include <glib.h> - #include <string.h> struct sticker_song_find_data { @@ -41,7 +40,7 @@ struct sticker_song_find_data { }; static void -sticker_song_find_print_cb(Song &song, const char *value, +sticker_song_find_print_cb(const LightSong &song, const char *value, void *user_data) { struct sticker_song_find_data *data = @@ -52,20 +51,20 @@ sticker_song_find_print_cb(Song &song, const char *value, } static CommandResult -handle_sticker_song(Client &client, int argc, char *argv[]) +handle_sticker_song(Client &client, unsigned argc, char *argv[]) { Error error; - const Database *db = GetDatabase(error); + const Database *db = client.GetDatabase(error); if (db == nullptr) return print_error(client, error); /* get song song_id key */ if (argc == 5 && strcmp(argv[1], "get") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); - const auto value = sticker_song_get_value(song, argv[4]); + const auto value = sticker_song_get_value(*song, argv[4]); db->ReturnSong(song); if (value.empty()) { command_error(client, ACK_ERROR_NO_EXIST, @@ -78,11 +77,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) return CommandResult::OK; /* list song song_id */ } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); - sticker *sticker = sticker_song_get(song); + sticker *sticker = sticker_song_get(*song); db->ReturnSong(song); if (sticker) { sticker_print(client, *sticker); @@ -92,11 +91,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) return CommandResult::OK; /* set song song_id id key */ } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); - bool ret = sticker_song_set_value(song, argv[4], argv[5]); + bool ret = sticker_song_set_value(*song, argv[4], argv[5]); db->ReturnSong(song); if (!ret) { command_error(client, ACK_ERROR_SYSTEM, @@ -108,13 +107,13 @@ handle_sticker_song(Client &client, int argc, char *argv[]) /* delete song song_id [key] */ } else if ((argc == 4 || argc == 5) && strcmp(argv[1], "delete") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); bool ret = argc == 4 - ? sticker_song_delete(song) - : sticker_song_delete_value(song, argv[4]); + ? sticker_song_delete(*song) + : sticker_song_delete_value(*song, argv[4]); db->ReturnSong(song); if (!ret) { command_error(client, ACK_ERROR_SYSTEM, @@ -126,24 +125,17 @@ handle_sticker_song(Client &client, int argc, char *argv[]) /* find song dir key */ } else if (argc == 5 && strcmp(argv[1], "find") == 0) { /* "sticker find song a/directory name" */ + + const char *const base_uri = argv[3]; + bool success; struct sticker_song_find_data data = { client, argv[4], }; - db_lock(); - Directory *directory = db_get_directory(argv[3]); - if (directory == nullptr) { - db_unlock(); - command_error(client, ACK_ERROR_NO_EXIST, - "no such directory"); - return CommandResult::ERROR; - } - - success = sticker_song_find(*directory, data.name, + success = sticker_song_find(*db, base_uri, data.name, sticker_song_find_print_cb, &data); - db_unlock(); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "failed to set search sticker database"); @@ -158,7 +150,7 @@ handle_sticker_song(Client &client, int argc, char *argv[]) } CommandResult -handle_sticker(Client &client, int argc, char *argv[]) +handle_sticker(Client &client, unsigned argc, char *argv[]) { assert(argc >= 4); diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx index 9e4380f37..cf46cd034 100644 --- a/src/command/StickerCommands.hxx +++ b/src/command/StickerCommands.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 @@ -25,6 +25,6 @@ class Client; CommandResult -handle_sticker(Client &client, int argc, char *argv[]); +handle_sticker(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx new file mode 100644 index 000000000..aeec73e1c --- /dev/null +++ b/src/command/StorageCommands.cxx @@ -0,0 +1,295 @@ +/* + * 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. + */ + +#define __STDC_FORMAT_MACROS /* for PRIu64 */ + +#include "config.h" +#include "StorageCommands.hxx" +#include "CommandError.hxx" +#include "protocol/Result.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/Traits.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "storage/Registry.hxx" +#include "storage/CompositeStorage.hxx" +#include "storage/FileInfo.hxx" +#include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "db/update/Service.hxx" +#include "TimePrint.hxx" +#include "Idle.hxx" + +#include <inttypes.h> /* for PRIu64 */ + +gcc_pure +static bool +skip_path(const char *name_utf8) +{ + return strchr(name_utf8, '\n') != nullptr; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +/* PRIu64 causes bogus compiler warning */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +#endif + +static bool +handle_listfiles_storage(Client &client, StorageDirectoryReader &reader, + Error &error) +{ + const char *name_utf8; + while ((name_utf8 = reader.Read()) != nullptr) { + if (skip_path(name_utf8)) + continue; + + FileInfo info; + if (!reader.GetInfo(false, info, error)) + continue; + + switch (info.type) { + case FileInfo::Type::OTHER: + /* ignore */ + continue; + + case FileInfo::Type::REGULAR: + client_printf(client, "file: %s\n" + "size: %" PRIu64 "\n", + name_utf8, + info.size); + break; + + case FileInfo::Type::DIRECTORY: + client_printf(client, "directory: %s\n", name_utf8); + break; + } + + if (info.mtime != 0) + time_print(client, "Last-Modified", info.mtime); + } + + return true; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif + +static bool +handle_listfiles_storage(Client &client, Storage &storage, const char *uri, + Error &error) +{ + auto reader = storage.OpenDirectory(uri, error); + if (reader == nullptr) + return false; + + bool success = handle_listfiles_storage(client, *reader, error); + delete reader; + return success; +} + +CommandResult +handle_listfiles_storage(Client &client, Storage &storage, const char *uri) +{ + Error error; + if (!handle_listfiles_storage(client, storage, uri, error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_listfiles_storage(Client &client, const char *uri) +{ + Error error; + Storage *storage = CreateStorageURI(uri, error); + if (storage == nullptr) { + if (error.IsDefined()) + return print_error(client, error); + + command_error(client, ACK_ERROR_ARG, + "Unrecognized storage URI"); + return CommandResult::ERROR; + } + + bool success = handle_listfiles_storage(client, *storage, "", error); + delete storage; + if (!success) + return print_error(client, error); + + return CommandResult::OK; +} + +static void +print_storage_uri(Client &client, const Storage &storage) +{ + std::string uri = storage.MapUTF8(""); + if (uri.empty()) + return; + + if (PathTraitsFS::IsAbsolute(uri.c_str())) { + /* storage points to local directory */ + + if (!client.IsLocal()) + /* only "local" clients may see local paths + (same policy as with the "config" + command) */ + return; + } else { + /* hide username/passwords from client */ + + std::string allocated = uri_remove_auth(uri.c_str()); + if (!allocated.empty()) + uri = std::move(allocated); + } + + client_printf(client, "storage: %s\n", uri.c_str()); +} + +CommandResult +handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[]) +{ + Storage *_composite = client.partition.instance.storage; + if (_composite == nullptr) { + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; + } + + CompositeStorage &composite = *(CompositeStorage *)_composite; + + const auto visitor = [&client](const char *mount_uri, + const Storage &storage){ + client_printf(client, "mount: %s\n", mount_uri); + print_storage_uri(client, storage); + }; + + composite.VisitMounts(visitor); + + return CommandResult::OK; +} + +CommandResult +handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + Storage *_composite = client.partition.instance.storage; + if (_composite == nullptr) { + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; + } + + CompositeStorage &composite = *(CompositeStorage *)_composite; + + const char *const local_uri = argv[1]; + const char *const remote_uri = argv[2]; + + if (*local_uri == 0) { + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + + if (strchr(local_uri, '/') != nullptr) { + /* allow only top-level mounts for now */ + /* TODO: eliminate this limitation after ensuring that + UpdateQueue::Erase() really gets called for every + unmount, and no Directory disappears recursively + during database update */ + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + + Error error; + Storage *storage = CreateStorageURI(remote_uri, error); + if (storage == nullptr) { + if (error.IsDefined()) + return print_error(client, error); + + command_error(client, ACK_ERROR_ARG, + "Unrecognized storage URI"); + return CommandResult::ERROR; + } + + composite.Mount(local_uri, storage); + idle_add(IDLE_MOUNT); + +#ifdef ENABLE_DATABASE + Database *_db = client.partition.instance.database; + if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) { + SimpleDatabase &db = *(SimpleDatabase *)_db; + + if (!db.Mount(local_uri, remote_uri, error)) { + composite.Unmount(local_uri); + return print_error(client, error); + } + + // TODO: call Instance::OnDatabaseModified()? + // TODO: trigger database update? + idle_add(IDLE_DATABASE); + } +#endif + + return CommandResult::OK; +} + +CommandResult +handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + Storage *_composite = client.partition.instance.storage; + if (_composite == nullptr) { + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; + } + + CompositeStorage &composite = *(CompositeStorage *)_composite; + + const char *const local_uri = argv[1]; + + if (*local_uri == 0) { + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + +#ifdef ENABLE_DATABASE + if (client.partition.instance.update != nullptr) + /* ensure that no database update will attempt to work + with the database/storage instances we're about to + destroy here */ + client.partition.instance.update->CancelMount(local_uri); + + Database *_db = client.partition.instance.database; + if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) { + SimpleDatabase &db = *(SimpleDatabase *)_db; + + if (db.Unmount(local_uri)) + // TODO: call Instance::OnDatabaseModified()? + idle_add(IDLE_DATABASE); + } +#endif + + if (!composite.Unmount(local_uri)) { + command_error(client, ACK_ERROR_ARG, "Not a mount point"); + return CommandResult::ERROR; + } + + idle_add(IDLE_MOUNT); + + return CommandResult::OK; +} diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx new file mode 100644 index 000000000..a3636d54a --- /dev/null +++ b/src/command/StorageCommands.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_STORAGE_COMMANDS_HXX +#define MPD_STORAGE_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; +class Storage; + +CommandResult +handle_listfiles_storage(Client &client, Storage &storage, const char *uri); + +CommandResult +handle_listfiles_storage(Client &client, const char *uri); + +CommandResult +handle_listmounts(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_mount(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_unmount(Client &client, unsigned argc, char *argv[]); + +#endif diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx new file mode 100644 index 000000000..2d537671c --- /dev/null +++ b/src/command/TagCommands.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 "TagCommands.hxx" +#include "CommandError.hxx" +#include "client/Client.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "tag/Tag.hxx" +#include "Partition.hxx" + +CommandResult +handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[1])) + return CommandResult::ERROR; + + const char *const tag_name = argv[2]; + const TagType tag_type = tag_name_parse_i(tag_name); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", tag_name); + return CommandResult::ERROR; + } + + const char *const value = argv[3]; + + Error error; + if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value, + error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_cleartagid(Client &client, unsigned argc, char *argv[]) +{ + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[1])) + return CommandResult::ERROR; + + TagType tag_type = TAG_NUM_OF_ITEM_TYPES; + if (argc >= 3) { + const char *const tag_name = argv[2]; + tag_type = tag_name_parse_i(tag_name); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", tag_name); + return CommandResult::ERROR; + } + } + + Error error; + if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type, + error)) + return print_error(client, error); + + return CommandResult::OK; +} diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx new file mode 100644 index 000000000..748838e68 --- /dev/null +++ b/src/command/TagCommands.hxx @@ -0,0 +1,33 @@ +/* + * 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_TAG_COMMANDS_HXX +#define MPD_TAG_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_addtagid(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_cleartagid(Client &client, unsigned argc, char *argv[]); + +#endif diff --git a/src/config/ConfigData.cxx b/src/config/ConfigData.cxx new file mode 100644 index 000000000..70e1e55ed --- /dev/null +++ b/src/config/ConfigData.cxx @@ -0,0 +1,159 @@ +/* + * 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 "ConfigData.hxx" +#include "ConfigParser.hxx" +#include "ConfigPath.hxx" +#include "util/Error.hxx" +#include "fs/AllocatedPath.hxx" +#include "system/FatalError.hxx" + +#include <assert.h> +#include <stdlib.h> + +int +block_param::GetIntValue() const +{ + char *endptr; + long value2 = strtol(value.c_str(), &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid number in line %i", line); + + return value2; +} + +unsigned +block_param::GetUnsignedValue() const +{ + char *endptr; + unsigned long value2 = strtoul(value.c_str(), &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid number in line %i", line); + + return (unsigned)value2; +} + +bool +block_param::GetBoolValue() const +{ + bool value2; + if (!get_bool(value.c_str(), &value2)) + FormatFatalError("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name.c_str(), line); + + return value2; +} + +config_param::config_param(const char *_value, int _line) + :next(nullptr), value(_value), line(_line), used(false) {} + +config_param::~config_param() +{ + delete next; +} + +const block_param * +config_param::GetBlockParam(const char *name) const +{ + for (const auto &i : block_params) { + if (i.name == name) { + i.used = true; + return &i; + } + } + + return NULL; +} + +const char * +config_param::GetBlockValue(const char *name, const char *default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == nullptr) + return default_value; + + return bp->value.c_str(); +} + +AllocatedPath +config_param::GetBlockPath(const char *name, const char *default_value, + Error &error) const +{ + assert(!error.IsDefined()); + + int line2 = line; + const char *s; + + const block_param *bp = GetBlockParam(name); + if (bp != nullptr) { + line2 = bp->line; + s = bp->value.c_str(); + } else { + if (default_value == nullptr) + return AllocatedPath::Null(); + + s = default_value; + } + + AllocatedPath path = ParsePath(s, error); + if (gcc_unlikely(path.IsNull())) + error.FormatPrefix("Invalid path in \"%s\" at line %i: ", + name, line2); + + return path; +} + +AllocatedPath +config_param::GetBlockPath(const char *name, Error &error) const +{ + return GetBlockPath(name, nullptr, error); +} + +int +config_param::GetBlockValue(const char *name, int default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == nullptr) + return default_value; + + return bp->GetIntValue(); +} + +unsigned +config_param::GetBlockValue(const char *name, unsigned default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == nullptr) + return default_value; + + return bp->GetUnsignedValue(); +} + +gcc_pure +bool +config_param::GetBlockValue(const char *name, bool default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == NULL) + return default_value; + + return bp->GetBoolValue(); +} diff --git a/src/config/ConfigData.hxx b/src/config/ConfigData.hxx new file mode 100644 index 000000000..e42d674ba --- /dev/null +++ b/src/config/ConfigData.hxx @@ -0,0 +1,134 @@ +/* + * 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_CONFIG_DATA_HXX +#define MPD_CONFIG_DATA_HXX + +#include "ConfigOption.hxx" +#include "Compiler.h" + +#include <string> +#include <array> +#include <vector> + +class AllocatedPath; +class Error; + +struct block_param { + std::string name; + std::string value; + int line; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + mutable bool used; + + gcc_nonnull_all + block_param(const char *_name, const char *_value, int _line=-1) + :name(_name), value(_value), line(_line), used(false) {} + + gcc_pure + int GetIntValue() const; + + gcc_pure + unsigned GetUnsignedValue() const; + + gcc_pure + bool GetBoolValue() const; +}; + +struct config_param { + /** + * The next config_param with the same name. The destructor + * deletes the whole chain. + */ + struct config_param *next; + + std::string value; + + unsigned int line; + + std::vector<block_param> block_params; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; + + config_param(int _line=-1) + :next(nullptr), line(_line), used(false) {} + + gcc_nonnull_all + config_param(const char *_value, int _line=-1); + + config_param(const config_param &) = delete; + + ~config_param(); + + config_param &operator=(const config_param &) = delete; + + /** + * Determine if this is a "null" instance, i.e. an empty + * object that was synthesized and not loaded from a + * configuration file. + */ + bool IsNull() const { + return line == unsigned(-1); + } + + gcc_nonnull_all + void AddBlockParam(const char *_name, const char *_value, + int _line=-1) { + block_params.emplace_back(_name, _value, _line); + } + + gcc_nonnull_all gcc_pure + const block_param *GetBlockParam(const char *_name) const; + + gcc_pure + const char *GetBlockValue(const char *name, + const char *default_value=nullptr) const; + + /** + * Same as config_dup_path(), but looks up the setting in the + * specified block. + */ + AllocatedPath GetBlockPath(const char *name, const char *default_value, + Error &error) const; + + AllocatedPath GetBlockPath(const char *name, Error &error) const; + + gcc_pure + int GetBlockValue(const char *name, int default_value) const; + + gcc_pure + unsigned GetBlockValue(const char *name, unsigned default_value) const; + + gcc_pure + bool GetBlockValue(const char *name, bool default_value) const; +}; + +struct ConfigData { + std::array<config_param *, std::size_t(CONF_MAX)> params; +}; + +#endif diff --git a/src/config/ConfigDefaults.hxx b/src/config/ConfigDefaults.hxx new file mode 100644 index 000000000..c50f28c91 --- /dev/null +++ b/src/config/ConfigDefaults.hxx @@ -0,0 +1,26 @@ +/* + * 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_CONFIG_DEFAULTS_HXX +#define MPD_CONFIG_DEFAULTS_HXX + +static constexpr unsigned DEFAULT_PLAYLIST_MAX_LENGTH = 16 * 1024; +static constexpr bool DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS = false; + +#endif diff --git a/src/config/ConfigError.cxx b/src/config/ConfigError.cxx new file mode 100644 index 000000000..70aff7175 --- /dev/null +++ b/src/config/ConfigError.cxx @@ -0,0 +1,23 @@ +/* + * 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 "ConfigError.hxx" +#include "util/Domain.hxx" + +const Domain config_domain("config"); diff --git a/src/config/ConfigError.hxx b/src/config/ConfigError.hxx new file mode 100644 index 000000000..cbfa79df3 --- /dev/null +++ b/src/config/ConfigError.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_CONFIG_ERROR_HXX +#define MPD_CONFIG_ERROR_HXX + +extern const class Domain config_domain; + +#endif diff --git a/src/config/ConfigFile.cxx b/src/config/ConfigFile.cxx new file mode 100644 index 000000000..1329c4cd4 --- /dev/null +++ b/src/config/ConfigFile.cxx @@ -0,0 +1,269 @@ +/* + * 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 "ConfigFile.hxx" +#include "ConfigData.hxx" +#include "ConfigTemplates.hxx" +#include "util/Tokenizer.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Limits.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdio.h> + +#define MAX_STRING_SIZE MPD_PATH_MAX+80 + +#define CONF_COMMENT '#' + +static constexpr Domain config_file_domain("config_file"); + +static bool +config_read_name_value(struct config_param *param, char *input, unsigned line, + Error &error) +{ + Tokenizer tokenizer(input); + + const char *name = tokenizer.NextWord(error); + if (name == nullptr) { + assert(!tokenizer.IsEnd()); + return false; + } + + const char *value = tokenizer.NextString(error); + if (value == nullptr) { + if (tokenizer.IsEnd()) { + error.Set(config_file_domain, "Value missing"); + } else { + assert(error.IsDefined()); + } + + return false; + } + + if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) { + error.Set(config_file_domain, "Unknown tokens after value"); + return false; + } + + const struct block_param *bp = param->GetBlockParam(name); + if (bp != nullptr) { + error.Format(config_file_domain, + "\"%s\" is duplicate, first defined on line %i", + name, bp->line); + return false; + } + + param->AddBlockParam(name, value, line); + return true; +} + +static struct config_param * +config_read_block(FILE *fp, int *count, char *string, Error &error) +{ + struct config_param *ret = new config_param(*count); + + while (true) { + char *line; + + line = fgets(string, MAX_STRING_SIZE, fp); + if (line == nullptr) { + delete ret; + error.Set(config_file_domain, + "Expected '}' before end-of-file"); + return nullptr; + } + + (*count)++; + line = StripLeft(line); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + if (*line == '}') { + /* end of this block; return from the function + (and from this "while" loop) */ + + line = StripLeft(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + delete ret; + error.Format(config_file_domain, + "line %i: Unknown tokens after '}'", + *count); + return nullptr; + } + + return ret; + } + + /* parse name and value */ + + if (!config_read_name_value(ret, line, *count, error)) { + assert(*line != 0); + delete ret; + error.FormatPrefix("line %i: ", *count); + return nullptr; + } + } +} + +gcc_nonnull_all +static void +Append(config_param *&head, config_param *p) +{ + assert(p->next == nullptr); + + config_param **i = &head; + while (*i != nullptr) + i = &(*i)->next; + + *i = p; +} + +static bool +ReadConfigFile(ConfigData &config_data, FILE *fp, Error &error) +{ + assert(fp != nullptr); + + char string[MAX_STRING_SIZE + 1]; + int count = 0; + struct config_param *param; + + while (fgets(string, MAX_STRING_SIZE, fp)) { + char *line; + const char *name, *value; + + count++; + + line = StripLeft(string); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + /* the first token in each line is the name, followed + by either the value or '{' */ + + Tokenizer tokenizer(line); + name = tokenizer.NextWord(error); + if (name == nullptr) { + assert(!tokenizer.IsEnd()); + error.FormatPrefix("line %i: ", count); + return false; + } + + /* get the definition of that option, and check the + "repeatable" flag */ + + const ConfigOption o = ParseConfigOptionName(name); + if (o == CONF_MAX) { + error.Format(config_file_domain, + "unrecognized parameter in config file at " + "line %i: %s\n", count, name); + return false; + } + + const unsigned i = unsigned(o); + const ConfigTemplate &option = config_templates[i]; + config_param *&head = config_data.params[i]; + + if (head != nullptr && !option.repeatable) { + param = head; + error.Format(config_file_domain, + "config parameter \"%s\" is first defined " + "on line %i and redefined on line %i\n", + name, param->line, count); + return false; + } + + /* now parse the block or the value */ + + if (option.block) { + /* it's a block, call config_read_block() */ + + if (tokenizer.CurrentChar() != '{') { + error.Format(config_file_domain, + "line %i: '{' expected", count); + return false; + } + + line = StripLeft(tokenizer.Rest() + 1); + if (*line != 0 && *line != CONF_COMMENT) { + error.Format(config_file_domain, + "line %i: Unknown tokens after '{'", + count); + return false; + } + + param = config_read_block(fp, &count, string, error); + if (param == nullptr) { + return false; + } + } else { + /* a string value */ + + value = tokenizer.NextString(error); + if (value == nullptr) { + if (tokenizer.IsEnd()) + error.Format(config_file_domain, + "line %i: Value missing", + count); + else + error.FormatPrefix("line %i: ", count); + + return false; + } + + if (!tokenizer.IsEnd() && + tokenizer.CurrentChar() != CONF_COMMENT) { + error.Format(config_file_domain, + "line %i: Unknown tokens after value", + count); + return false; + } + + param = new config_param(value, count); + } + + Append(head, param); + } + + return true; +} + +bool +ReadConfigFile(ConfigData &config_data, Path path, Error &error) +{ + assert(!path.IsNull()); + const std::string path_utf8 = path.ToUTF8(); + + FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str()); + + FILE *fp = FOpen(path, FOpenMode::ReadText); + if (fp == nullptr) { + error.FormatErrno("Failed to open %s", path_utf8.c_str()); + return false; + } + + bool result = ReadConfigFile(config_data, fp, error); + fclose(fp); + return result; +} diff --git a/src/config/ConfigFile.hxx b/src/config/ConfigFile.hxx new file mode 100644 index 000000000..b87182c6a --- /dev/null +++ b/src/config/ConfigFile.hxx @@ -0,0 +1,30 @@ +/* + * 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_CONFIG_FILE_HXX +#define MPD_CONFIG_FILE_HXX + +class Error; +class Path; +struct ConfigData; + +bool +ReadConfigFile(ConfigData &data, Path path, Error &error); + +#endif diff --git a/src/config/ConfigGlobal.cxx b/src/config/ConfigGlobal.cxx new file mode 100644 index 000000000..9bc83398c --- /dev/null +++ b/src/config/ConfigGlobal.cxx @@ -0,0 +1,190 @@ +/* + * 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 "ConfigGlobal.hxx" +#include "ConfigParser.hxx" +#include "ConfigData.hxx" +#include "ConfigFile.hxx" +#include "ConfigPath.hxx" +#include "ConfigError.hxx" +#include "fs/Path.hxx" +#include "fs/AllocatedPath.hxx" +#include "util/Error.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <stdlib.h> + +static ConfigData config_data; + +void config_global_finish(void) +{ + for (auto i : config_data.params) + delete i; +} + +void config_global_init(void) +{ +} + +bool +ReadConfigFile(Path path, Error &error) +{ + return ReadConfigFile(config_data, path, error); +} + +static void +Check(const config_param *param) +{ + if (!param->used) + /* this whole config_param was not queried at all - + the feature might be disabled at compile time? + Silently ignore it here. */ + return; + + for (const auto &i : param->block_params) { + if (!i.used) + FormatWarning(config_domain, + "option '%s' on line %i was not recognized", + i.name.c_str(), i.line); + } +} + +void config_global_check(void) +{ + for (auto i : config_data.params) + for (const config_param *p = i; p != nullptr; p = p->next) + Check(p); +} + +const config_param * +config_get_param(ConfigOption option) +{ + config_param *param = config_data.params[unsigned(option)]; + if (param != nullptr) + param->used = true; + return param; +} + +const config_param * +config_find_block(ConfigOption option, const char *key, const char *value) +{ + for (const config_param *param = config_get_param(option); + param != nullptr; param = param->next) { + const char *value2 = param->GetBlockValue(key); + if (value2 == nullptr) + FormatFatalError("block without '%s' name in line %d", + key, param->line); + + if (strcmp(value2, value) == 0) + return param; + } + + return nullptr; +} + +const char * +config_get_string(ConfigOption option, const char *default_value) +{ + const struct config_param *param = config_get_param(option); + + if (param == nullptr) + return default_value; + + return param->value.c_str(); +} + +AllocatedPath +config_get_path(ConfigOption option, Error &error) +{ + const struct config_param *param = config_get_param(option); + if (param == nullptr) + return AllocatedPath::Null(); + + return config_parse_path(param, error); +} + +AllocatedPath +config_parse_path(const struct config_param *param, Error & error) +{ + AllocatedPath path = ParsePath(param->value.c_str(), error); + if (gcc_unlikely(path.IsNull())) + error.FormatPrefix("Invalid path at line %i: ", + param->line); + + return path; +} + +unsigned +config_get_unsigned(ConfigOption option, unsigned default_value) +{ + const struct config_param *param = config_get_param(option); + long value; + char *endptr; + + if (param == nullptr) + return default_value; + + value = strtol(param->value.c_str(), &endptr, 0); + if (*endptr != 0 || value < 0) + FormatFatalError("Not a valid non-negative number in line %i", + param->line); + + return (unsigned)value; +} + +unsigned +config_get_positive(ConfigOption option, unsigned default_value) +{ + const struct config_param *param = config_get_param(option); + long value; + char *endptr; + + if (param == nullptr) + return default_value; + + value = strtol(param->value.c_str(), &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid number in line %i", param->line); + + if (value <= 0) + FormatFatalError("Not a positive number in line %i", + param->line); + + return (unsigned)value; +} + +bool +config_get_bool(ConfigOption option, bool default_value) +{ + const struct config_param *param = config_get_param(option); + bool success, value; + + if (param == nullptr) + return default_value; + + success = get_bool(param->value.c_str(), &value); + if (!success) + FormatFatalError("Expected boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + param->line); + + return value; +} diff --git a/src/config/ConfigGlobal.hxx b/src/config/ConfigGlobal.hxx new file mode 100644 index 000000000..831418d03 --- /dev/null +++ b/src/config/ConfigGlobal.hxx @@ -0,0 +1,97 @@ +/* + * 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_CONFIG_GLOBAL_HXX +#define MPD_CONFIG_GLOBAL_HXX + +#include "ConfigOption.hxx" +#include "Compiler.h" + +class Error; +class Path; +class AllocatedPath; +struct config_param; + +void config_global_init(void); +void config_global_finish(void); + +/** + * Call this function after all configuration has been evaluated. It + * checks for unused parameters, and logs warnings. + */ +void config_global_check(void); + +bool +ReadConfigFile(Path path, Error &error); + +gcc_pure +const config_param * +config_get_param(enum ConfigOption option); + +/** + * Find a block with a matching attribute. + * + * @param option the blocks to search + * @param key the attribute name + * @param value the expected attribute value + */ +gcc_pure +const config_param * +config_find_block(ConfigOption option, const char *key, const char *value); + +/* Note on gcc_pure: Some of the functions declared pure are not + really pure in strict sense. They have side effect such that they + validate parameter's value and signal an error if it's invalid. + However, if the argument was already validated or we don't care + about the argument at all, this may be ignored so in the end, we + should be fine with calling those functions pure. */ + +gcc_pure +const char * +config_get_string(enum ConfigOption option, const char *default_value); + +/** + * Returns an optional configuration variable which contains an + * absolute path. If there is a tilde prefix, it is expanded. + * Returns AllocatedPath::Null() if the value is not present. If the path + * could not be parsed, returns AllocatedPath::Null() and sets the error. + */ +AllocatedPath +config_get_path(enum ConfigOption option, Error &error); + +/** + * Parse a configuration parameter as a path. + * If there is a tilde prefix, it is expanded. If the path could + * not be parsed, returns AllocatedPath::Null() and sets the error. + */ +AllocatedPath +config_parse_path(const struct config_param *param, Error & error_r); + +gcc_pure +unsigned +config_get_unsigned(enum ConfigOption option, unsigned default_value); + +gcc_pure +unsigned +config_get_positive(enum ConfigOption option, unsigned default_value); + +gcc_pure +bool config_get_bool(enum ConfigOption option, bool default_value); + +#endif diff --git a/src/config/ConfigOption.hxx b/src/config/ConfigOption.hxx new file mode 100644 index 000000000..506c9e9dc --- /dev/null +++ b/src/config/ConfigOption.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_CONFIG_OPTION_HXX +#define MPD_CONFIG_OPTION_HXX + +#include "Compiler.h" + +enum ConfigOption { + CONF_MUSIC_DIR, + CONF_PLAYLIST_DIR, + CONF_FOLLOW_INSIDE_SYMLINKS, + CONF_FOLLOW_OUTSIDE_SYMLINKS, + CONF_DB_FILE, + CONF_STICKER_FILE, + CONF_LOG_FILE, + CONF_PID_FILE, + CONF_STATE_FILE, + CONF_RESTORE_PAUSED, + CONF_USER, + CONF_GROUP, + CONF_BIND_TO_ADDRESS, + CONF_PORT, + CONF_LOG_LEVEL, + CONF_ZEROCONF_NAME, + CONF_ZEROCONF_ENABLED, + CONF_PASSWORD, + CONF_DEFAULT_PERMS, + CONF_AUDIO_OUTPUT, + CONF_AUDIO_OUTPUT_FORMAT, + CONF_MIXER_TYPE, + CONF_REPLAYGAIN, + CONF_REPLAYGAIN_PREAMP, + CONF_REPLAYGAIN_MISSING_PREAMP, + CONF_REPLAYGAIN_LIMIT, + CONF_VOLUME_NORMALIZATION, + CONF_SAMPLERATE_CONVERTER, + CONF_AUDIO_BUFFER_SIZE, + CONF_BUFFER_BEFORE_PLAY, + CONF_HTTP_PROXY_HOST, + CONF_HTTP_PROXY_PORT, + CONF_HTTP_PROXY_USER, + CONF_HTTP_PROXY_PASSWORD, + CONF_CONN_TIMEOUT, + CONF_MAX_CONN, + CONF_MAX_PLAYLIST_LENGTH, + CONF_MAX_COMMAND_LIST_SIZE, + CONF_MAX_OUTPUT_BUFFER_SIZE, + CONF_FS_CHARSET, + CONF_ID3V1_ENCODING, + CONF_METADATA_TO_USE, + CONF_SAVE_ABSOLUTE_PATHS, + CONF_DECODER, + CONF_INPUT, + CONF_GAPLESS_MP3_PLAYBACK, + CONF_PLAYLIST_PLUGIN, + CONF_AUTO_UPDATE, + CONF_AUTO_UPDATE_DEPTH, + CONF_DESPOTIFY_USER, + CONF_DESPOTIFY_PASSWORD, + CONF_DESPOTIFY_HIGH_BITRATE, + CONF_AUDIO_FILTER, + CONF_DATABASE, + CONF_NEIGHBORS, + CONF_MAX +}; + +/** + * @return #CONF_MAX if not found + */ +gcc_pure +enum ConfigOption +ParseConfigOptionName(const char *name); + +#endif diff --git a/src/config/ConfigParser.cxx b/src/config/ConfigParser.cxx new file mode 100644 index 000000000..3535c9a13 --- /dev/null +++ b/src/config/ConfigParser.cxx @@ -0,0 +1,40 @@ +/* + * 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 "ConfigParser.hxx" +#include "util/StringUtil.hxx" + +bool +get_bool(const char *value, bool *value_r) +{ + static const char *t[] = { "yes", "true", "1", nullptr }; + static const char *f[] = { "no", "false", "0", nullptr }; + + if (string_array_contains(t, value)) { + *value_r = true; + return true; + } + + if (string_array_contains(f, value)) { + *value_r = false; + return true; + } + + return false; +} diff --git a/src/config/ConfigParser.hxx b/src/config/ConfigParser.hxx new file mode 100644 index 000000000..06151b0bd --- /dev/null +++ b/src/config/ConfigParser.hxx @@ -0,0 +1,26 @@ +/* + * 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_CONFIG_PARSER_HXX +#define MPD_CONFIG_PARSER_HXX + +bool +get_bool(const char *value, bool *value_r); + +#endif diff --git a/src/config/ConfigPath.cxx b/src/config/ConfigPath.cxx new file mode 100644 index 000000000..a3b3f83a5 --- /dev/null +++ b/src/config/ConfigPath.cxx @@ -0,0 +1,131 @@ +/* + * 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 "ConfigPath.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" +#include "fs/Domain.hxx" +#include "fs/StandardDirectory.hxx" +#include "util/Error.hxx" +#include "ConfigGlobal.hxx" + +#include <assert.h> +#include <string.h> + +#ifndef WIN32 +#include <pwd.h> + +/** + * Determine a given user's home directory. + */ +static AllocatedPath +GetHome(const char *user, Error &error) +{ + AllocatedPath result = GetHomeDir(user); + if (result.IsNull()) { + error.Format(path_domain, + "no such user: %s", user); + return AllocatedPath::Null(); + } + + return result; +} + +/** + * Determine the current user's home directory. + */ +static AllocatedPath +GetHome(Error &error) +{ + AllocatedPath result = GetHomeDir(); + if (result.IsNull()) { + error.Set(path_domain, + "problems getting home for current user"); + return AllocatedPath::Null(); + } + + return result; +} + +/** + * Determine the configured user's home directory. + */ +static AllocatedPath +GetConfiguredHome(Error &error) +{ + const char *user = config_get_string(CONF_USER, nullptr); + return user != nullptr + ? GetHome(user, error) + : GetHome(error); +} + +#endif + +AllocatedPath +ParsePath(const char *path, Error &error) +{ + assert(path != nullptr); + +#ifndef WIN32 + if (path[0] == '~') { + ++path; + + if (*path == '\0') + return GetConfiguredHome(error); + + AllocatedPath home = AllocatedPath::Null(); + + if (*path == '/') { + home = GetConfiguredHome(error); + + ++path; + } else { + const char *slash = strchr(path, '/'); + const char *end = slash == nullptr + ? path + strlen(path) + : slash; + const std::string user(path, end); + home = GetHome(user.c_str(), error); + + if (slash == nullptr) + return home; + + path = slash + 1; + } + + if (home.IsNull()) + return AllocatedPath::Null(); + + AllocatedPath path2 = AllocatedPath::FromUTF8(path, error); + if (path2.IsNull()) + return AllocatedPath::Null(); + + return AllocatedPath::Build(home, path2); + } else if (!PathTraitsUTF8::IsAbsolute(path)) { + error.Format(path_domain, + "not an absolute path: %s", path); + return AllocatedPath::Null(); + } else { +#endif + return AllocatedPath::FromUTF8(path, error); +#ifndef WIN32 + } +#endif +} diff --git a/src/config/ConfigPath.hxx b/src/config/ConfigPath.hxx new file mode 100644 index 000000000..a5518a497 --- /dev/null +++ b/src/config/ConfigPath.hxx @@ -0,0 +1,29 @@ +/* + * 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_CONFIG_PATH_HXX +#define MPD_CONFIG_PATH_HXX + +class AllocatedPath; +class Error; + +AllocatedPath +ParsePath(const char *path, Error &error); + +#endif diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx new file mode 100644 index 000000000..8eaa22bdd --- /dev/null +++ b/src/config/ConfigTemplates.cxx @@ -0,0 +1,97 @@ +/* + * 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 "ConfigTemplates.hxx" +#include "ConfigOption.hxx" + +#include <string.h> + +const ConfigTemplate config_templates[] = { + { "music_directory", false, false }, + { "playlist_directory", false, false }, + { "follow_inside_symlinks", false, false }, + { "follow_outside_symlinks", false, false }, + { "db_file", false, false }, + { "sticker_file", false, false }, + { "log_file", false, false }, + { "pid_file", false, false }, + { "state_file", false, false }, + { "restore_paused", false, false }, + { "user", false, false }, + { "group", false, false }, + { "bind_to_address", true, false }, + { "port", false, false }, + { "log_level", false, false }, + { "zeroconf_name", false, false }, + { "zeroconf_enabled", false, false }, + { "password", true, false }, + { "default_permissions", false, false }, + { "audio_output", true, true }, + { "audio_output_format", false, false }, + { "mixer_type", false, false }, + { "replaygain", false, false }, + { "replaygain_preamp", false, false }, + { "replaygain_missing_preamp", false, false }, + { "replaygain_limit", false, false }, + { "volume_normalization", false, false }, + { "samplerate_converter", false, false }, + { "audio_buffer_size", false, false }, + { "buffer_before_play", false, false }, + { "http_proxy_host", false, false }, + { "http_proxy_port", false, false }, + { "http_proxy_user", false, false }, + { "http_proxy_password", false, false }, + { "connection_timeout", false, false }, + { "max_connections", false, false }, + { "max_playlist_length", false, false }, + { "max_command_list_size", false, false }, + { "max_output_buffer_size", false, false }, + { "filesystem_charset", false, false }, + { "id3v1_encoding", false, false }, + { "metadata_to_use", false, false }, + { "save_absolute_paths_in_playlists", false, false }, + { "decoder", true, true }, + { "input", true, true }, + { "gapless_mp3_playback", false, false }, + { "playlist_plugin", true, true }, + { "auto_update", false, false }, + { "auto_update_depth", false, false }, + { "despotify_user", false, false }, + { "despotify_password", false, false}, + { "despotify_high_bitrate", false, false }, + { "filter", true, true }, + { "database", false, true }, + { "neighbors", true, true }, +}; + +static constexpr unsigned n_config_templates = + sizeof(config_templates) / sizeof(config_templates[0]); + +static_assert(n_config_templates == unsigned(CONF_MAX), + "Wrong number of config_templates"); + +ConfigOption +ParseConfigOptionName(const char *name) +{ + for (unsigned i = 0; i < n_config_templates; ++i) + if (strcmp(config_templates[i].name, name) == 0) + return ConfigOption(i); + + return CONF_MAX; +} diff --git a/src/config/ConfigTemplates.hxx b/src/config/ConfigTemplates.hxx new file mode 100644 index 000000000..90d098dc0 --- /dev/null +++ b/src/config/ConfigTemplates.hxx @@ -0,0 +1,31 @@ +/* + * 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_CONFIG_TEMPLATES_HXX +#define MPD_CONFIG_TEMPLATES_HXX + +struct ConfigTemplate { + const char *const name; + const bool repeatable; + const bool block; +}; + +extern const ConfigTemplate config_templates[]; + +#endif diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx deleted file mode 100644 index 60b33b6b4..000000000 --- a/src/cue/CueParser.cxx +++ /dev/null @@ -1,321 +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 "CueParser.hxx" -#include "util/StringUtil.hxx" -#include "util/CharUtil.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -CueParser::CueParser() - :state(HEADER), tag(new Tag()), - current(nullptr), - previous(nullptr), - finished(nullptr), - end(false) {} - -CueParser::~CueParser() -{ - delete tag; - - if (current != nullptr) - current->Free(); - - if (previous != nullptr) - previous->Free(); - - if (finished != nullptr) - finished->Free(); -} - -static const char * -cue_next_word(char *p, char **pp) -{ - assert(p >= *pp); - assert(!IsWhitespaceNotNull(*p)); - - const char *word = p; - while (!IsWhitespaceOrNull(*p)) - ++p; - - *p = 0; - *pp = p + 1; - return word; -} - -static const char * -cue_next_quoted(char *p, char **pp) -{ - assert(p >= *pp); - assert(p[-1] == '"'); - - char *end = strchr(p, '"'); - if (end == nullptr) { - /* syntax error - ignore it silently */ - *pp = p + strlen(p); - return p; - } - - *end = 0; - *pp = end + 1; - - return p; -} - -static const char * -cue_next_token(char **pp) -{ - char *p = strchug_fast(*pp); - if (*p == 0) - return nullptr; - - return cue_next_word(p, pp); -} - -static const char * -cue_next_value(char **pp) -{ - char *p = strchug_fast(*pp); - if (*p == 0) - return nullptr; - - if (*p == '"') - return cue_next_quoted(p + 1, pp); - else - return cue_next_word(p, pp); -} - -static void -cue_add_tag(Tag &tag, TagType type, char *p) -{ - const char *value = cue_next_value(&p); - if (value != nullptr) - tag.AddItem(type, value); - -} - -static void -cue_parse_rem(char *p, Tag &tag) -{ - const char *type = cue_next_token(&p); - if (type == nullptr) - return; - - TagType type2 = tag_name_parse_i(type); - if (type2 != TAG_NUM_OF_ITEM_TYPES) - cue_add_tag(tag, type2, p); -} - -Tag * -CueParser::GetCurrentTag() -{ - if (state == HEADER) - return tag; - else if (state == TRACK) - return current->tag; - else - return nullptr; -} - -static int -cue_parse_position(const char *p) -{ - char *endptr; - unsigned long minutes = strtoul(p, &endptr, 10); - if (endptr == p || *endptr != ':') - return -1; - - p = endptr + 1; - unsigned long seconds = strtoul(p, &endptr, 10); - if (endptr == p || *endptr != ':') - return -1; - - p = endptr + 1; - unsigned long frames = strtoul(p, &endptr, 10); - if (endptr == p || *endptr != 0) - return -1; - - return minutes * 60000 + seconds * 1000 + frames * 1000 / 75; -} - -void -CueParser::Commit() -{ - /* the caller of this library must call cue_parser_get() often - enough */ - assert(finished == nullptr); - assert(!end); - - if (current == nullptr) - return; - - finished = previous; - previous = current; - current = nullptr; -} - -void -CueParser::Feed2(char *p) -{ - assert(!end); - assert(p != nullptr); - - const char *command = cue_next_token(&p); - if (command == nullptr) - return; - - if (strcmp(command, "REM") == 0) { - Tag *current_tag = GetCurrentTag(); - if (current_tag != nullptr) - cue_parse_rem(p, *current_tag); - } else if (strcmp(command, "PERFORMER") == 0) { - /* MPD knows a "performer" tag, but it is not a good - match for this CUE tag; from the Hydrogenaudio - Knowledgebase: "At top-level this will specify the - CD artist, while at track-level it specifies the - track artist." */ - - TagType type = state == TRACK - ? TAG_ARTIST - : TAG_ALBUM_ARTIST; - - Tag *current_tag = GetCurrentTag(); - if (current_tag != nullptr) - cue_add_tag(*current_tag, type, p); - } else if (strcmp(command, "TITLE") == 0) { - if (state == HEADER) - cue_add_tag(*tag, TAG_ALBUM, p); - else if (state == TRACK) - cue_add_tag(*current->tag, TAG_TITLE, p); - } else if (strcmp(command, "FILE") == 0) { - Commit(); - - const char *new_filename = cue_next_value(&p); - if (new_filename == nullptr) - return; - - const char *type = cue_next_token(&p); - if (type == nullptr) - return; - - if (strcmp(type, "WAVE") != 0 && - strcmp(type, "MP3") != 0 && - strcmp(type, "AIFF") != 0) { - state = IGNORE_FILE; - return; - } - - state = WAVE; - filename = new_filename; - } else if (state == IGNORE_FILE) { - return; - } else if (strcmp(command, "TRACK") == 0) { - Commit(); - - const char *nr = cue_next_token(&p); - if (nr == nullptr) - return; - - const char *type = cue_next_token(&p); - if (type == nullptr) - return; - - if (strcmp(type, "AUDIO") != 0) { - state = IGNORE_TRACK; - return; - } - - state = TRACK; - current = Song::NewRemote(filename.c_str()); - assert(current->tag == nullptr); - current->tag = new Tag(*tag); - current->tag->AddItem(TAG_TRACK, nr); - last_updated = false; - } else if (state == IGNORE_TRACK) { - return; - } else if (state == TRACK && strcmp(command, "INDEX") == 0) { - const char *nr = cue_next_token(&p); - if (nr == nullptr) - return; - - const char *position = cue_next_token(&p); - if (position == nullptr) - return; - - int position_ms = cue_parse_position(position); - if (position_ms < 0) - return; - - if (!last_updated && previous != nullptr && - previous->start_ms < (unsigned)position_ms) { - last_updated = true; - previous->end_ms = position_ms; - previous->tag->time = - (previous->end_ms - previous->start_ms + 500) / 1000; - } - - current->start_ms = position_ms; - } -} - -void -CueParser::Feed(const char *line) -{ - assert(!end); - assert(line != nullptr); - - char *allocated = g_strdup(line); - Feed2(allocated); - g_free(allocated); -} - -void -CueParser::Finish() -{ - if (end) - /* has already been called, ignore */ - return; - - Commit(); - end = true; -} - -Song * -CueParser::Get() -{ - if (finished == nullptr && end) { - /* cue_parser_finish() has been called already: - deliver all remaining (partial) results */ - assert(current == nullptr); - - finished = previous; - previous = nullptr; - } - - Song *song = finished; - finished = nullptr; - return song; -} diff --git a/src/cue/CueParser.hxx b/src/cue/CueParser.hxx deleted file mode 100644 index abcceaa2e..000000000 --- a/src/cue/CueParser.hxx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CUE_PARSER_HXX -#define MPD_CUE_PARSER_HXX - -#include "check.h" -#include "Compiler.h" - -#include <string> - -struct Song; -struct Tag; - -class CueParser { - enum { - /** - * Parsing the CUE header. - */ - HEADER, - - /** - * Parsing a "FILE ... WAVE". - */ - WAVE, - - /** - * Ignore everything until the next "FILE". - */ - IGNORE_FILE, - - /** - * Parsing a "TRACK ... AUDIO". - */ - TRACK, - - /** - * Ignore everything until the next "TRACK". - */ - IGNORE_TRACK, - } state; - - Tag *tag; - - std::string filename; - - /** - * The song currently being edited. - */ - Song *current; - - /** - * The previous song. It is remembered because its end_time - * will be set to the current song's start time. - */ - Song *previous; - - /** - * A song that is completely finished and can be returned to - * the caller via cue_parser_get(). - */ - Song *finished; - - /** - * Set to true after previous.end_time has been updated to the - * start time of the current song. - */ - bool last_updated; - - /** - * Tracks whether cue_parser_finish() has been called. If - * true, then all remaining (partial) results will be - * delivered by cue_parser_get(). - */ - bool end; - -public: - CueParser(); - ~CueParser(); - - /** - * Feed a text line from the CUE file into the parser. Call - * cue_parser_get() after this to see if a song has been finished. - */ - void Feed(const char *line); - - /** - * Tell the parser that the end of the file has been reached. Call - * cue_parser_get() after this to see if a song has been finished. - * This procedure must be done twice! - */ - void Finish(); - - /** - * Check if a song was finished by the last cue_parser_feed() or - * cue_parser_finish() call. - * - * @return a song object that must be freed by the caller, or NULL if - * no song was finished at this time - */ - Song *Get(); - -private: - gcc_pure - Tag *GetCurrentTag(); - - /** - * Commit the current song. It will be moved to "previous", - * so the next song may soon edit its end time (using the next - * song's start time). - */ - void Commit(); - - void Feed2(char *p); -}; - -#endif diff --git a/src/db/Configured.cxx b/src/db/Configured.cxx new file mode 100644 index 000000000..625300d75 --- /dev/null +++ b/src/db/Configured.cxx @@ -0,0 +1,72 @@ +/* + * 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 "Configured.hxx" +#include "DatabaseGlue.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigError.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/StandardDirectory.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +Database * +CreateConfiguredDatabase(EventLoop &loop, DatabaseListener &listener, + Error &error) +{ + const struct config_param *param = config_get_param(CONF_DATABASE); + const struct config_param *path = config_get_param(CONF_DB_FILE); + + if (param != nullptr && path != nullptr) { + error.Format(config_domain, + "Found both 'database' (line %d) and 'db_file' (line %d) setting", + param->line, path->line); + return nullptr; + } + + struct config_param *allocated = nullptr; + + if (param == nullptr && path != nullptr) { + allocated = new config_param("database", path->line); + allocated->AddBlockParam("path", path->value.c_str(), + path->line); + param = allocated; + } + + if (param == nullptr) { + /* if there is no override, use the cache directory */ + + const AllocatedPath cache_dir = GetUserCacheDir(); + if (cache_dir.IsNull()) + return nullptr; + + const auto db_file = AllocatedPath::Build(cache_dir, "mpd.db"); + + allocated = new config_param("database"); + allocated->AddBlockParam("path", db_file.c_str(), -1); + param = allocated; + } + + Database *db = DatabaseGlobalInit(loop, listener, *param, + error); + delete allocated; + return db; +} diff --git a/src/db/Configured.hxx b/src/db/Configured.hxx new file mode 100644 index 000000000..5d25b701c --- /dev/null +++ b/src/db/Configured.hxx @@ -0,0 +1,39 @@ +/* + * 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_DB_CONFIG_HXX +#define MPD_DB_CONFIG_HXX + +#include "check.h" + +class EventLoop; +class DatabaseListener; +class Database; +class Error; + +/** + * Read database configuration settings and create a #Database + * instance from it, but do not open it. Returns nullptr on error or + * if no database is configured (no #Error set in that case). + */ +Database * +CreateConfiguredDatabase(EventLoop &loop, DatabaseListener &listener, + Error &error); + +#endif diff --git a/src/db/Count.cxx b/src/db/Count.cxx new file mode 100644 index 000000000..4fd53a73b --- /dev/null +++ b/src/db/Count.cxx @@ -0,0 +1,145 @@ +/* + * 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 "Count.hxx" +#include "Selection.hxx" +#include "Interface.hxx" +#include "client/Client.hxx" +#include "LightSong.hxx" +#include "tag/Set.hxx" + +#include <functional> +#include <map> + +struct SearchStats { + unsigned n_songs; + unsigned long total_time_s; + + constexpr SearchStats() + :n_songs(0), total_time_s(0) {} +}; + +class TagCountMap : public std::map<std::string, SearchStats> { +}; + +static void +PrintSearchStats(Client &client, const SearchStats &stats) +{ + client_printf(client, + "songs: %u\n" + "playtime: %lu\n", + stats.n_songs, stats.total_time_s); +} + +static void +Print(Client &client, TagType group, const TagCountMap &m) +{ + assert(unsigned(group) < TAG_NUM_OF_ITEM_TYPES); + + for (const auto &i : m) { + client_printf(client, "%s: %s\n", + tag_item_names[group], i.first.c_str()); + PrintSearchStats(client, i.second); + } +} + +static bool +stats_visitor_song(SearchStats &stats, const LightSong &song) +{ + stats.n_songs++; + stats.total_time_s += song.GetDuration(); + + return true; +} + +static bool +CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag) +{ + bool found = false; + for (const auto &item : tag) { + if (item.type == group) { + auto r = map.insert(std::make_pair(item.value, + SearchStats())); + SearchStats &s = r.first->second; + ++s.n_songs; + if (tag.time > 0) + s.total_time_s += tag.time; + + found = true; + } + } + + return found; +} + +static bool +GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song) +{ + assert(song.tag != nullptr); + + const Tag &tag = *song.tag; + if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST) + /* fall back to "Artist" if no "AlbumArtist" was found */ + CollectGroupCounts(map, TAG_ARTIST, tag); + + return true; +} + +bool +PrintSongCount(Client &client, const char *name, + const SongFilter *filter, + TagType group, + Error &error) +{ + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return false; + + const DatabaseSelection selection(name, true, filter); + + if (group == TAG_NUM_OF_ITEM_TYPES) { + /* no grouping */ + + SearchStats stats; + + using namespace std::placeholders; + const auto f = std::bind(stats_visitor_song, std::ref(stats), + _1); + if (!db->Visit(selection, f, error)) + return false; + + PrintSearchStats(client, stats); + } else { + /* group by the specified tag: store counts in a + std::map */ + + TagCountMap map; + + using namespace std::placeholders; + const auto f = std::bind(GroupCountVisitor, std::ref(map), + group, _1); + if (!db->Visit(selection, f, error)) + return false; + + Print(client, group, map); + } + + return true; +} diff --git a/src/db/Count.hxx b/src/db/Count.hxx new file mode 100644 index 000000000..d22a3210d --- /dev/null +++ b/src/db/Count.hxx @@ -0,0 +1,39 @@ +/* + * 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_DB_COUNT_HXX +#define MPD_DB_COUNT_HXX + +#include "Compiler.h" + +#include <stdint.h> + +enum TagType : uint8_t; +class Client; +class SongFilter; +class Error; + +gcc_nonnull(2) +bool +PrintSongCount(Client &client, const char *name, + const SongFilter *filter, + TagType group, + Error &error); + +#endif diff --git a/src/db/DatabaseError.cxx b/src/db/DatabaseError.cxx new file mode 100644 index 000000000..e0cbdd6a3 --- /dev/null +++ b/src/db/DatabaseError.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseError.hxx" +#include "util/Domain.hxx" + +const Domain db_domain("db"); diff --git a/src/db/DatabaseError.hxx b/src/db/DatabaseError.hxx new file mode 100644 index 000000000..c71bbdfff --- /dev/null +++ b/src/db/DatabaseError.hxx @@ -0,0 +1,39 @@ +/* + * 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_DB_ERROR_HXX +#define MPD_DB_ERROR_HXX + +class Domain; + +enum db_error { + /** + * The database is disabled, i.e. none is configured in this + * MPD instance. + */ + DB_DISABLED, + + DB_NOT_FOUND, + + DB_CONFLICT, +}; + +extern const Domain db_domain; + +#endif diff --git a/src/db/DatabaseGlue.cxx b/src/db/DatabaseGlue.cxx new file mode 100644 index 000000000..ade5c95f3 --- /dev/null +++ b/src/db/DatabaseGlue.cxx @@ -0,0 +1,45 @@ +/* + * 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 "DatabaseGlue.hxx" +#include "Registry.hxx" +#include "DatabaseError.hxx" +#include "util/Error.hxx" +#include "config/ConfigData.hxx" +#include "DatabasePlugin.hxx" + +#include <string.h> + +Database * +DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + const char *plugin_name = + param.GetBlockValue("plugin", "simple"); + + const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); + if (plugin == nullptr) { + error.Format(db_domain, + "No such database plugin: %s", plugin_name); + return nullptr; + } + + return plugin->create(loop, listener, param, error); +} diff --git a/src/db/DatabaseGlue.hxx b/src/db/DatabaseGlue.hxx new file mode 100644 index 000000000..70b50def3 --- /dev/null +++ b/src/db/DatabaseGlue.hxx @@ -0,0 +1,40 @@ +/* + * 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_DATABASE_GLUE_HXX +#define MPD_DATABASE_GLUE_HXX + +#include "Compiler.h" + +struct config_param; +class EventLoop; +class DatabaseListener; +class Database; +class Error; + +/** + * Initialize the database library. + * + * @param param the database configuration block + */ +Database * +DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error); + +#endif diff --git a/src/db/DatabaseListener.hxx b/src/db/DatabaseListener.hxx new file mode 100644 index 000000000..8b410c2f5 --- /dev/null +++ b/src/db/DatabaseListener.hxx @@ -0,0 +1,46 @@ +/* + * 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_DATABASE_CLIENT_HXX +#define MPD_DATABASE_CLIENT_HXX + +struct LightSong; + +/** + * An object that listens to events from the #Database. + * + * @see #Instance + */ +class DatabaseListener { +public: + /** + * The database has been modified. This must be called in the + * thread that has created the #Database instance and that + * runs the #EventLoop. + */ + virtual void OnDatabaseModified() = 0; + + /** + * During database update, a song is about to be removed from + * the database because the file has disappeared. + */ + virtual void OnDatabaseSongRemoved(const LightSong &song) = 0; +}; + +#endif diff --git a/src/db/DatabaseLock.cxx b/src/db/DatabaseLock.cxx new file mode 100644 index 000000000..c0b5e4844 --- /dev/null +++ b/src/db/DatabaseLock.cxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseLock.hxx" + +Mutex db_mutex; + +#ifndef NDEBUG +ThreadId db_mutex_holder; +#endif diff --git a/src/db/DatabaseLock.hxx b/src/db/DatabaseLock.hxx new file mode 100644 index 000000000..9d0b0c152 --- /dev/null +++ b/src/db/DatabaseLock.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Support for locking data structures from the database, for safe + * multi-threading. + */ + +#ifndef MPD_DB_LOCK_HXX +#define MPD_DB_LOCK_HXX + +#include "check.h" +#include "thread/Mutex.hxx" +#include "Compiler.h" + +#include <assert.h> + +extern Mutex db_mutex; + +#ifndef NDEBUG + +#include "thread/Id.hxx" + +extern ThreadId db_mutex_holder; + +/** + * Does the current thread hold the database lock? + */ +gcc_pure +static inline bool +holding_db_lock(void) +{ + return db_mutex_holder.IsInside(); +} + +#endif + +/** + * Obtain the global database lock. This is needed before + * dereferencing a #song or #directory. It is not recursive. + */ +static inline void +db_lock(void) +{ + assert(!holding_db_lock()); + + db_mutex.lock(); + + assert(db_mutex_holder.IsNull()); +#ifndef NDEBUG + db_mutex_holder = ThreadId::GetCurrent(); +#endif +} + +/** + * Release the global database lock. + */ +static inline void +db_unlock(void) +{ + assert(holding_db_lock()); +#ifndef NDEBUG + db_mutex_holder = ThreadId::Null(); +#endif + + db_mutex.unlock(); +} + +class ScopeDatabaseLock { +public: + ScopeDatabaseLock() { + db_lock(); + } + + ~ScopeDatabaseLock() { + db_unlock(); + } +}; + +#endif diff --git a/src/db/DatabasePlaylist.cxx b/src/db/DatabasePlaylist.cxx new file mode 100644 index 000000000..f1cfdc874 --- /dev/null +++ b/src/db/DatabasePlaylist.cxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabasePlaylist.hxx" +#include "DatabaseSong.hxx" +#include "Selection.hxx" +#include "PlaylistFile.hxx" +#include "Interface.hxx" +#include "DetachedSong.hxx" +#include "storage/StorageInterface.hxx" + +#include <functional> + +static bool +AddSong(const Storage &storage, const char *playlist_path_utf8, + const LightSong &song, Error &error) +{ + return spl_append_song(playlist_path_utf8, + DatabaseDetachSong(storage, song), + error); +} + +bool +search_add_to_playlist(const Database &db, const Storage &storage, + const char *uri, const char *playlist_path_utf8, + const SongFilter *filter, + Error &error) +{ + const DatabaseSelection selection(uri, true, filter); + + using namespace std::placeholders; + const auto f = std::bind(AddSong, std::ref(storage), + playlist_path_utf8, _1, _2); + return db.Visit(selection, f, error); +} diff --git a/src/db/DatabasePlaylist.hxx b/src/db/DatabasePlaylist.hxx new file mode 100644 index 000000000..9dc3526bb --- /dev/null +++ b/src/db/DatabasePlaylist.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_PLAYLIST_HXX +#define MPD_DATABASE_PLAYLIST_HXX + +#include "Compiler.h" + +class Database; +class Storage; +class SongFilter; +class Error; + +gcc_nonnull(3,4) +bool +search_add_to_playlist(const Database &db, const Storage &storage, + const char *uri, const char *path_utf8, + const SongFilter *filter, + Error &error); + +#endif diff --git a/src/db/DatabasePlugin.hxx b/src/db/DatabasePlugin.hxx new file mode 100644 index 000000000..831101786 --- /dev/null +++ b/src/db/DatabasePlugin.hxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the db_plugin class. It describes a + * plugin API for databases of song metadata. + */ + +#ifndef MPD_DATABASE_PLUGIN_HXX +#define MPD_DATABASE_PLUGIN_HXX + +struct config_param; +class Error; +class EventLoop; +class DatabaseListener; +class Database; + +struct DatabasePlugin { + /** + * This plugin requires a #Storage instance. It contains only + * cached metadata from files in the #Storage. + */ + static constexpr unsigned FLAG_REQUIRE_STORAGE = 0x1; + + const char *name; + + unsigned flags; + + /** + * Allocates and configures a database. + */ + Database *(*create)(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + constexpr bool RequireStorage() const { + return flags & FLAG_REQUIRE_STORAGE; + } +}; + +#endif diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx new file mode 100644 index 000000000..498aedf97 --- /dev/null +++ b/src/db/DatabasePrint.cxx @@ -0,0 +1,221 @@ +/* + * 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 "DatabasePrint.hxx" +#include "Selection.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "TimePrint.hxx" +#include "client/Client.hxx" +#include "tag/Tag.hxx" +#include "LightSong.hxx" +#include "LightDirectory.hxx" +#include "PlaylistInfo.hxx" +#include "Interface.hxx" +#include "fs/Traits.hxx" + +#include <functional> + +static const char * +ApplyBaseFlag(const char *uri, bool base) +{ + if (base) + uri = PathTraitsUTF8::GetBase(uri); + return uri; +} + +static void +PrintDirectoryURI(Client &client, bool base, const LightDirectory &directory) +{ + client_printf(client, "directory: %s\n", + ApplyBaseFlag(directory.GetPath(), base)); +} + +static bool +PrintDirectoryBrief(Client &client, bool base, const LightDirectory &directory) +{ + if (!directory.IsRoot()) + PrintDirectoryURI(client, base, directory); + + return true; +} + +static bool +PrintDirectoryFull(Client &client, bool base, const LightDirectory &directory) +{ + if (!directory.IsRoot()) { + PrintDirectoryURI(client, base, directory); + + if (directory.mtime > 0) + time_print(client, "Last-Modified", directory.mtime); + } + + return true; +} + +static void +print_playlist_in_directory(Client &client, bool base, + const char *directory, + const char *name_utf8) +{ + if (base || directory == nullptr) + client_printf(client, "playlist: %s\n", + ApplyBaseFlag(name_utf8, base)); + else + client_printf(client, "playlist: %s/%s\n", + directory, name_utf8); +} + +static void +print_playlist_in_directory(Client &client, bool base, + const LightDirectory *directory, + const char *name_utf8) +{ + if (base || directory == nullptr || directory->IsRoot()) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory->GetPath(), name_utf8); +} + +static bool +PrintSongBrief(Client &client, bool base, const LightSong &song) +{ + song_print_uri(client, song, base); + + if (song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, base, + song.directory, song.uri); + + return true; +} + +static bool +PrintSongFull(Client &client, bool base, const LightSong &song) +{ + song_print_info(client, song, base); + + if (song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, base, + song.directory, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(Client &client, bool base, + const PlaylistInfo &playlist, + const LightDirectory &directory) +{ + print_playlist_in_directory(client, base, + &directory, playlist.name.c_str()); + return true; +} + +static bool +PrintPlaylistFull(Client &client, bool base, + const PlaylistInfo &playlist, + const LightDirectory &directory) +{ + print_playlist_in_directory(client, base, + &directory, playlist.name.c_str()); + + if (playlist.mtime > 0) + time_print(client, "Last-Modified", playlist.mtime); + + return true; +} + +bool +db_selection_print(Client &client, const DatabaseSelection &selection, + bool full, bool base, Error &error) +{ + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto d = selection.filter == nullptr + ? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief, + std::ref(client), base, _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + std::ref(client), base, _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + std::ref(client), base, _1, _2) + : VisitPlaylist(); + + return db->Visit(selection, d, s, p, error); +} + +static bool +PrintSongURIVisitor(Client &client, const LightSong &song) +{ + song_print_uri(client, song); + + return true; +} + +static bool +PrintUniqueTag(Client &client, TagType tag_type, + const Tag &tag) +{ + const char *value = tag.GetValue(tag_type); + assert(value != nullptr); + client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); + + for (const auto &item : tag) + if (item.type != tag_type) + client_printf(client, "%s: %s\n", + tag_item_names[item.type], item.value); + + return true; +} + +bool +PrintUniqueTags(Client &client, unsigned type, uint32_t group_mask, + const SongFilter *filter, + Error &error) +{ + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return false; + + const DatabaseSelection selection("", true, filter); + + if (type == LOCATE_TAG_FILE_TYPE) { + using namespace std::placeholders; + const auto f = std::bind(PrintSongURIVisitor, + std::ref(client), _1); + return db->Visit(selection, f, error); + } else { + assert(type < TAG_NUM_OF_ITEM_TYPES); + + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, std::ref(client), + (TagType)type, _1); + return db->VisitUniqueTags(selection, (TagType)type, + group_mask, + f, error); + } +} diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx new file mode 100644 index 000000000..2ab5e703d --- /dev/null +++ b/src/db/DatabasePrint.hxx @@ -0,0 +1,45 @@ +/* + * 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_DB_PRINT_H +#define MPD_DB_PRINT_H + +#include "Compiler.h" + +#include <stdint.h> + +class SongFilter; +struct DatabaseSelection; +class Client; +class Error; + +/** + * @param full print attributes/tags + * @param base print only base name of songs/directories? + */ +bool +db_selection_print(Client &client, const DatabaseSelection &selection, + bool full, bool base, Error &error); + +bool +PrintUniqueTags(Client &client, unsigned type, uint32_t group_mask, + const SongFilter *filter, + Error &error); + +#endif diff --git a/src/db/DatabaseQueue.cxx b/src/db/DatabaseQueue.cxx new file mode 100644 index 000000000..490678188 --- /dev/null +++ b/src/db/DatabaseQueue.cxx @@ -0,0 +1,53 @@ +/* + * 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 "DatabaseQueue.hxx" +#include "DatabaseSong.hxx" +#include "Interface.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "DetachedSong.hxx" + +#include <functional> + +static bool +AddToQueue(Partition &partition, const LightSong &song, Error &error) +{ + const Storage &storage = *partition.instance.storage; + unsigned id = + partition.playlist.AppendSong(partition.pc, + DatabaseDetachSong(storage, + song), + error); + return id != 0; +} + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + Error &error) +{ + const Database *db = partition.instance.GetDatabase(error); + if (db == nullptr) + return false; + + using namespace std::placeholders; + const auto f = std::bind(AddToQueue, std::ref(partition), _1, _2); + return db->Visit(selection, f, error); +} diff --git a/src/db/DatabaseQueue.hxx b/src/db/DatabaseQueue.hxx new file mode 100644 index 000000000..e653f973c --- /dev/null +++ b/src/db/DatabaseQueue.hxx @@ -0,0 +1,31 @@ +/* + * 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_DATABASE_QUEUE_HXX +#define MPD_DATABASE_QUEUE_HXX + +struct Partition; +struct DatabaseSelection; +class Error; + +bool +AddFromDatabase(Partition &partition, const DatabaseSelection &selection, + Error &error); + +#endif diff --git a/src/db/DatabaseSong.cxx b/src/db/DatabaseSong.cxx new file mode 100644 index 000000000..699213835 --- /dev/null +++ b/src/db/DatabaseSong.cxx @@ -0,0 +1,53 @@ +/* + * 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 "DatabaseSong.hxx" +#include "LightSong.hxx" +#include "Interface.hxx" +#include "DetachedSong.hxx" +#include "storage/StorageInterface.hxx" + +DetachedSong +DatabaseDetachSong(const Storage &storage, const LightSong &song) +{ + DetachedSong detached(song); + assert(detached.IsInDatabase()); + + if (!detached.HasRealURI()) { + const auto uri = song.GetURI(); + detached.SetRealURI(storage.MapUTF8(uri.c_str())); + } + + return detached; +} + +DetachedSong * +DatabaseDetachSong(const Database &db, const Storage &storage, const char *uri, + Error &error) +{ + const LightSong *tmp = db.GetSong(uri, error); + if (tmp == nullptr) + return nullptr; + + DetachedSong *song = new DetachedSong(DatabaseDetachSong(storage, + *tmp)); + db.ReturnSong(tmp); + return song; +} diff --git a/src/db/DatabaseSong.hxx b/src/db/DatabaseSong.hxx new file mode 100644 index 000000000..4daaf4047 --- /dev/null +++ b/src/db/DatabaseSong.hxx @@ -0,0 +1,50 @@ +/* + * 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_DATABASE_SONG_HXX +#define MPD_DATABASE_SONG_HXX + +#include "Compiler.h" + +struct LightSong; +class Database; +class Storage; +class DetachedSong; +class Error; + +/** + * "Detach" the #Song object, i.e. convert it to a #DetachedSong + * instance. + */ +gcc_pure +DetachedSong +DatabaseDetachSong(const Storage &storage, const LightSong &song); + +/** + * Look up a song in the database and convert it to a #DetachedSong + * instance. The caller is responsible for freeing it. + * + * @return nullptr on error + */ +gcc_malloc gcc_nonnull_all +DetachedSong * +DatabaseDetachSong(const Database &db, const Storage &storage, const char *uri, + Error &error); + +#endif diff --git a/src/db/Helpers.cxx b/src/db/Helpers.cxx new file mode 100644 index 000000000..089b2b17d --- /dev/null +++ b/src/db/Helpers.cxx @@ -0,0 +1,98 @@ +/* + * 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 "Helpers.hxx" +#include "Stats.hxx" +#include "Interface.hxx" +#include "LightSong.hxx" +#include "tag/Tag.hxx" + +#include <set> + +#include <string.h> + +struct StringLess { + gcc_pure + bool operator()(const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +typedef std::set<const char *, StringLess> StringSet; + +static void +StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, + const Tag &tag) +{ + if (tag.time > 0) + stats.total_duration += tag.time; + + for (const auto &item : tag) { + switch (item.type) { + case TAG_ARTIST: +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + artists.emplace(item.value); +#else + artists.insert(item.value); +#endif + break; + + case TAG_ALBUM: +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + albums.emplace(item.value); +#else + albums.insert(item.value); +#endif + break; + + default: + break; + } + } +} + +static bool +StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums, + const LightSong &song) +{ + ++stats.song_count; + + StatsVisitTag(stats, artists, albums, *song.tag); + + return true; +} + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) +{ + stats.Clear(); + + StringSet artists, albums; + using namespace std::placeholders; + const auto f = std::bind(StatsVisitSong, + std::ref(stats), std::ref(artists), + std::ref(albums), _1); + if (!db.Visit(selection, f, error)) + return false; + + stats.artist_count = artists.size(); + stats.album_count = albums.size(); + return true; +} diff --git a/src/db/Helpers.hxx b/src/db/Helpers.hxx new file mode 100644 index 000000000..651bac0e0 --- /dev/null +++ b/src/db/Helpers.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MEMORY_DATABASE_PLUGIN_HXX +#define MPD_MEMORY_DATABASE_PLUGIN_HXX + +class Error; +class Database; +struct DatabaseSelection; +struct DatabaseStats; + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, Error &error); + +#endif diff --git a/src/db/Interface.hxx b/src/db/Interface.hxx new file mode 100644 index 000000000..152928c79 --- /dev/null +++ b/src/db/Interface.hxx @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_INTERFACE_HXX +#define MPD_DATABASE_INTERFACE_HXX + +#include "Visitor.hxx" +#include "tag/TagType.h" +#include "Compiler.h" + +#include <time.h> +#include <stdint.h> + +struct DatabasePlugin; +struct DatabaseStats; +struct DatabaseSelection; +struct LightSong; +class Error; + +class Database { + const DatabasePlugin &plugin; + +public: + Database(const DatabasePlugin &_plugin) + :plugin(_plugin) {} + + /** + * Free instance data. + */ + virtual ~Database() {} + + const DatabasePlugin &GetPlugin() const { + return plugin; + } + + bool IsPlugin(const DatabasePlugin &other) const { + return &plugin == &other; + } + + /** + * Open the database. Read it into memory if applicable. + */ + virtual bool Open(gcc_unused Error &error) { + return true; + } + + /** + * Close the database, free allocated memory. + */ + virtual void Close() {} + + /** + * Look up a song (including tag data) in the database. When + * you don't need this anymore, call ReturnSong(). + * + * @param uri_utf8 the URI of the song within the music + * directory (UTF-8) + */ + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const = 0; + + /** + * Mark the song object as "unused". Call this on objects + * returned by GetSong(). + */ + virtual void ReturnSong(const LightSong *song) const = 0; + + /** + * Visit the selected entities. + */ + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const = 0; + + bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + Error &error) const { + return Visit(selection, visit_directory, visit_song, + VisitPlaylist(), error); + } + + bool Visit(const DatabaseSelection &selection, VisitSong visit_song, + Error &error) const { + return Visit(selection, VisitDirectory(), visit_song, error); + } + + /** + * Visit all unique tag values. + */ + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const = 0; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const = 0; + + /** + * Update the database. Returns the job id on success, 0 on + * error (with #Error set) and 0 if not implemented (#Error + * not set). + */ + virtual unsigned Update(gcc_unused const char *uri_utf8, + gcc_unused bool discard, + gcc_unused Error &error) { + /* not implemented: return 0 and don't set an Error */ + return 0; + } + + /** + * Returns the time stamp of the last database update. + * Returns 0 if that is not not known/available. + */ + gcc_pure + virtual time_t GetUpdateStamp() const = 0; +}; + +#endif diff --git a/src/db/LightDirectory.hxx b/src/db/LightDirectory.hxx new file mode 100644 index 000000000..d134151a4 --- /dev/null +++ b/src/db/LightDirectory.hxx @@ -0,0 +1,61 @@ +/* + * 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_LIGHT_DIRECTORY_HXX +#define MPD_LIGHT_DIRECTORY_HXX + +#include "Compiler.h" + +#include <string> + +#include <time.h> + +struct Tag; + +/** + * A reference to a directory. Unlike the #Directory class, this one + * consists only of pointers. It is supposed to be as light as + * possible while still providing all the information MPD has about a + * directory. This class does not manage any memory, and the pointers + * become invalid quickly. Only to be used to pass around during + * well-defined situations. + */ +struct LightDirectory { + const char *uri; + + time_t mtime; + + constexpr LightDirectory(const char *_uri, time_t _mtime) + :uri(_uri), mtime(_mtime) {} + + static constexpr LightDirectory Root() { + return LightDirectory("", 0); + } + + bool IsRoot() const { + return *uri == 0; + } + + gcc_pure + const char *GetPath() const { + return uri; + } +}; + +#endif diff --git a/src/db/LightSong.cxx b/src/db/LightSong.cxx new file mode 100644 index 000000000..af1e801f8 --- /dev/null +++ b/src/db/LightSong.cxx @@ -0,0 +1,33 @@ +/* + * 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 "LightSong.hxx" +#include "tag/Tag.hxx" + +double +LightSong::GetDuration() const +{ + if (end_ms > 0) + return (end_ms - start_ms) / 1000.0; + + if (tag->time <= 0) + return 0; + + return tag->time - start_ms / 1000.0; +} diff --git a/src/db/LightSong.hxx b/src/db/LightSong.hxx new file mode 100644 index 000000000..add9da855 --- /dev/null +++ b/src/db/LightSong.hxx @@ -0,0 +1,92 @@ +/* + * 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_LIGHT_SONG_HXX +#define MPD_LIGHT_SONG_HXX + +#include "Compiler.h" + +#include <string> + +#include <time.h> + +struct Tag; + +/** + * A reference to a song file. Unlike the other "Song" classes in the + * MPD code base, this one consists only of pointers. It is supposed + * to be as light as possible while still providing all the + * information MPD has about a song file. This class does not manage + * any memory, and the pointers become invalid quickly. Only to be + * used to pass around during well-defined situations. + */ +struct LightSong { + /** + * If this is not nullptr, then it denotes a prefix for the + * #uri. To build the full URI, join directory and uri with a + * slash. + */ + const char *directory; + + const char *uri; + + /** + * The "real" URI, the one to be used for opening the + * resource. If this attribute is nullptr, then #uri (and + * #directory) shall be used. + * + * This attribute is used for songs from the database which + * have a relative URI. + */ + const char *real_uri; + + /** + * Must not be nullptr. + */ + const Tag *tag; + + time_t mtime; + + /** + * Start of this sub-song within the file in milliseconds. + */ + unsigned start_ms; + + /** + * End of this sub-song within the file in milliseconds. + * Unused if zero. + */ + unsigned end_ms; + + gcc_pure + std::string GetURI() const { + if (directory == nullptr) + return std::string(uri); + + std::string result(directory); + result.push_back('/'); + result.append(uri); + return result; + } + + gcc_pure + double GetDuration() const; +}; + +#endif diff --git a/src/db/PlaylistInfo.hxx b/src/db/PlaylistInfo.hxx new file mode 100644 index 000000000..baa6cc361 --- /dev/null +++ b/src/db/PlaylistInfo.hxx @@ -0,0 +1,63 @@ +/* + * 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_PLAYLIST_INFO_HXX +#define MPD_PLAYLIST_INFO_HXX + +#include "check.h" +#include "Compiler.h" + +#include <string> + +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct PlaylistInfo { + /** + * The UTF-8 encoded name of the playlist file. + */ + std::string name; + + time_t mtime; + + class CompareName { + const char *const name; + + public: + constexpr CompareName(const char *_name):name(_name) {} + + gcc_pure + bool operator()(const PlaylistInfo &pi) const { + return pi.name.compare(name) == 0; + } + }; + + PlaylistInfo() = default; + + template<typename N> + PlaylistInfo(N &&_name, time_t _mtime) + :name(std::forward<N>(_name)), mtime(_mtime) {} + + PlaylistInfo(const PlaylistInfo &other) = delete; + PlaylistInfo(PlaylistInfo &&) = default; +}; + +#endif diff --git a/src/db/PlaylistVector.cxx b/src/db/PlaylistVector.cxx new file mode 100644 index 000000000..82a3519d9 --- /dev/null +++ b/src/db/PlaylistVector.cxx @@ -0,0 +1,66 @@ +/* + * 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 "PlaylistVector.hxx" +#include "db/DatabaseLock.hxx" + +#include <algorithm> + +#include <assert.h> + +PlaylistVector::iterator +PlaylistVector::find(const char *name) +{ + assert(holding_db_lock()); + assert(name != nullptr); + + return std::find_if(begin(), end(), + PlaylistInfo::CompareName(name)); +} + +bool +PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi) +{ + assert(holding_db_lock()); + + auto i = find(pi.name.c_str()); + if (i != end()) { + if (pi.mtime == i->mtime) + return false; + + i->mtime = pi.mtime; + } else + push_back(std::move(pi)); + + return true; +} + +bool +PlaylistVector::erase(const char *name) +{ + assert(holding_db_lock()); + + auto i = find(name); + if (i == end()) + return false; + + erase(i); + return true; +} diff --git a/src/db/PlaylistVector.hxx b/src/db/PlaylistVector.hxx new file mode 100644 index 000000000..accd4fd42 --- /dev/null +++ b/src/db/PlaylistVector.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_PLAYLIST_VECTOR_HXX +#define MPD_PLAYLIST_VECTOR_HXX + +#include "db/PlaylistInfo.hxx" +#include "Compiler.h" + +#include <list> + +class PlaylistVector : protected std::list<PlaylistInfo> { +protected: + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + iterator find(const char *name); + +public: + using std::list<PlaylistInfo>::empty; + using std::list<PlaylistInfo>::begin; + using std::list<PlaylistInfo>::end; + using std::list<PlaylistInfo>::push_back; + using std::list<PlaylistInfo>::erase; + + /** + * Caller must lock the #db_mutex. + * + * @return true if the vector or one of its items was modified + */ + bool UpdateOrInsert(PlaylistInfo &&pi); + + /** + * Caller must lock the #db_mutex. + */ + bool erase(const char *name); +}; + +#endif /* SONGVEC_H */ diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx deleted file mode 100644 index cb1bcdc6b..000000000 --- a/src/db/ProxyDatabasePlugin.cxx +++ /dev/null @@ -1,681 +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 "ProxyDatabasePlugin.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseSelection.hxx" -#include "DatabaseError.hxx" -#include "PlaylistVector.hxx" -#include "Directory.hxx" -#include "Song.hxx" -#include "SongFilter.hxx" -#include "Compiler.h" -#include "ConfigData.hxx" -#include "tag/TagBuilder.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "protocol/Ack.hxx" - -#include <mpd/client.h> - -#include <cassert> -#include <string> -#include <list> - -class ProxyDatabase : public Database { - std::string host; - unsigned port; - - struct mpd_connection *connection; - Directory *root; - - /* this is mutable because GetStats() must be "const" */ - mutable time_t update_stamp; - -public: - static Database *Create(const config_param ¶m, - Error &error); - - virtual bool Open(Error &error) override; - virtual void Close() override; - virtual Song *GetSong(const char *uri_utf8, - Error &error) const override; - virtual void ReturnSong(Song *song) const; - - virtual bool Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const override; - - virtual bool VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) const override; - - virtual bool GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, - Error &error) const override; - - virtual time_t GetUpdateStamp() const override { - return update_stamp; - } - -private: - bool Configure(const config_param ¶m, Error &error); - - bool Connect(Error &error); - bool CheckConnection(Error &error); - bool EnsureConnected(Error &error); -}; - -static constexpr Domain libmpdclient_domain("libmpdclient"); - -static constexpr struct { - TagType d; - enum mpd_tag_type s; -} tag_table[] = { - { TAG_ARTIST, MPD_TAG_ARTIST }, - { TAG_ALBUM, MPD_TAG_ALBUM }, - { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, - { TAG_TITLE, MPD_TAG_TITLE }, - { TAG_TRACK, MPD_TAG_TRACK }, - { TAG_NAME, MPD_TAG_NAME }, - { TAG_GENRE, MPD_TAG_GENRE }, - { TAG_DATE, MPD_TAG_DATE }, - { TAG_COMPOSER, MPD_TAG_COMPOSER }, - { TAG_PERFORMER, MPD_TAG_PERFORMER }, - { TAG_COMMENT, MPD_TAG_COMMENT }, - { TAG_DISC, MPD_TAG_DISC }, - { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, - { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, - { TAG_MUSICBRAINZ_ALBUMARTISTID, - MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, - { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, - { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } -}; - -gcc_const -static enum mpd_tag_type -Convert(TagType tag_type) -{ - for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) - if (i->d == tag_type) - return i->s; - - return MPD_TAG_COUNT; -} - -static bool -CheckError(struct mpd_connection *connection, Error &error) -{ - const auto code = mpd_connection_get_error(connection); - if (code == MPD_ERROR_SUCCESS) - return true; - - if (code == MPD_ERROR_SERVER) { - /* libmpdclient's "enum mpd_server_error" is the same - as our "enum ack" */ - const auto server_error = - mpd_connection_get_server_error(connection); - error.Set(ack_domain, (int)server_error, - mpd_connection_get_error_message(connection)); - } else { - error.Set(libmpdclient_domain, (int)code, - mpd_connection_get_error_message(connection)); - } - - mpd_connection_clear_error(connection); - return false; -} - -static bool -SendConstraints(mpd_connection *connection, const SongFilter::Item &item) -{ - switch (item.GetTag()) { - mpd_tag_type tag; - -#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) - case LOCATE_TAG_BASE_TYPE: - if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0) - /* requires MPD 0.18 */ - return true; - - return mpd_search_add_base_constraint(connection, - MPD_OPERATOR_DEFAULT, - item.GetValue().c_str()); -#endif - - case LOCATE_TAG_FILE_TYPE: - return mpd_search_add_uri_constraint(connection, - MPD_OPERATOR_DEFAULT, - item.GetValue().c_str()); - - case LOCATE_TAG_ANY_TYPE: - return mpd_search_add_any_tag_constraint(connection, - MPD_OPERATOR_DEFAULT, - item.GetValue().c_str()); - - default: - tag = Convert(TagType(item.GetTag())); - if (tag == MPD_TAG_COUNT) - return true; - - return mpd_search_add_tag_constraint(connection, - MPD_OPERATOR_DEFAULT, - tag, - item.GetValue().c_str()); - } -} - -static bool -SendConstraints(mpd_connection *connection, const SongFilter &filter) -{ - for (const auto &i : filter.GetItems()) - if (!SendConstraints(connection, i)) - return false; - - return true; -} - -static bool -SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) -{ -#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) - if (!selection.uri.empty() && - mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) { - /* requires MPD 0.18 */ - if (!mpd_search_add_base_constraint(connection, - MPD_OPERATOR_DEFAULT, - selection.uri.c_str())) - return false; - } -#endif - - if (selection.filter != nullptr && - !SendConstraints(connection, *selection.filter)) - return false; - - return true; -} - -Database * -ProxyDatabase::Create(const config_param ¶m, Error &error) -{ - ProxyDatabase *db = new ProxyDatabase(); - if (!db->Configure(param, error)) { - delete db; - db = nullptr; - } - - return db; -} - -bool -ProxyDatabase::Configure(const config_param ¶m, gcc_unused Error &error) -{ - host = param.GetBlockValue("host", ""); - port = param.GetBlockValue("port", 0u); - - return true; -} - -bool -ProxyDatabase::Open(Error &error) -{ - if (!Connect(error)) - return false; - - root = Directory::NewRoot(); - update_stamp = 0; - - return true; -} - -void -ProxyDatabase::Close() -{ - root->Free(); - - if (connection != nullptr) - mpd_connection_free(connection); -} - -bool -ProxyDatabase::Connect(Error &error) -{ - const char *_host = host.empty() ? nullptr : host.c_str(); - connection = mpd_connection_new(_host, port, 0); - if (connection == nullptr) { - error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM, - "Out of memory"); - return false; - } - - if (!CheckError(connection, error)) { - if (connection != nullptr) { - mpd_connection_free(connection); - connection = nullptr; - } - - return false; - } - - return true; -} - -bool -ProxyDatabase::CheckConnection(Error &error) -{ - assert(connection != nullptr); - - if (!mpd_connection_clear_error(connection)) { - mpd_connection_free(connection); - return Connect(error); - } - - return true; -} - -bool -ProxyDatabase::EnsureConnected(Error &error) -{ - return connection != nullptr - ? CheckConnection(error) - : Connect(error); -} - -static Song * -Convert(const struct mpd_song *song); - -Song * -ProxyDatabase::GetSong(const char *uri, Error &error) const -{ - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - if (!mpd_send_list_meta(connection, uri)) { - CheckError(connection, error); - return nullptr; - } - - struct mpd_song *song = mpd_recv_song(connection); - Song *song2 = song != nullptr - ? Convert(song) - : nullptr; - if (song != nullptr) - mpd_song_free(song); - if (!mpd_response_finish(connection)) { - if (song2 != nullptr) - song2->Free(); - - CheckError(connection, error); - return nullptr; - } - - if (song2 == nullptr) - error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); - - return song2; -} - -void -ProxyDatabase::ReturnSong(Song *song) const -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - assert(song->IsDetached()); - - song->Free(); -} - -static bool -Visit(struct mpd_connection *connection, const char *uri, - bool recursive, const SongFilter *filter, - VisitDirectory visit_directory, VisitSong visit_song, - VisitPlaylist visit_playlist, Error &error); - -static bool -Visit(struct mpd_connection *connection, - bool recursive, const SongFilter *filter, - const struct mpd_directory *directory, - VisitDirectory visit_directory, VisitSong visit_song, - VisitPlaylist visit_playlist, Error &error) -{ - const char *path = mpd_directory_get_path(directory); - - if (visit_directory) { - Directory *d = Directory::NewGeneric(path, &detached_root); - bool success = visit_directory(*d, error); - d->Free(); - if (!success) - return false; - } - - if (recursive && - !Visit(connection, path, recursive, filter, - visit_directory, visit_song, visit_playlist, error)) - return false; - - return true; -} - -static void -Copy(TagBuilder &tag, TagType d_tag, - const struct mpd_song *song, enum mpd_tag_type s_tag) -{ - - for (unsigned i = 0;; ++i) { - const char *value = mpd_song_get_tag(song, s_tag, i); - if (value == nullptr) - break; - - tag.AddItem(d_tag, value); - } -} - -static Song * -Convert(const struct mpd_song *song) -{ - Song *s = Song::NewDetached(mpd_song_get_uri(song)); - - s->mtime = mpd_song_get_last_modified(song); - -#if LIBMPDCLIENT_CHECK_VERSION(2,3,0) - s->start_ms = mpd_song_get_start(song) * 1000; - s->end_ms = mpd_song_get_end(song) * 1000; -#else - s->start_ms = s->end_ms = 0; -#endif - - TagBuilder tag; - tag.SetTime(mpd_song_get_duration(song)); - - for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) - Copy(tag, i->d, song, i->s); - - s->tag = tag.Commit(); - - return s; -} - -gcc_pure -static bool -Match(const SongFilter *filter, const Song &song) -{ - return filter == nullptr || filter->Match(song); -} - -static bool -Visit(const SongFilter *filter, - const struct mpd_song *song, - VisitSong visit_song, Error &error) -{ - if (!visit_song) - return true; - - Song *s = Convert(song); - bool success = !Match(filter, *s) || visit_song(*s, error); - s->Free(); - - return success; -} - -static bool -Visit(const struct mpd_playlist *playlist, - VisitPlaylist visit_playlist, Error &error) -{ - if (!visit_playlist) - return true; - - PlaylistInfo p(mpd_playlist_get_path(playlist), - mpd_playlist_get_last_modified(playlist)); - - return visit_playlist(p, detached_root, error); -} - -class ProxyEntity { - struct mpd_entity *entity; - -public: - explicit ProxyEntity(struct mpd_entity *_entity) - :entity(_entity) {} - - ProxyEntity(const ProxyEntity &other) = delete; - - ProxyEntity(ProxyEntity &&other) - :entity(other.entity) { - other.entity = nullptr; - } - - ~ProxyEntity() { - if (entity != nullptr) - mpd_entity_free(entity); - } - - ProxyEntity &operator=(const ProxyEntity &other) = delete; - - operator const struct mpd_entity *() const { - return entity; - } -}; - -static std::list<ProxyEntity> -ReceiveEntities(struct mpd_connection *connection) -{ - std::list<ProxyEntity> entities; - struct mpd_entity *entity; - while ((entity = mpd_recv_entity(connection)) != nullptr) - entities.push_back(ProxyEntity(entity)); - - mpd_response_finish(connection); - return entities; -} - -static bool -Visit(struct mpd_connection *connection, const char *uri, - bool recursive, const SongFilter *filter, - VisitDirectory visit_directory, VisitSong visit_song, - VisitPlaylist visit_playlist, Error &error) -{ - if (!mpd_send_list_meta(connection, uri)) - return CheckError(connection, error); - - std::list<ProxyEntity> entities(ReceiveEntities(connection)); - if (!CheckError(connection, error)) - return false; - - for (const auto &entity : entities) { - switch (mpd_entity_get_type(entity)) { - case MPD_ENTITY_TYPE_UNKNOWN: - break; - - case MPD_ENTITY_TYPE_DIRECTORY: - if (!Visit(connection, recursive, filter, - mpd_entity_get_directory(entity), - visit_directory, visit_song, visit_playlist, - error)) - return false; - break; - - case MPD_ENTITY_TYPE_SONG: - if (!Visit(filter, - mpd_entity_get_song(entity), visit_song, - error)) - return false; - break; - - case MPD_ENTITY_TYPE_PLAYLIST: - if (!Visit(mpd_entity_get_playlist(entity), - visit_playlist, error)) - return false; - break; - } - } - - return CheckError(connection, error); -} - -static bool -SearchSongs(struct mpd_connection *connection, - const DatabaseSelection &selection, - VisitSong visit_song, - Error &error) -{ - assert(selection.recursive); - assert(visit_song); - - const bool exact = selection.filter == nullptr || - !selection.filter->HasFoldCase(); - - if (!mpd_search_db_songs(connection, exact) || - !SendConstraints(connection, selection) || - !mpd_search_commit(connection)) - return CheckError(connection, error); - - bool result = true; - struct mpd_song *song; - while (result && (song = mpd_recv_song(connection)) != nullptr) { - Song *song2 = Convert(song); - mpd_song_free(song); - - result = !Match(selection.filter, *song2) || - visit_song(*song2, error); - song2->Free(); - } - - mpd_response_finish(connection); - return result && CheckError(connection, error); -} - -/** - * Check whether we can use the "base" constraint. Requires - * libmpdclient 2.9 and MPD 0.18. - */ -gcc_pure -static bool -ServerSupportsSearchBase(const struct mpd_connection *connection) -{ -#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) - return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0; -#else - (void)connection; - - return false; -#endif -} - -bool -ProxyDatabase::Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const -{ - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - if (!visit_directory && !visit_playlist && selection.recursive && - (ServerSupportsSearchBase(connection) - ? !selection.IsEmpty() - : selection.HasOtherThanBase())) - /* this optimized code path can only be used under - certain conditions */ - return ::SearchSongs(connection, selection, visit_song, error); - - /* fall back to recursive walk (slow!) */ - return ::Visit(connection, selection.uri.c_str(), - selection.recursive, selection.filter, - visit_directory, visit_song, visit_playlist, - error); -} - -bool -ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) const -{ - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - enum mpd_tag_type tag_type2 = Convert(tag_type); - if (tag_type2 == MPD_TAG_COUNT) { - error.Set(libmpdclient_domain, "Unsupported tag"); - return false; - } - - if (!mpd_search_db_tags(connection, tag_type2)) - return CheckError(connection, error); - - if (!SendConstraints(connection, selection)) - return CheckError(connection, error); - - if (!mpd_search_commit(connection)) - return CheckError(connection, error); - - bool result = true; - - struct mpd_pair *pair; - while (result && - (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) { - result = visit_string(pair->value, error); - mpd_return_pair(connection, pair); - } - - return mpd_response_finish(connection) && - CheckError(connection, error) && - result; -} - -bool -ProxyDatabase::GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, Error &error) const -{ - // TODO: match - (void)selection; - - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - struct mpd_stats *stats2 = - mpd_run_stats(connection); - if (stats2 == nullptr) - return CheckError(connection, error); - - update_stamp = (time_t)mpd_stats_get_db_update_time(stats2); - - stats.song_count = mpd_stats_get_number_of_songs(stats2); - stats.total_duration = mpd_stats_get_db_play_time(stats2); - stats.artist_count = mpd_stats_get_number_of_artists(stats2); - stats.album_count = mpd_stats_get_number_of_albums(stats2); - mpd_stats_free(stats2); - - return true; -} - -const DatabasePlugin proxy_db_plugin = { - "proxy", - ProxyDatabase::Create, -}; diff --git a/src/db/ProxyDatabasePlugin.hxx b/src/db/ProxyDatabasePlugin.hxx deleted file mode 100644 index 576c01c69..000000000 --- a/src/db/ProxyDatabasePlugin.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX -#define MPD_PROXY_DATABASE_PLUGIN_HXX - -struct DatabasePlugin; - -extern const DatabasePlugin proxy_db_plugin; - -#endif diff --git a/src/db/Registry.cxx b/src/db/Registry.cxx new file mode 100644 index 000000000..5681a9b82 --- /dev/null +++ b/src/db/Registry.cxx @@ -0,0 +1,48 @@ +/* + * 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 "DatabasePlugin.hxx" +#include "plugins/simple/SimpleDatabasePlugin.hxx" +#include "plugins/ProxyDatabasePlugin.hxx" +#include "plugins/upnp/UpnpDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif +#ifdef HAVE_LIBUPNP + &upnp_db_plugin, +#endif + nullptr +}; + +const DatabasePlugin * +GetDatabasePluginByName(const char *name) +{ + for (auto i = database_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/db/Registry.hxx b/src/db/Registry.hxx new file mode 100644 index 000000000..050842e21 --- /dev/null +++ b/src/db/Registry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_REGISTRY_HXX +#define MPD_DATABASE_REGISTRY_HXX + +#include "Compiler.h" + +struct DatabasePlugin; + +/** + * nullptr terminated list of all database plugins which were enabled at + * compile time. + */ +extern const DatabasePlugin *const database_plugins[]; + +gcc_pure +const DatabasePlugin * +GetDatabasePluginByName(const char *name); + +#endif diff --git a/src/db/Selection.cxx b/src/db/Selection.cxx new file mode 100644 index 000000000..a886916cb --- /dev/null +++ b/src/db/Selection.cxx @@ -0,0 +1,49 @@ +/* + * 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 "Selection.hxx" +#include "SongFilter.hxx" + +DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter) + :uri(_uri), recursive(_recursive), filter(_filter) +{ + /* optimization: if the caller didn't specify a base URI, pick + the one from SongFilter */ + if (uri.empty() && filter != nullptr) + uri = filter->GetBase(); +} + +bool +DatabaseSelection::IsEmpty() const +{ + return uri.empty() && (filter == nullptr || filter->IsEmpty()); +} + +bool +DatabaseSelection::HasOtherThanBase() const +{ + return filter != nullptr && filter->HasOtherThanBase(); +} + +bool +DatabaseSelection::Match(const LightSong &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/db/Selection.hxx b/src/db/Selection.hxx new file mode 100644 index 000000000..9802603fc --- /dev/null +++ b/src/db/Selection.hxx @@ -0,0 +1,60 @@ +/* + * 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_DATABASE_SELECTION_HXX +#define MPD_DATABASE_SELECTION_HXX + +#include "Compiler.h" + +#include <string> + +class SongFilter; +struct LightSong; + +struct DatabaseSelection { + /** + * The base URI of the search (UTF-8). Must not begin or end + * with a slash. An empty string searches the whole database. + */ + std::string uri; + + /** + * Recursively search all sub directories? + */ + bool recursive; + + const SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr); + + gcc_pure + bool IsEmpty() const; + + /** + * Does this selection contain constraints other than "base"? + */ + gcc_pure + bool HasOtherThanBase() const; + + gcc_pure + bool Match(const LightSong &song) const; +}; + +#endif diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx deleted file mode 100644 index e7ea7a62d..000000000 --- a/src/db/SimpleDatabasePlugin.cxx +++ /dev/null @@ -1,323 +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 "SimpleDatabasePlugin.hxx" -#include "DatabaseSelection.hxx" -#include "DatabaseHelpers.hxx" -#include "Directory.hxx" -#include "SongFilter.hxx" -#include "DatabaseSave.hxx" -#include "DatabaseLock.hxx" -#include "DatabaseError.hxx" -#include "TextFile.hxx" -#include "ConfigData.hxx" -#include "fs/FileSystem.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <sys/types.h> -#include <errno.h> - -static constexpr Domain simple_db_domain("simple_db"); - -Database * -SimpleDatabase::Create(const config_param ¶m, Error &error) -{ - SimpleDatabase *db = new SimpleDatabase(); - if (!db->Configure(param, error)) { - delete db; - db = nullptr; - } - - return db; -} - -bool -SimpleDatabase::Configure(const config_param ¶m, Error &error) -{ - path = param.GetBlockPath("path", error); - if (path.IsNull()) { - if (!error.IsDefined()) - error.Set(simple_db_domain, - "No \"path\" parameter specified"); - return false; - } - - path_utf8 = path.ToUTF8(); - - return true; -} - -bool -SimpleDatabase::Check(Error &error) const -{ - assert(!path.IsNull()); - - /* Check if the file exists */ - if (!CheckAccess(path, F_OK)) { - /* If the file doesn't exist, we can't check if we can write - * it, so we are going to try to get the directory path, and - * see if we can write a file in that */ - const auto dirPath = path.GetDirectoryName(); - - /* Check that the parent part of the path is a directory */ - struct stat st; - if (!StatFile(dirPath, st)) { - error.FormatErrno("Couldn't stat parent directory of db file " - "\"%s\"", - path_utf8.c_str()); - return false; - } - - if (!S_ISDIR(st.st_mode)) { - error.Format(simple_db_domain, - "Couldn't create db file \"%s\" because the " - "parent path is not a directory", - path_utf8.c_str()); - return false; - } - - /* Check if we can write to the directory */ - if (!CheckAccess(dirPath, X_OK | W_OK)) { - const int e = errno; - const std::string dirPath_utf8 = dirPath.ToUTF8(); - error.FormatErrno(e, "Can't create db file in \"%s\"", - dirPath_utf8.c_str()); - return false; - } - - return true; - } - - /* Path exists, now check if it's a regular file */ - struct stat st; - if (!StatFile(path, st)) { - error.FormatErrno("Couldn't stat db file \"%s\"", - path_utf8.c_str()); - return false; - } - - if (!S_ISREG(st.st_mode)) { - error.Format(simple_db_domain, - "db file \"%s\" is not a regular file", - path_utf8.c_str()); - return false; - } - - /* And check that we can write to it */ - if (!CheckAccess(path, R_OK | W_OK)) { - error.FormatErrno("Can't open db file \"%s\" for reading/writing", - path_utf8.c_str()); - return false; - } - - return true; -} - -bool -SimpleDatabase::Load(Error &error) -{ - assert(!path.IsNull()); - assert(root != nullptr); - - TextFile file(path); - if (file.HasFailed()) { - error.FormatErrno("Failed to open database file \"%s\"", - path_utf8.c_str()); - return false; - } - - if (!db_load_internal(file, *root, error)) - return false; - - struct stat st; - if (StatFile(path, st)) - mtime = st.st_mtime; - - return true; -} - -bool -SimpleDatabase::Open(Error &error) -{ - root = Directory::NewRoot(); - mtime = 0; - -#ifndef NDEBUG - borrowed_song_count = 0; -#endif - - if (!Load(error)) { - root->Free(); - - LogError(error); - error.Clear(); - - if (!Check(error)) - return false; - - root = Directory::NewRoot(); - } - - return true; -} - -void -SimpleDatabase::Close() -{ - assert(root != nullptr); - assert(borrowed_song_count == 0); - - root->Free(); -} - -Song * -SimpleDatabase::GetSong(const char *uri, Error &error) const -{ - assert(root != nullptr); - - db_lock(); - Song *song = root->LookupSong(uri); - db_unlock(); - if (song == nullptr) - error.Format(db_domain, DB_NOT_FOUND, - "No such song: %s", uri); -#ifndef NDEBUG - else - ++const_cast<unsigned &>(borrowed_song_count); -#endif - - return song; -} - -void -SimpleDatabase::ReturnSong(gcc_unused Song *song) const -{ - assert(song != nullptr); - -#ifndef NDEBUG - assert(borrowed_song_count > 0); - --const_cast<unsigned &>(borrowed_song_count); -#endif -} - -gcc_pure -const Directory * -SimpleDatabase::LookupDirectory(const char *uri) const -{ - assert(root != nullptr); - assert(uri != nullptr); - - ScopeDatabaseLock protect; - return root->LookupDirectory(uri); -} - -bool -SimpleDatabase::Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const -{ - ScopeDatabaseLock protect; - - const Directory *directory = root->LookupDirectory(selection.uri.c_str()); - if (directory == nullptr) { - if (visit_song) { - Song *song = root->LookupSong(selection.uri.c_str()); - if (song != nullptr) - return !selection.Match(*song) || - visit_song(*song, error); - } - - error.Set(db_domain, DB_NOT_FOUND, "No such directory"); - return false; - } - - if (selection.recursive && visit_directory && - !visit_directory(*directory, error)) - return false; - - return directory->Walk(selection.recursive, selection.filter, - visit_directory, visit_song, visit_playlist, - error); -} - -bool -SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) const -{ - return ::VisitUniqueTags(*this, selection, tag_type, visit_string, - error); -} - -bool -SimpleDatabase::GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, Error &error) const -{ - return ::GetStats(*this, selection, stats, error); -} - -bool -SimpleDatabase::Save(Error &error) -{ - db_lock(); - - LogDebug(simple_db_domain, "removing empty directories from DB"); - root->PruneEmpty(); - - LogDebug(simple_db_domain, "sorting DB"); - root->Sort(); - - db_unlock(); - - LogDebug(simple_db_domain, "writing DB"); - - FILE *fp = FOpen(path, FOpenMode::WriteText); - if (!fp) { - error.FormatErrno("unable to write to db file \"%s\"", - path_utf8.c_str()); - return false; - } - - db_save_internal(fp, *root); - - if (ferror(fp)) { - error.SetErrno("Failed to write to database file"); - fclose(fp); - return false; - } - - fclose(fp); - - struct stat st; - if (StatFile(path, st)) - mtime = st.st_mtime; - - return true; -} - -const DatabasePlugin simple_db_plugin = { - "simple", - SimpleDatabase::Create, -}; diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx deleted file mode 100644 index dfe981dd8..000000000 --- a/src/db/SimpleDatabasePlugin.hxx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SIMPLE_DATABASE_PLUGIN_HXX -#define MPD_SIMPLE_DATABASE_PLUGIN_HXX - -#include "DatabasePlugin.hxx" -#include "fs/AllocatedPath.hxx" -#include "Compiler.h" - -#include <cassert> - -struct Directory; - -class SimpleDatabase : public Database { - AllocatedPath path; - std::string path_utf8; - - Directory *root; - - time_t mtime; - -#ifndef NDEBUG - unsigned borrowed_song_count; -#endif - - SimpleDatabase() - :path(AllocatedPath::Null()) {} - -public: - gcc_pure - Directory *GetRoot() { - assert(root != NULL); - - return root; - } - - bool Save(Error &error); - - static Database *Create(const config_param ¶m, - Error &error); - - virtual bool Open(Error &error) override; - virtual void Close() override; - - virtual Song *GetSong(const char *uri_utf8, - Error &error) const override; - virtual void ReturnSong(Song *song) const; - - virtual bool Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const override; - - virtual bool VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) const override; - - virtual bool GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, - Error &error) const override; - - virtual time_t GetUpdateStamp() const override { - return mtime; - } - -protected: - bool Configure(const config_param ¶m, Error &error); - - gcc_pure - bool Check(Error &error) const; - - bool Load(Error &error); - - gcc_pure - const Directory *LookupDirectory(const char *uri) const; -}; - -extern const DatabasePlugin simple_db_plugin; - -#endif diff --git a/src/db/Stats.hxx b/src/db/Stats.hxx new file mode 100644 index 000000000..107af9d5f --- /dev/null +++ b/src/db/Stats.hxx @@ -0,0 +1,51 @@ +/* + * 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_DATABASE_STATS_HXX +#define MPD_DATABASE_STATS_HXX + +struct DatabaseStats { + /** + * Number of songs. + */ + unsigned song_count; + + /** + * Total duration of all songs (in seconds). + */ + unsigned long total_duration; + + /** + * Number of distinct artist names. + */ + unsigned artist_count; + + /** + * Number of distinct album names. + */ + unsigned album_count; + + void Clear() { + song_count = 0; + total_duration = 0; + artist_count = album_count = 0; + } +}; + +#endif diff --git a/src/db/UniqueTags.cxx b/src/db/UniqueTags.cxx new file mode 100644 index 000000000..589dc936d --- /dev/null +++ b/src/db/UniqueTags.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "UniqueTags.hxx" +#include "Interface.hxx" +#include "LightSong.hxx" +#include "tag/Set.hxx" + +#include <functional> + +#include <assert.h> + +static bool +CollectTags(TagSet &set, TagType tag_type, uint32_t group_mask, + const LightSong &song) +{ + assert(song.tag != nullptr); + const Tag &tag = *song.tag; + + set.InsertUnique(tag, tag_type, group_mask); + return true; +} + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) +{ + TagSet set; + + using namespace std::placeholders; + const auto f = std::bind(CollectTags, std::ref(set), + tag_type, group_mask, _1); + if (!db.Visit(selection, f, error)) + return false; + + for (const auto &value : set) + if (!visit_tag(value, error)) + return false; + + return true; +} diff --git a/src/db/UniqueTags.hxx b/src/db/UniqueTags.hxx new file mode 100644 index 000000000..61004fc56 --- /dev/null +++ b/src/db/UniqueTags.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DB_UNIQUE_TAGS_HXX +#define MPD_DB_UNIQUE_TAGS_HXX + +#include "Visitor.hxx" +#include "tag/TagType.h" + +#include <stdint.h> + +class Error; +class Database; +struct DatabaseSelection; + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error); + +#endif diff --git a/src/db/Uri.hxx b/src/db/Uri.hxx new file mode 100644 index 000000000..04960ba80 --- /dev/null +++ b/src/db/Uri.hxx @@ -0,0 +1,29 @@ +/* + * 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_DB_URI_HXX +#define MPD_DB_URI_HXX + +static inline bool +isRootDirectory(const char *name) +{ + return name[0] == 0 || (name[0] == '/' && name[1] == 0); +} + +#endif diff --git a/src/db/Visitor.hxx b/src/db/Visitor.hxx new file mode 100644 index 000000000..c524f1722 --- /dev/null +++ b/src/db/Visitor.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_VISITOR_HXX +#define MPD_DATABASE_VISITOR_HXX + +#include <functional> + +struct LightDirectory; +struct LightSong; +struct PlaylistInfo; +struct Tag; +class Error; + +typedef std::function<bool(const LightDirectory &, Error &)> VisitDirectory; +typedef std::function<bool(const LightSong &, Error &)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const LightDirectory &, + Error &)> VisitPlaylist; + +typedef std::function<bool(const Tag &, Error &)> VisitTag; + +#endif diff --git a/src/db/plugins/LazyDatabase.cxx b/src/db/plugins/LazyDatabase.cxx new file mode 100644 index 000000000..bc52395c5 --- /dev/null +++ b/src/db/plugins/LazyDatabase.cxx @@ -0,0 +1,108 @@ +/* + * 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 "LazyDatabase.hxx" +#include "db/Interface.hxx" + +#include <assert.h> + +LazyDatabase::LazyDatabase(Database *_db) + :Database(_db->GetPlugin()), db(_db), open(false) {} + +LazyDatabase::~LazyDatabase() +{ + assert(!open); + + delete db; +} + +bool +LazyDatabase::EnsureOpen(Error &error) const +{ + if (open) + return true; + + if (!db->Open(error)) + return false; + + open = true; + return true; +} + +void +LazyDatabase::Close() +{ + if (open) { + open = false; + db->Close(); + } +} + +const LightSong * +LazyDatabase::GetSong(const char *uri, Error &error) const +{ + return EnsureOpen(error) + ? db->GetSong(uri, error) + : nullptr; +} + +void +LazyDatabase::ReturnSong(const LightSong *song) const +{ + assert(open); + + db->ReturnSong(song); +} + +bool +LazyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + return EnsureOpen(error) && + db->Visit(selection, visit_directory, visit_song, + visit_playlist, error); +} + +bool +LazyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const +{ + return EnsureOpen(error) && + db->VisitUniqueTags(selection, tag_type, group_mask, visit_tag, + error); +} + +bool +LazyDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) const +{ + return EnsureOpen(error) && db->GetStats(selection, stats, error); +} + +time_t +LazyDatabase::GetUpdateStamp() const +{ + return open ? db->GetUpdateStamp() : 0; +} diff --git a/src/db/plugins/LazyDatabase.hxx b/src/db/plugins/LazyDatabase.hxx new file mode 100644 index 000000000..ae1b961d0 --- /dev/null +++ b/src/db/plugins/LazyDatabase.hxx @@ -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. + */ + +#ifndef MPD_LAZY_DATABASE_PLUGIN_HXX +#define MPD_LAZY_DATABASE_PLUGIN_HXX + +#include "db/Interface.hxx" +#include "Compiler.h" + +/** + * A wrapper for a #Database object that gets opened on the first + * database access. This works around daemonization problems with + * some plugins. + */ +class LazyDatabase final : public Database { + Database *const db; + + mutable bool open; + +public: + gcc_nonnull_all + LazyDatabase(Database *_db); + + virtual ~LazyDatabase(); + + virtual void Close() override; + + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + + virtual time_t GetUpdateStamp() const override; + +private: + bool EnsureOpen(Error &error) const; +}; + +#endif diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..0b358c0ba --- /dev/null +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -0,0 +1,835 @@ +/* + * 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 "ProxyDatabasePlugin.hxx" +#include "db/Interface.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/DatabaseListener.hxx" +#include "db/Selection.hxx" +#include "db/DatabaseError.hxx" +#include "db/PlaylistInfo.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "db/Stats.hxx" +#include "SongFilter.hxx" +#include "Compiler.h" +#include "config/ConfigData.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "protocol/Ack.hxx" +#include "event/SocketMonitor.hxx" +#include "event/IdleMonitor.hxx" +#include "Log.hxx" + +#include <mpd/client.h> +#include <mpd/async.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxySong : public LightSong { + Tag tag2; + +public: + explicit ProxySong(const mpd_song *song); +}; + +class AllocatedProxySong : public ProxySong { + mpd_song *const song; + +public: + explicit AllocatedProxySong(mpd_song *_song) + :ProxySong(_song), song(_song) {} + + ~AllocatedProxySong() { + mpd_song_free(song); + } +}; + +class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor { + DatabaseListener &listener; + + std::string host; + unsigned port; + + struct mpd_connection *connection; + + /* this is mutable because GetStats() must be "const" */ + mutable time_t update_stamp; + + /** + * The libmpdclient idle mask that was removed from the other + * MPD. This will be handled by the next OnIdle() call. + */ + unsigned idle_received; + + /** + * Is the #connection currently "idle"? That is, did we send + * the "idle" command to it? + */ + bool is_idle; + +public: + ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener) + :Database(proxy_db_plugin), + SocketMonitor(_loop), IdleMonitor(_loop), + listener(_listener) {} + + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + + virtual unsigned Update(const char *uri_utf8, bool discard, + Error &error) override; + + virtual time_t GetUpdateStamp() const override { + return update_stamp; + } + +private: + bool Configure(const config_param ¶m, Error &error); + + bool Connect(Error &error); + bool CheckConnection(Error &error); + bool EnsureConnected(Error &error); + + void Disconnect(); + + /* virtual methods from SocketMonitor */ + virtual bool OnSocketReady(unsigned flags) override; + + /* virtual methods from IdleMonitor */ + virtual void OnIdle() override; +}; + +static constexpr Domain libmpdclient_domain("libmpdclient"); + +static constexpr struct { + TagType d; + enum mpd_tag_type s; +} tag_table[] = { + { TAG_ARTIST, MPD_TAG_ARTIST }, + { TAG_ALBUM, MPD_TAG_ALBUM }, + { TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST }, + { TAG_TITLE, MPD_TAG_TITLE }, + { TAG_TRACK, MPD_TAG_TRACK }, + { TAG_NAME, MPD_TAG_NAME }, + { TAG_GENRE, MPD_TAG_GENRE }, + { TAG_DATE, MPD_TAG_DATE }, + { TAG_COMPOSER, MPD_TAG_COMPOSER }, + { TAG_PERFORMER, MPD_TAG_PERFORMER }, + { TAG_COMMENT, MPD_TAG_COMMENT }, + { TAG_DISC, MPD_TAG_DISC }, + { TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID }, + { TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID }, + { TAG_MUSICBRAINZ_ALBUMARTISTID, + MPD_TAG_MUSICBRAINZ_ALBUMARTISTID }, + { TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID }, + { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT } +}; + +static void +Copy(TagBuilder &tag, TagType d_tag, + const struct mpd_song *song, enum mpd_tag_type s_tag) +{ + + for (unsigned i = 0;; ++i) { + const char *value = mpd_song_get_tag(song, s_tag, i); + if (value == nullptr) + break; + + tag.AddItem(d_tag, value); + } +} + +ProxySong::ProxySong(const mpd_song *song) +{ + directory = nullptr; + uri = mpd_song_get_uri(song); + real_uri = nullptr; + tag = &tag2; + mtime = mpd_song_get_last_modified(song); + +#if LIBMPDCLIENT_CHECK_VERSION(2,3,0) + start_ms = mpd_song_get_start(song) * 1000; + end_ms = mpd_song_get_end(song) * 1000; +#else + start_ms = end_ms = 0; +#endif + + TagBuilder tag_builder; + tag_builder.SetTime(mpd_song_get_duration(song)); + + for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + Copy(tag_builder, i->d, song, i->s); + + tag_builder.Commit(tag2); +} + +gcc_const +static enum mpd_tag_type +Convert(TagType tag_type) +{ + for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + if (i->d == tag_type) + return i->s; + + return MPD_TAG_COUNT; +} + +static bool +CheckError(struct mpd_connection *connection, Error &error) +{ + const auto code = mpd_connection_get_error(connection); + if (code == MPD_ERROR_SUCCESS) + return true; + + if (code == MPD_ERROR_SERVER) { + /* libmpdclient's "enum mpd_server_error" is the same + as our "enum ack" */ + const auto server_error = + mpd_connection_get_server_error(connection); + error.Set(ack_domain, (int)server_error, + mpd_connection_get_error_message(connection)); + } else { + error.Set(libmpdclient_domain, (int)code, + mpd_connection_get_error_message(connection)); + } + + mpd_connection_clear_error(connection); + return false; +} + +static bool +SendConstraints(mpd_connection *connection, const SongFilter::Item &item) +{ + switch (item.GetTag()) { + mpd_tag_type tag; + +#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) + case LOCATE_TAG_BASE_TYPE: + if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0) + /* requires MPD 0.18 */ + return true; + + return mpd_search_add_base_constraint(connection, + MPD_OPERATOR_DEFAULT, + item.GetValue().c_str()); +#endif + + case LOCATE_TAG_FILE_TYPE: + return mpd_search_add_uri_constraint(connection, + MPD_OPERATOR_DEFAULT, + item.GetValue().c_str()); + + case LOCATE_TAG_ANY_TYPE: + return mpd_search_add_any_tag_constraint(connection, + MPD_OPERATOR_DEFAULT, + item.GetValue().c_str()); + + default: + tag = Convert(TagType(item.GetTag())); + if (tag == MPD_TAG_COUNT) + return true; + + return mpd_search_add_tag_constraint(connection, + MPD_OPERATOR_DEFAULT, + tag, + item.GetValue().c_str()); + } +} + +static bool +SendConstraints(mpd_connection *connection, const SongFilter &filter) +{ + for (const auto &i : filter.GetItems()) + if (!SendConstraints(connection, i)) + return false; + + return true; +} + +static bool +SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) +{ +#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) + if (!selection.uri.empty() && + mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) { + /* requires MPD 0.18 */ + if (!mpd_search_add_base_constraint(connection, + MPD_OPERATOR_DEFAULT, + selection.uri.c_str())) + return false; + } +#endif + + if (selection.filter != nullptr && + !SendConstraints(connection, *selection.filter)) + return false; + + return true; +} + +Database * +ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + ProxyDatabase *db = new ProxyDatabase(loop, listener); + if (!db->Configure(param, error)) { + delete db; + db = nullptr; + } + + return db; +} + +bool +ProxyDatabase::Configure(const config_param ¶m, gcc_unused Error &error) +{ + host = param.GetBlockValue("host", ""); + port = param.GetBlockValue("port", 0u); + + return true; +} + +bool +ProxyDatabase::Open(Error &error) +{ + if (!Connect(error)) + return false; + + update_stamp = 0; + + return true; +} + +void +ProxyDatabase::Close() +{ + if (connection != nullptr) + Disconnect(); +} + +bool +ProxyDatabase::Connect(Error &error) +{ + const char *_host = host.empty() ? nullptr : host.c_str(); + connection = mpd_connection_new(_host, port, 0); + if (connection == nullptr) { + error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM, + "Out of memory"); + return false; + } + + if (!CheckError(connection, error)) { + mpd_connection_free(connection); + connection = nullptr; + + return false; + } + + idle_received = unsigned(-1); + is_idle = false; + + SocketMonitor::Open(mpd_async_get_fd(mpd_connection_get_async(connection))); + IdleMonitor::Schedule(); + + return true; +} + +bool +ProxyDatabase::CheckConnection(Error &error) +{ + assert(connection != nullptr); + + if (!mpd_connection_clear_error(connection)) { + Disconnect(); + return Connect(error); + } + + if (is_idle) { + unsigned idle = mpd_run_noidle(connection); + if (idle == 0 && !CheckError(connection, error)) { + Disconnect(); + return false; + } + + idle_received |= idle; + is_idle = false; + IdleMonitor::Schedule(); + } + + return true; +} + +bool +ProxyDatabase::EnsureConnected(Error &error) +{ + return connection != nullptr + ? CheckConnection(error) + : Connect(error); +} + +void +ProxyDatabase::Disconnect() +{ + assert(connection != nullptr); + + IdleMonitor::Cancel(); + SocketMonitor::Steal(); + + mpd_connection_free(connection); + connection = nullptr; +} + +bool +ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) +{ + assert(connection != nullptr); + + if (!is_idle) { + // TODO: can this happen? + IdleMonitor::Schedule(); + return false; + } + + unsigned idle = (unsigned)mpd_recv_idle(connection, false); + if (idle == 0) { + Error error; + if (!CheckError(connection, error)) { + LogError(error); + Disconnect(); + return false; + } + } + + /* let OnIdle() handle this */ + idle_received |= idle; + is_idle = false; + IdleMonitor::Schedule(); + return false; +} + +void +ProxyDatabase::OnIdle() +{ + assert(connection != nullptr); + + /* handle previous idle events */ + + if (idle_received & MPD_IDLE_DATABASE) + listener.OnDatabaseModified(); + + idle_received = 0; + + /* send a new idle command to the other MPD */ + + if (is_idle) + // TODO: can this happen? + return; + + if (!mpd_send_idle_mask(connection, MPD_IDLE_DATABASE)) { + Error error; + if (!CheckError(connection, error)) + LogError(error); + + SocketMonitor::Steal(); + mpd_connection_free(connection); + connection = nullptr; + return; + } + + is_idle = true; + SocketMonitor::ScheduleRead(); +} + +const LightSong * +ProxyDatabase::GetSong(const char *uri, Error &error) const +{ + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + if (!mpd_send_list_meta(connection, uri)) { + CheckError(connection, error); + return nullptr; + } + + struct mpd_song *song = mpd_recv_song(connection); + if (!mpd_response_finish(connection) && + !CheckError(connection, error)) { + if (song != nullptr) + mpd_song_free(song); + return nullptr; + } + + if (song == nullptr) { + error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); + return nullptr; + } + + return new AllocatedProxySong(song); +} + +void +ProxyDatabase::ReturnSong(const LightSong *_song) const +{ + assert(_song != nullptr); + + AllocatedProxySong *song = (AllocatedProxySong *) + const_cast<LightSong *>(_song); + delete song; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, const SongFilter *filter, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error); + +static bool +Visit(struct mpd_connection *connection, + bool recursive, const SongFilter *filter, + const struct mpd_directory *directory, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error) +{ + const char *path = mpd_directory_get_path(directory); +#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) + time_t mtime = mpd_directory_get_last_modified(directory); +#else + time_t mtime = 0; +#endif + + if (visit_directory && + !visit_directory(LightDirectory(path, mtime), error)) + return false; + + if (recursive && + !Visit(connection, path, recursive, filter, + visit_directory, visit_song, visit_playlist, error)) + return false; + + return true; +} + +gcc_pure +static bool +Match(const SongFilter *filter, const LightSong &song) +{ + return filter == nullptr || filter->Match(song); +} + +static bool +Visit(const SongFilter *filter, + const mpd_song *_song, + VisitSong visit_song, Error &error) +{ + if (!visit_song) + return true; + + const ProxySong song(_song); + return !Match(filter, song) || visit_song(song, error); +} + +static bool +Visit(const struct mpd_playlist *playlist, + VisitPlaylist visit_playlist, Error &error) +{ + if (!visit_playlist) + return true; + + PlaylistInfo p(mpd_playlist_get_path(playlist), + mpd_playlist_get_last_modified(playlist)); + + return visit_playlist(p, LightDirectory::Root(), error); +} + +class ProxyEntity { + struct mpd_entity *entity; + +public: + explicit ProxyEntity(struct mpd_entity *_entity) + :entity(_entity) {} + + ProxyEntity(const ProxyEntity &other) = delete; + + ProxyEntity(ProxyEntity &&other) + :entity(other.entity) { + other.entity = nullptr; + } + + ~ProxyEntity() { + if (entity != nullptr) + mpd_entity_free(entity); + } + + ProxyEntity &operator=(const ProxyEntity &other) = delete; + + operator const struct mpd_entity *() const { + return entity; + } +}; + +static std::list<ProxyEntity> +ReceiveEntities(struct mpd_connection *connection) +{ + std::list<ProxyEntity> entities; + struct mpd_entity *entity; + while ((entity = mpd_recv_entity(connection)) != nullptr) + entities.push_back(ProxyEntity(entity)); + + mpd_response_finish(connection); + return entities; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, const SongFilter *filter, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error) +{ + if (!mpd_send_list_meta(connection, uri)) + return CheckError(connection, error); + + std::list<ProxyEntity> entities(ReceiveEntities(connection)); + if (!CheckError(connection, error)) + return false; + + for (const auto &entity : entities) { + switch (mpd_entity_get_type(entity)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + + case MPD_ENTITY_TYPE_DIRECTORY: + if (!Visit(connection, recursive, filter, + mpd_entity_get_directory(entity), + visit_directory, visit_song, visit_playlist, + error)) + return false; + break; + + case MPD_ENTITY_TYPE_SONG: + if (!Visit(filter, + mpd_entity_get_song(entity), visit_song, + error)) + return false; + break; + + case MPD_ENTITY_TYPE_PLAYLIST: + if (!Visit(mpd_entity_get_playlist(entity), + visit_playlist, error)) + return false; + break; + } + } + + return CheckError(connection, error); +} + +static bool +SearchSongs(struct mpd_connection *connection, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) +{ + assert(selection.recursive); + assert(visit_song); + + const bool exact = selection.filter == nullptr || + !selection.filter->HasFoldCase(); + + if (!mpd_search_db_songs(connection, exact) || + !SendConstraints(connection, selection) || + !mpd_search_commit(connection)) + return CheckError(connection, error); + + bool result = true; + struct mpd_song *song; + while (result && (song = mpd_recv_song(connection)) != nullptr) { + AllocatedProxySong song2(song); + + result = !Match(selection.filter, song2) || + visit_song(song2, error); + } + + mpd_response_finish(connection); + return result && CheckError(connection, error); +} + +/** + * Check whether we can use the "base" constraint. Requires + * libmpdclient 2.9 and MPD 0.18. + */ +gcc_pure +static bool +ServerSupportsSearchBase(const struct mpd_connection *connection) +{ +#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) + return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0; +#else + (void)connection; + + return false; +#endif +} + +bool +ProxyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + if (!visit_directory && !visit_playlist && selection.recursive && + (ServerSupportsSearchBase(connection) + ? !selection.IsEmpty() + : selection.HasOtherThanBase())) + /* this optimized code path can only be used under + certain conditions */ + return ::SearchSongs(connection, selection, visit_song, error); + + /* fall back to recursive walk (slow!) */ + return ::Visit(connection, selection.uri.c_str(), + selection.recursive, selection.filter, + visit_directory, visit_song, visit_playlist, + error); +} + +bool +ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + gcc_unused uint32_t group_mask, + VisitTag visit_tag, + Error &error) const +{ + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + enum mpd_tag_type tag_type2 = Convert(tag_type); + if (tag_type2 == MPD_TAG_COUNT) { + error.Set(libmpdclient_domain, "Unsupported tag"); + return false; + } + + if (!mpd_search_db_tags(connection, tag_type2)) + return CheckError(connection, error); + + if (!SendConstraints(connection, selection)) + return CheckError(connection, error); + + // TODO: use group_mask + + if (!mpd_search_commit(connection)) + return CheckError(connection, error); + + bool result = true; + + struct mpd_pair *pair; + while (result && + (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) { + TagBuilder tag; + tag.AddItem(tag_type, pair->value); + result = visit_tag(tag.Commit(), error); + mpd_return_pair(connection, pair); + } + + return mpd_response_finish(connection) && + CheckError(connection, error) && + result; +} + +bool +ProxyDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) const +{ + // TODO: match + (void)selection; + + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + struct mpd_stats *stats2 = + mpd_run_stats(connection); + if (stats2 == nullptr) + return CheckError(connection, error); + + update_stamp = (time_t)mpd_stats_get_db_update_time(stats2); + + stats.song_count = mpd_stats_get_number_of_songs(stats2); + stats.total_duration = mpd_stats_get_db_play_time(stats2); + stats.artist_count = mpd_stats_get_number_of_artists(stats2); + stats.album_count = mpd_stats_get_number_of_albums(stats2); + mpd_stats_free(stats2); + + return true; +} + +unsigned +ProxyDatabase::Update(const char *uri_utf8, bool discard, + Error &error) +{ + if (!EnsureConnected(error)) + return 0; + + unsigned id = discard + ? mpd_run_rescan(connection, uri_utf8) + : mpd_run_update(connection, uri_utf8); + if (id == 0) + CheckError(connection, error); + + return id; +} + +const DatabasePlugin proxy_db_plugin = { + "proxy", + DatabasePlugin::FLAG_REQUIRE_STORAGE, + ProxyDatabase::Create, +}; diff --git a/src/db/plugins/ProxyDatabasePlugin.hxx b/src/db/plugins/ProxyDatabasePlugin.hxx new file mode 100644 index 000000000..699d374b5 --- /dev/null +++ b/src/db/plugins/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX +#define MPD_PROXY_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin proxy_db_plugin; + +#endif diff --git a/src/db/plugins/simple/DatabaseSave.cxx b/src/db/plugins/simple/DatabaseSave.cxx new file mode 100644 index 000000000..c766843b6 --- /dev/null +++ b/src/db/plugins/simple/DatabaseSave.cxx @@ -0,0 +1,160 @@ +/* + * 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 "DatabaseSave.hxx" +#include "db/DatabaseLock.hxx" +#include "db/DatabaseError.hxx" +#include "Directory.hxx" +#include "DirectorySave.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "fs/io/TextFile.hxx" +#include "tag/Tag.hxx" +#include "tag/TagSettings.h" +#include "fs/Charset.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <string.h> +#include <stdlib.h> + +#define DIRECTORY_INFO_BEGIN "info_begin" +#define DIRECTORY_INFO_END "info_end" +#define DB_FORMAT_PREFIX "format: " +#define DIRECTORY_MPD_VERSION "mpd_version: " +#define DIRECTORY_FS_CHARSET "fs_charset: " +#define DB_TAG_PREFIX "tag: " + +static constexpr unsigned DB_FORMAT = 2; + +/** + * The oldest database format understood by this MPD version. + */ +static constexpr unsigned OLDEST_DB_FORMAT = 1; + +void +db_save_internal(BufferedOutputStream &os, const Directory &music_root) +{ + os.Format("%s\n", DIRECTORY_INFO_BEGIN); + os.Format(DB_FORMAT_PREFIX "%u\n", DB_FORMAT); + os.Format("%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + os.Format("%s%s\n", DIRECTORY_FS_CHARSET, GetFSCharset()); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (!ignore_tag_items[i]) + os.Format(DB_TAG_PREFIX "%s\n", tag_item_names[i]); + + os.Format("%s\n", DIRECTORY_INFO_END); + + directory_save(os, music_root); +} + +bool +db_load_internal(TextFile &file, Directory &music_root, Error &error) +{ + char *line; + unsigned format = 0; + bool found_charset = false, found_version = false; + bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; + + /* get initial info */ + line = file.ReadLine(); + if (line == nullptr || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { + error.Set(db_domain, "Database corrupted"); + return false; + } + + memset(tags, false, sizeof(tags)); + + while ((line = file.ReadLine()) != nullptr && + strcmp(line, DIRECTORY_INFO_END) != 0) { + if (StringStartsWith(line, DB_FORMAT_PREFIX)) { + format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); + } else if (StringStartsWith(line, DIRECTORY_MPD_VERSION)) { + if (found_version) { + error.Set(db_domain, "Duplicate version line"); + return false; + } + + found_version = true; + } else if (StringStartsWith(line, DIRECTORY_FS_CHARSET)) { + const char *new_charset; + + if (found_charset) { + error.Set(db_domain, "Duplicate charset line"); + return false; + } + + found_charset = true; + + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; + const char *const old_charset = GetFSCharset(); + if (*old_charset != 0 + && strcmp(new_charset, old_charset) != 0) { + error.Format(db_domain, + "Existing database has charset " + "\"%s\" instead of \"%s\"; " + "discarding database file", + new_charset, old_charset); + return false; + } + } else if (StringStartsWith(line, DB_TAG_PREFIX)) { + const char *name = line + sizeof(DB_TAG_PREFIX) - 1; + TagType tag = tag_name_parse(name); + if (tag == TAG_NUM_OF_ITEM_TYPES) { + error.Format(db_domain, + "Unrecognized tag '%s', " + "discarding database file", + name); + return false; + } + + tags[tag] = true; + } else { + error.Format(db_domain, "Malformed line: %s", line); + return false; + } + } + + if (format < OLDEST_DB_FORMAT || format > DB_FORMAT) { + error.Set(db_domain, + "Database format mismatch, " + "discarding database file"); + return false; + } + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + if (!ignore_tag_items[i] && !tags[i]) { + error.Set(db_domain, + "Tag list mismatch, " + "discarding database file"); + return false; + } + } + + LogDebug(db_domain, "reading DB"); + + db_lock(); + success = directory_load(file, music_root, error); + db_unlock(); + + return success; +} diff --git a/src/db/plugins/simple/DatabaseSave.hxx b/src/db/plugins/simple/DatabaseSave.hxx new file mode 100644 index 000000000..bb7f57115 --- /dev/null +++ b/src/db/plugins/simple/DatabaseSave.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DATABASE_SAVE_HXX +#define MPD_DATABASE_SAVE_HXX + +struct Directory; +class BufferedOutputStream; +class TextFile; +class Error; + +void +db_save_internal(BufferedOutputStream &os, const Directory &root); + +bool +db_load_internal(TextFile &file, Directory &root, Error &error); + +#endif diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx new file mode 100644 index 000000000..218652b03 --- /dev/null +++ b/src/db/plugins/simple/Directory.cxx @@ -0,0 +1,277 @@ +/* + * 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 "Directory.hxx" +#include "SongSort.hxx" +#include "Song.hxx" +#include "Mount.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "db/Uri.hxx" +#include "db/DatabaseLock.hxx" +#include "db/Interface.hxx" +#include "SongFilter.hxx" +#include "lib/icu/Collate.hxx" +#include "fs/Traits.hxx" +#include "util/Alloc.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +Directory::Directory(std::string &&_path_utf8, Directory *_parent) + :parent(_parent), + mtime(0), + inode(0), device(0), + path(std::move(_path_utf8)), + mounted_database(nullptr) +{ +} + +Directory::~Directory() +{ + delete mounted_database; + + songs.clear_and_dispose(Song::Disposer()); + children.clear_and_dispose(Disposer()); +} + +void +Directory::Delete() +{ + assert(holding_db_lock()); + assert(parent != nullptr); + + parent->children.erase_and_dispose(parent->children.iterator_to(*this), + Disposer()); +} + +const char * +Directory::GetName() const +{ + assert(!IsRoot()); + + return PathTraitsUTF8::GetBase(path.c_str()); +} + +Directory * +Directory::CreateChild(const char *name_utf8) +{ + assert(holding_db_lock()); + assert(name_utf8 != nullptr); + assert(*name_utf8 != 0); + + std::string path_utf8 = IsRoot() + ? std::string(name_utf8) + : PathTraitsUTF8::Build(GetPath(), name_utf8); + + Directory *child = new Directory(std::move(path_utf8), this); + children.push_back(*child); + return child; +} + +const Directory * +Directory::FindChild(const char *name) const +{ + assert(holding_db_lock()); + + for (const auto &child : children) + if (strcmp(child.GetName(), name) == 0) + return &child; + + return nullptr; +} + +void +Directory::PruneEmpty() +{ + assert(holding_db_lock()); + + for (auto child = children.begin(), end = children.end(); + child != end;) { + child->PruneEmpty(); + + if (child->IsEmpty()) + child = children.erase_and_dispose(child, Disposer()); + else + ++child; + } +} + +Directory::LookupResult +Directory::LookupDirectory(const char *uri) +{ + assert(holding_db_lock()); + assert(uri != nullptr); + + if (isRootDirectory(uri)) + return { this, nullptr }; + + char *duplicated = xstrdup(uri), *name = duplicated; + + Directory *d = this; + while (true) { + char *slash = strchr(name, '/'); + if (slash == name) + break; + + if (slash != nullptr) + *slash = '\0'; + + Directory *tmp = d->FindChild(name); + if (tmp == nullptr) + /* not found */ + break; + + d = tmp; + + if (slash == nullptr) { + /* found everything */ + name = nullptr; + break; + } + + name = slash + 1; + } + + free(duplicated); + + const char *rest = name == nullptr + ? nullptr + : uri + (name - duplicated); + + return { d, rest }; +} + +void +Directory::AddSong(Song *song) +{ + assert(holding_db_lock()); + assert(song != nullptr); + assert(song->parent == this); + + songs.push_back(*song); +} + +void +Directory::RemoveSong(Song *song) +{ + assert(holding_db_lock()); + assert(song != nullptr); + assert(song->parent == this); + + songs.erase(songs.iterator_to(*song)); +} + +const Song * +Directory::FindSong(const char *name_utf8) const +{ + assert(holding_db_lock()); + assert(name_utf8 != nullptr); + + for (auto &song : songs) { + assert(song.parent == this); + + if (strcmp(song.uri, name_utf8) == 0) + return &song; + } + + return nullptr; +} + +gcc_pure +static bool +directory_cmp(const Directory &a, const Directory &b) +{ + return IcuCollate(a.path.c_str(), b.path.c_str()) < 0; +} + +void +Directory::Sort() +{ + assert(holding_db_lock()); + + children.sort(directory_cmp); + song_list_sort(songs); + + for (auto &child : children) + child.Sort(); +} + +bool +Directory::Walk(bool recursive, const SongFilter *filter, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + assert(!error.IsDefined()); + + if (IsMount()) { + assert(IsEmpty()); + + /* TODO: eliminate this unlock/lock; it is necessary + because the child's SimpleDatabasePlugin::Visit() + call will lock it again */ + db_unlock(); + bool result = WalkMount(GetPath(), *mounted_database, + recursive, filter, + visit_directory, visit_song, + visit_playlist, + error); + db_lock(); + return result; + } + + if (visit_song) { + for (auto &song : songs){ + const LightSong song2 = song.Export(); + if ((filter == nullptr || filter->Match(song2)) && + !visit_song(song2, error)) + return false; + } + } + + if (visit_playlist) { + for (const PlaylistInfo &p : playlists) + if (!visit_playlist(p, Export(), error)) + return false; + } + + for (auto &child : children) { + if (visit_directory && + !visit_directory(child.Export(), error)) + return false; + + if (recursive && + !child.Walk(recursive, filter, + visit_directory, visit_song, visit_playlist, + error)) + return false; + } + + return true; +} + +LightDirectory +Directory::Export() const +{ + return LightDirectory(GetPath(), mtime); +} diff --git a/src/db/plugins/simple/Directory.hxx b/src/db/plugins/simple/Directory.hxx new file mode 100644 index 000000000..acef62143 --- /dev/null +++ b/src/db/plugins/simple/Directory.hxx @@ -0,0 +1,285 @@ +/* + * 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_DIRECTORY_HXX +#define MPD_DIRECTORY_HXX + +#include "check.h" +#include "Compiler.h" +#include "db/Visitor.hxx" +#include "db/PlaylistVector.hxx" +#include "Song.hxx" + +#include <boost/intrusive/list.hpp> + +#include <string> + +/** + * Virtual directory that is really an archive file or a folder inside + * the archive (special value for Directory::device). + */ +static constexpr unsigned DEVICE_INARCHIVE = -1; + +/** + * Virtual directory that is really a song file with one or more "sub" + * songs as specified by DecoderPlugin::container_scan() (special + * value for Directory::device). + */ +static constexpr unsigned DEVICE_CONTAINER = -2; + +struct db_visitor; +class SongFilter; +class Error; +class Database; + +struct Directory { + static constexpr auto link_mode = boost::intrusive::normal_link; + typedef boost::intrusive::link_mode<link_mode> LinkMode; + typedef boost::intrusive::list_member_hook<LinkMode> Hook; + + struct Disposer { + void operator()(Directory *directory) const { + delete directory; + } + }; + + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) in the root + * directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + Hook siblings; + + typedef boost::intrusive::member_hook<Directory, Hook, + &Directory::siblings> SiblingsHook; + typedef boost::intrusive::list<Directory, SiblingsHook, + boost::intrusive::constant_time_size<false>> List; + + /** + * A doubly linked list of child directories. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + List children; + + /** + * A doubly linked list of songs within this directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + SongList songs; + + PlaylistVector playlists; + + Directory *parent; + time_t mtime; + unsigned inode, device; + + std::string path; + + /** + * If this is not nullptr, then this directory does not really + * exist, but is a mount point for another #Database. + */ + Database *mounted_database; + +public: + Directory(std::string &&_path_utf8, Directory *_parent); + ~Directory(); + + /** + * Create a new root #Directory object. + */ + gcc_malloc + static Directory *NewRoot() { + return new Directory(std::string(), nullptr); + } + + bool IsMount() const { + return mounted_database != nullptr; + } + + /** + * Remove this #Directory object from its parent and free it. This + * must not be called with the root Directory. + * + * Caller must lock the #db_mutex. + */ + void Delete(); + + /** + * Create a new #Directory object as a child of the given one. + * + * Caller must lock the #db_mutex. + * + * @param name_utf8 the UTF-8 encoded name of the new sub directory + */ + gcc_malloc + Directory *CreateChild(const char *name_utf8); + + /** + * Caller must lock the #db_mutex. + */ + gcc_pure + const Directory *FindChild(const char *name) const; + + gcc_pure + Directory *FindChild(const char *name) { + const Directory *cthis = this; + return const_cast<Directory *>(cthis->FindChild(name)); + } + + /** + * Look up a sub directory, and create the object if it does not + * exist. + * + * Caller must lock the #db_mutex. + */ + Directory *MakeChild(const char *name_utf8) { + Directory *child = FindChild(name_utf8); + if (child == nullptr) + child = CreateChild(name_utf8); + return child; + } + + struct LookupResult { + /** + * The last directory that was found. If the given + * URI could not be resolved at all, then this is the + * root directory. + */ + Directory *directory; + + /** + * The remaining URI part (without leading slash) or + * nullptr if the given URI was consumed completely. + */ + const char *uri; + }; + + /** + * Looks up a directory by its relative URI. + * + * @param uri the relative URI + * @return the Directory, or nullptr if none was found + */ + gcc_pure + LookupResult LookupDirectory(const char *uri); + + gcc_pure + bool IsEmpty() const { + return children.empty() && + songs.empty() && + playlists.empty(); + } + + gcc_pure + const char *GetPath() const { + return path.c_str(); + } + + /** + * Returns the base name of the directory. + */ + gcc_pure + const char *GetName() const; + + /** + * Is this the root directory of the music database? + */ + gcc_pure + bool IsRoot() const { + return parent == nullptr; + } + + template<typename T> + void ForEachChildSafe(T &&t) { + const auto end = children.end(); + for (auto i = children.begin(), next = i; i != end; i = next) { + next = std::next(i); + t(*i); + } + } + + template<typename T> + void ForEachSongSafe(T &&t) { + const auto end = songs.end(); + for (auto i = songs.begin(), next = i; i != end; i = next) { + next = std::next(i); + t(*i); + } + } + + /** + * Look up a song in this directory by its name. + * + * Caller must lock the #db_mutex. + */ + gcc_pure + const Song *FindSong(const char *name_utf8) const; + + gcc_pure + Song *FindSong(const char *name_utf8) { + const Directory *cthis = this; + return const_cast<Song *>(cthis->FindSong(name_utf8)); + } + + /** + * Add a song object to this directory. Its "parent" attribute must + * be set already. + */ + void AddSong(Song *song); + + /** + * Remove a song object from this directory (which effectively + * invalidates the song object, because the "parent" attribute becomes + * stale), but does not free it. + */ + void RemoveSong(Song *song); + + /** + * Caller must lock the #db_mutex. + */ + void PruneEmpty(); + + /** + * Sort all directory entries recursively. + * + * Caller must lock the #db_mutex. + */ + void Sort(); + + /** + * Caller must lock #db_mutex. + */ + bool Walk(bool recursive, const SongFilter *match, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const; + + gcc_pure + LightDirectory Export() const; +}; + +#endif diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx new file mode 100644 index 000000000..e1650cbe8 --- /dev/null +++ b/src/db/plugins/simple/DirectorySave.cxx @@ -0,0 +1,207 @@ +/* + * 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 "DirectorySave.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "SongSave.hxx" +#include "DetachedSong.hxx" +#include "PlaylistDatabase.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "util/StringUtil.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <stddef.h> +#include <string.h> + +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_TYPE "type: " +#define DIRECTORY_MTIME "mtime: " +#define DIRECTORY_BEGIN "begin: " +#define DIRECTORY_END "end: " + +static constexpr Domain directory_domain("directory"); + +gcc_const +static const char * +DeviceToTypeString(unsigned device) +{ + switch (device) { + case DEVICE_INARCHIVE: + return "archive"; + + case DEVICE_CONTAINER: + return "container"; + + default: + return nullptr; + } +} + +gcc_pure +static unsigned +ParseTypeString(const char *type) +{ + if (strcmp(type, "archive") == 0) + return DEVICE_INARCHIVE; + else if (strcmp(type, "container") == 0) + return DEVICE_CONTAINER; + else + return 0; +} + +void +directory_save(BufferedOutputStream &os, const Directory &directory) +{ + if (!directory.IsRoot()) { + const char *type = DeviceToTypeString(directory.device); + if (type != nullptr) + os.Format(DIRECTORY_TYPE "%s\n", type); + + if (directory.mtime != 0) + os.Format(DIRECTORY_MTIME "%lu\n", + (unsigned long)directory.mtime); + + os.Format("%s%s\n", DIRECTORY_BEGIN, directory.GetPath()); + } + + for (const auto &child : directory.children) { + os.Format(DIRECTORY_DIR "%s\n", child.GetName()); + + if (!child.IsMount()) + directory_save(os, child); + + if (!os.Check()) + return; + } + + for (const auto &song : directory.songs) + song_save(os, song); + + playlist_vector_save(os, directory.playlists); + + if (!directory.IsRoot()) + os.Format(DIRECTORY_END "%s\n", directory.GetPath()); +} + +static bool +ParseLine(Directory &directory, const char *line) +{ + if (StringStartsWith(line, DIRECTORY_MTIME)) { + directory.mtime = + ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1); + } else if (StringStartsWith(line, DIRECTORY_TYPE)) { + directory.device = + ParseTypeString(line + sizeof(DIRECTORY_TYPE) - 1); + } else + return false; + + return true; +} + +static Directory * +directory_load_subdir(TextFile &file, Directory &parent, const char *name, + Error &error) +{ + bool success; + + if (parent.FindChild(name) != nullptr) { + error.Format(directory_domain, + "Duplicate subdirectory '%s'", name); + return nullptr; + } + + Directory *directory = parent.CreateChild(name); + + while (true) { + const char *line = file.ReadLine(); + if (line == nullptr) { + error.Set(directory_domain, "Unexpected end of file"); + directory->Delete(); + return nullptr; + } + + if (StringStartsWith(line, DIRECTORY_BEGIN)) + break; + + if (!ParseLine(*directory, line)) { + error.Format(directory_domain, + "Malformed line: %s", line); + directory->Delete(); + return nullptr; + } + } + + success = directory_load(file, *directory, error); + if (!success) { + directory->Delete(); + return nullptr; + } + + return directory; +} + +bool +directory_load(TextFile &file, Directory &directory, Error &error) +{ + const char *line; + + while ((line = file.ReadLine()) != nullptr && + !StringStartsWith(line, DIRECTORY_END)) { + if (StringStartsWith(line, DIRECTORY_DIR)) { + Directory *subdir = + directory_load_subdir(file, directory, + line + sizeof(DIRECTORY_DIR) - 1, + error); + if (subdir == nullptr) + return false; + } else if (StringStartsWith(line, SONG_BEGIN)) { + const char *name = line + sizeof(SONG_BEGIN) - 1; + + if (directory.FindSong(name) != nullptr) { + error.Format(directory_domain, + "Duplicate song '%s'", name); + return false; + } + + DetachedSong *song = song_load(file, name, error); + if (song == nullptr) + return false; + + directory.AddSong(Song::NewFrom(std::move(*song), + directory)); + delete song; + } else if (StringStartsWith(line, PLAYLIST_META_BEGIN)) { + const char *name = line + sizeof(PLAYLIST_META_BEGIN) - 1; + if (!playlist_metadata_load(file, directory.playlists, + name, error)) + return false; + } else { + error.Format(directory_domain, + "Malformed line: %s", line); + return false; + } + } + + return true; +} diff --git a/src/db/plugins/simple/DirectorySave.hxx b/src/db/plugins/simple/DirectorySave.hxx new file mode 100644 index 000000000..f464f9946 --- /dev/null +++ b/src/db/plugins/simple/DirectorySave.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DIRECTORY_SAVE_HXX +#define MPD_DIRECTORY_SAVE_HXX + +struct Directory; +class TextFile; +class BufferedOutputStream; +class Error; + +void +directory_save(BufferedOutputStream &os, const Directory &directory); + +bool +directory_load(TextFile &file, Directory &directory, Error &error); + +#endif diff --git a/src/db/plugins/simple/Mount.cxx b/src/db/plugins/simple/Mount.cxx new file mode 100644 index 000000000..96c7bbb5c --- /dev/null +++ b/src/db/plugins/simple/Mount.cxx @@ -0,0 +1,96 @@ +/* + * 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 "Mount.hxx" +#include "PrefixedLightSong.hxx" +#include "db/Selection.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "db/Interface.hxx" +#include "fs/Traits.hxx" +#include "util/Error.hxx" + +#include <string> + +struct PrefixedLightDirectory : LightDirectory { + std::string buffer; + + PrefixedLightDirectory(const LightDirectory &directory, + const char *base) + :LightDirectory(directory), + buffer(IsRoot() + ? std::string(base) + : PathTraitsUTF8::Build(base, uri)) { + uri = buffer.c_str(); + } +}; + +static bool +PrefixVisitDirectory(const char *base, const VisitDirectory &visit_directory, + const LightDirectory &directory, Error &error) +{ + return visit_directory(PrefixedLightDirectory(directory, base), error); +} + +static bool +PrefixVisitSong(const char *base, const VisitSong &visit_song, + const LightSong &song, Error &error) +{ + return visit_song(PrefixedLightSong(song, base), error); +} + +static bool +PrefixVisitPlaylist(const char *base, const VisitPlaylist &visit_playlist, + const PlaylistInfo &playlist, + const LightDirectory &directory, + Error &error) +{ + return visit_playlist(playlist, + PrefixedLightDirectory(directory, base), + error); +} + +bool +WalkMount(const char *base, const Database &db, + bool recursive, const SongFilter *filter, + const VisitDirectory &visit_directory, const VisitSong &visit_song, + const VisitPlaylist &visit_playlist, + Error &error) +{ + using namespace std::placeholders; + + VisitDirectory vd; + if (visit_directory) + vd = std::bind(PrefixVisitDirectory, + base, std::ref(visit_directory), _1, _2); + + VisitSong vs; + if (visit_song) + vs = std::bind(PrefixVisitSong, + base, std::ref(visit_song), _1, _2); + + VisitPlaylist vp; + if (visit_playlist) + vp = std::bind(PrefixVisitPlaylist, + base, std::ref(visit_playlist), _1, _2, _3); + + return db.Visit(DatabaseSelection("", recursive, filter), + vd, vs, vp, error); +} diff --git a/src/db/plugins/simple/Mount.hxx b/src/db/plugins/simple/Mount.hxx new file mode 100644 index 000000000..a4690114c --- /dev/null +++ b/src/db/plugins/simple/Mount.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_DB_SIMPLE_MOUNT_HXX +#define MPD_DB_SIMPLE_MOUNT_HXX + +#include "db/Visitor.hxx" + +class Database; +class SongFilter; +class Error; + +bool +WalkMount(const char *base, const Database &db, + bool recursive, const SongFilter *filter, + const VisitDirectory &visit_directory, const VisitSong &visit_song, + const VisitPlaylist &visit_playlist, + Error &error); + +#endif diff --git a/src/db/plugins/simple/PrefixedLightSong.hxx b/src/db/plugins/simple/PrefixedLightSong.hxx new file mode 100644 index 000000000..3664de001 --- /dev/null +++ b/src/db/plugins/simple/PrefixedLightSong.hxx @@ -0,0 +1,41 @@ +/* + * 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_DB_SIMPLE_PREFIXED_LIGHT_SONG_HXX +#define MPD_DB_SIMPLE_PREFIXED_LIGHT_SONG_HXX + +#include "check.h" +#include "db/LightSong.hxx" +#include "fs/Traits.hxx" + +#include <string> + +class PrefixedLightSong : public LightSong { + std::string buffer; + +public: + PrefixedLightSong(const LightSong &song, const char *base) + :LightSong(song), + buffer(PathTraitsUTF8::Build(base, GetURI().c_str())) { + uri = buffer.c_str(); + directory = nullptr; + } +}; + +#endif diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx new file mode 100644 index 000000000..9fd88ab83 --- /dev/null +++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx @@ -0,0 +1,538 @@ +/* + * 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 "SimpleDatabasePlugin.hxx" +#include "PrefixedLightSong.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/Selection.hxx" +#include "db/Helpers.hxx" +#include "db/UniqueTags.hxx" +#include "db/LightDirectory.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "SongFilter.hxx" +#include "DatabaseSave.hxx" +#include "db/DatabaseLock.hxx" +#include "db/DatabaseError.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "fs/io/FileOutputStream.hxx" +#include "fs/io/GzipOutputStream.hxx" +#include "config/ConfigData.hxx" +#include "fs/FileSystem.hxx" +#include "util/CharUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <errno.h> + +static constexpr Domain simple_db_domain("simple_db"); + +inline SimpleDatabase::SimpleDatabase() + :Database(simple_db_plugin), + path(AllocatedPath::Null()), +#ifdef HAVE_ZLIB + compress(true), +#endif + cache_path(AllocatedPath::Null()), + prefixed_light_song(nullptr) {} + +inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path, +#ifndef HAVE_ZLIB + gcc_unused +#endif + bool _compress) + :Database(simple_db_plugin), + path(std::move(_path)), + path_utf8(path.ToUTF8()), +#ifdef HAVE_ZLIB + compress(_compress), +#endif + cache_path(AllocatedPath::Null()), + prefixed_light_song(nullptr) { +} + +Database * +SimpleDatabase::Create(gcc_unused EventLoop &loop, + gcc_unused DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + SimpleDatabase *db = new SimpleDatabase(); + if (!db->Configure(param, error)) { + delete db; + db = nullptr; + } + + return db; +} + +bool +SimpleDatabase::Configure(const config_param ¶m, Error &error) +{ + path = param.GetBlockPath("path", error); + if (path.IsNull()) { + if (!error.IsDefined()) + error.Set(simple_db_domain, + "No \"path\" parameter specified"); + return false; + } + + path_utf8 = path.ToUTF8(); + + cache_path = param.GetBlockPath("cache_directory", error); + if (path.IsNull() && error.IsDefined()) + return false; + +#ifdef HAVE_ZLIB + compress = param.GetBlockValue("compress", compress); +#endif + + return true; +} + +bool +SimpleDatabase::Check(Error &error) const +{ + assert(!path.IsNull()); + + /* Check if the file exists */ + if (!CheckAccess(path)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + const auto dirPath = path.GetDirectoryName(); + + /* Check that the parent part of the path is a directory */ + struct stat st; + if (!StatFile(dirPath, st)) { + error.FormatErrno("Couldn't stat parent directory of db file " + "\"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISDIR(st.st_mode)) { + error.Format(simple_db_domain, + "Couldn't create db file \"%s\" because the " + "parent path is not a directory", + path_utf8.c_str()); + return false; + } + +#ifndef WIN32 + /* Check if we can write to the directory */ + if (!CheckAccess(dirPath, X_OK | W_OK)) { + const int e = errno; + const std::string dirPath_utf8 = dirPath.ToUTF8(); + error.FormatErrno(e, "Can't create db file in \"%s\"", + dirPath_utf8.c_str()); + return false; + } +#endif + return true; + } + + /* Path exists, now check if it's a regular file */ + struct stat st; + if (!StatFile(path, st)) { + error.FormatErrno("Couldn't stat db file \"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISREG(st.st_mode)) { + error.Format(simple_db_domain, + "db file \"%s\" is not a regular file", + path_utf8.c_str()); + return false; + } + +#ifndef WIN32 + /* And check that we can write to it */ + if (!CheckAccess(path, R_OK | W_OK)) { + error.FormatErrno("Can't open db file \"%s\" for reading/writing", + path_utf8.c_str()); + return false; + } +#endif + + return true; +} + +bool +SimpleDatabase::Load(Error &error) +{ + assert(!path.IsNull()); + assert(root != nullptr); + + TextFile file(path, error); + if (file.HasFailed()) + return false; + + if (!db_load_internal(file, *root, error) || !file.Check(error)) + return false; + + struct stat st; + if (StatFile(path, st)) + mtime = st.st_mtime; + + return true; +} + +bool +SimpleDatabase::Open(Error &error) +{ + assert(prefixed_light_song == nullptr); + + root = Directory::NewRoot(); + mtime = 0; + +#ifndef NDEBUG + borrowed_song_count = 0; +#endif + + if (!Load(error)) { + delete root; + + LogError(error); + error.Clear(); + + if (!Check(error)) + return false; + + root = Directory::NewRoot(); + } + + return true; +} + +void +SimpleDatabase::Close() +{ + assert(root != nullptr); + assert(prefixed_light_song == nullptr); + assert(borrowed_song_count == 0); + + delete root; +} + +const LightSong * +SimpleDatabase::GetSong(const char *uri, Error &error) const +{ + assert(root != nullptr); + assert(prefixed_light_song == nullptr); + assert(borrowed_song_count == 0); + + db_lock(); + + auto r = root->LookupDirectory(uri); + + if (r.directory->IsMount()) { + /* pass the request to the mounted database */ + db_unlock(); + + const LightSong *song = + r.directory->mounted_database->GetSong(r.uri, error); + if (song == nullptr) + return nullptr; + + prefixed_light_song = + new PrefixedLightSong(*song, r.directory->GetPath()); + return prefixed_light_song; + } + + if (r.uri == nullptr) { + /* it's a directory */ + db_unlock(); + error.Format(db_domain, DB_NOT_FOUND, + "No such song: %s", uri); + return nullptr; + } + + if (strchr(r.uri, '/') != nullptr) { + /* refers to a URI "below" the actual song */ + db_unlock(); + error.Format(db_domain, DB_NOT_FOUND, + "No such song: %s", uri); + return nullptr; + } + + const Song *song = r.directory->FindSong(r.uri); + db_unlock(); + if (song == nullptr) { + error.Format(db_domain, DB_NOT_FOUND, + "No such song: %s", uri); + return nullptr; + } + + light_song = song->Export(); + +#ifndef NDEBUG + ++borrowed_song_count; +#endif + + return &light_song; +} + +void +SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const +{ + assert(song != nullptr); + assert(song == &light_song || song == prefixed_light_song); + + delete prefixed_light_song; + prefixed_light_song = nullptr; + +#ifndef NDEBUG + if (song == &light_song) { + assert(borrowed_song_count > 0); + --borrowed_song_count; + } +#endif +} + +bool +SimpleDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + ScopeDatabaseLock protect; + + auto r = root->LookupDirectory(selection.uri.c_str()); + if (r.uri == nullptr) { + /* it's a directory */ + + if (selection.recursive && visit_directory && + !visit_directory(r.directory->Export(), error)) + return false; + + return r.directory->Walk(selection.recursive, selection.filter, + visit_directory, visit_song, + visit_playlist, + error); + } + + if (strchr(r.uri, '/') == nullptr) { + if (visit_song) { + Song *song = r.directory->FindSong(r.uri); + if (song != nullptr) { + const LightSong song2 = song->Export(); + return !selection.Match(song2) || + visit_song(song2, error); + } + } + } + + error.Set(db_domain, DB_NOT_FOUND, "No such directory"); + return false; +} + +bool +SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const +{ + return ::VisitUniqueTags(*this, selection, tag_type, group_mask, + visit_tag, + error); +} + +bool +SimpleDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) const +{ + return ::GetStats(*this, selection, stats, error); +} + +bool +SimpleDatabase::Save(Error &error) +{ + db_lock(); + + LogDebug(simple_db_domain, "removing empty directories from DB"); + root->PruneEmpty(); + + LogDebug(simple_db_domain, "sorting DB"); + root->Sort(); + + db_unlock(); + + LogDebug(simple_db_domain, "writing DB"); + + FileOutputStream fos(path, error); + if (!fos.IsDefined()) + return false; + + OutputStream *os = &fos; + +#ifdef HAVE_ZLIB + GzipOutputStream *gzip = nullptr; + if (compress) { + gzip = new GzipOutputStream(*os, error); + if (!gzip->IsDefined()) { + delete gzip; + return false; + } + + os = gzip; + } +#endif + + BufferedOutputStream bos(*os); + + db_save_internal(bos, *root); + + if (!bos.Flush(error)) { +#ifdef HAVE_ZLIB + delete gzip; +#endif + return false; + } + +#ifdef HAVE_ZLIB + if (gzip != nullptr) { + bool success = gzip->Flush(error); + delete gzip; + if (!success) + return false; + } +#endif + + if (!fos.Commit(error)) + return false; + + struct stat st; + if (StatFile(path, st)) + mtime = st.st_mtime; + + return true; +} + +bool +SimpleDatabase::Mount(const char *uri, Database *db, Error &error) +{ + assert(uri != nullptr); + assert(*uri != 0); + assert(db != nullptr); + + ScopeDatabaseLock protect; + + auto r = root->LookupDirectory(uri); + if (r.uri == nullptr) { + error.Format(db_domain, DB_CONFLICT, + "Already exists: %s", uri); + return nullptr; + } + + if (strchr(r.uri, '/') != nullptr) { + error.Format(db_domain, DB_NOT_FOUND, + "Parent not found: %s", uri); + return nullptr; + } + + Directory *mnt = r.directory->CreateChild(r.uri); + mnt->mounted_database = db; + return true; +} + +static constexpr bool +IsSafeChar(char ch) +{ + return IsAlphaNumericASCII(ch) || ch == '-' || ch == '_' || ch == '%'; +} + +static constexpr bool +IsUnsafeChar(char ch) +{ + return !IsSafeChar(ch); +} + +bool +SimpleDatabase::Mount(const char *local_uri, const char *storage_uri, + Error &error) +{ + if (cache_path.IsNull()) { + error.Format(db_domain, DB_NOT_FOUND, + "No 'cache_directory' configured"); + return nullptr; + } + + std::string name(storage_uri); + std::replace_if(name.begin(), name.end(), IsUnsafeChar, '_'); + +#ifndef HAVE_ZLIB + constexpr bool compress = false; +#endif + auto db = new SimpleDatabase(AllocatedPath::Build(cache_path, + name.c_str()), + compress); + if (!db->Open(error)) { + delete db; + return false; + } + + // TODO: update the new database instance? + + if (!Mount(local_uri, db, error)) { + db->Close(); + delete db; + return false; + } + + return true; +} + +Database * +SimpleDatabase::LockUmountSteal(const char *uri) +{ + ScopeDatabaseLock protect; + + auto r = root->LookupDirectory(uri); + if (r.uri != nullptr || !r.directory->IsMount()) + return nullptr; + + Database *db = r.directory->mounted_database; + r.directory->mounted_database = nullptr; + r.directory->Delete(); + + return db; +} + +bool +SimpleDatabase::Unmount(const char *uri) +{ + Database *db = LockUmountSteal(uri); + if (db == nullptr) + return false; + + db->Close(); + delete db; + return true; +} + +const DatabasePlugin simple_db_plugin = { + "simple", + DatabasePlugin::FLAG_REQUIRE_STORAGE, + SimpleDatabase::Create, +}; diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx new file mode 100644 index 000000000..7ba71e272 --- /dev/null +++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx @@ -0,0 +1,149 @@ +/* + * 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_SIMPLE_DATABASE_PLUGIN_HXX +#define MPD_SIMPLE_DATABASE_PLUGIN_HXX + +#include "check.h" +#include "db/Interface.hxx" +#include "fs/AllocatedPath.hxx" +#include "db/LightSong.hxx" +#include "Compiler.h" + +#include <cassert> + +struct config_param; +struct Directory; +struct DatabasePlugin; +class EventLoop; +class DatabaseListener; +class PrefixedLightSong; + +class SimpleDatabase : public Database { + AllocatedPath path; + std::string path_utf8; + +#ifdef HAVE_ZLIB + bool compress; +#endif + + /** + * The path where cache files for Mount() are located. + */ + AllocatedPath cache_path; + + Directory *root; + + time_t mtime; + + /** + * A buffer for GetSong() when prefixing the #LightSong + * instance from a mounted #Database. + */ + mutable PrefixedLightSong *prefixed_light_song; + + /** + * A buffer for GetSong(). + */ + mutable LightSong light_song; + +#ifndef NDEBUG + mutable unsigned borrowed_song_count; +#endif + + SimpleDatabase(); + + SimpleDatabase(AllocatedPath &&_path, bool _compress); + +public: + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + gcc_pure + Directory &GetRoot() { + assert(root != NULL); + + return *root; + } + + bool Save(Error &error); + + /** + * Returns true if there is a valid database file on the disk. + */ + bool FileExists() const { + return mtime > 0; + } + + /** + * @param db the #Database to be mounted; must be "open"; on + * success, this object gains ownership of the given #Database + */ + gcc_nonnull_all + bool Mount(const char *uri, Database *db, Error &error); + + gcc_nonnull_all + bool Mount(const char *local_uri, const char *storage_uri, + Error &error); + + gcc_nonnull_all + bool Unmount(const char *uri); + + /* virtual methods from class Database */ + virtual bool Open(Error &error) override; + virtual void Close() override; + + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + + virtual time_t GetUpdateStamp() const override { + return mtime; + } + +private: + bool Configure(const config_param ¶m, Error &error); + + gcc_pure + bool Check(Error &error) const; + + bool Load(Error &error); + + Database *LockUmountSteal(const char *uri); +}; + +extern const DatabasePlugin simple_db_plugin; + +#endif diff --git a/src/db/plugins/simple/Song.cxx b/src/db/plugins/simple/Song.cxx new file mode 100644 index 000000000..3bd3d8316 --- /dev/null +++ b/src/db/plugins/simple/Song.cxx @@ -0,0 +1,111 @@ +/* + * 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 "Song.hxx" +#include "Directory.hxx" +#include "tag/Tag.hxx" +#include "util/VarSize.hxx" +#include "DetachedSong.hxx" +#include "db/LightSong.hxx" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +inline Song::Song(const char *_uri, size_t uri_length, Directory &_parent) + :parent(&_parent), mtime(0), start_ms(0), end_ms(0) +{ + memcpy(uri, _uri, uri_length + 1); +} + +inline Song::~Song() +{ +} + +static Song * +song_alloc(const char *uri, Directory &parent) +{ + size_t uri_length; + + assert(uri); + uri_length = strlen(uri); + assert(uri_length); + + return NewVarSize<Song>(sizeof(Song::uri), + uri_length + 1, + uri, uri_length, parent); +} + +Song * +Song::NewFrom(DetachedSong &&other, Directory &parent) +{ + Song *song = song_alloc(other.GetURI(), parent); + song->tag = std::move(other.WritableTag()); + song->mtime = other.GetLastModified(); + song->start_ms = other.GetStartMS(); + song->end_ms = other.GetEndMS(); + return song; +} + +Song * +Song::NewFile(const char *path, Directory &parent) +{ + return song_alloc(path, parent); +} + +void +Song::Free() +{ + DeleteVarSize(this); +} + +std::string +Song::GetURI() const +{ + assert(*uri); + + if (parent->IsRoot()) + return std::string(uri); + else { + const char *path = parent->GetPath(); + + std::string result; + result.reserve(strlen(path) + 1 + strlen(uri)); + result.assign(path); + result.push_back('/'); + result.append(uri); + return result; + } +} + +LightSong +Song::Export() const +{ + LightSong dest; + dest.directory = parent->IsRoot() + ? nullptr : parent->GetPath(); + dest.uri = uri; + dest.real_uri = nullptr; + dest.tag = &tag; + dest.mtime = mtime; + dest.start_ms = start_ms; + dest.end_ms = end_ms; + return dest; +} diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx new file mode 100644 index 000000000..b2e85aa6b --- /dev/null +++ b/src/db/plugins/simple/Song.hxx @@ -0,0 +1,129 @@ +/* + * 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_SONG_HXX +#define MPD_SONG_HXX + +#include "tag/Tag.hxx" +#include "Compiler.h" + +#include <boost/intrusive/list.hpp> + +#include <string> + +#include <assert.h> +#include <time.h> + +struct LightSong; +struct Directory; +class DetachedSong; +class Storage; + +/** + * A song file inside the configured music directory. Internal + * #SimpleDatabase class. + */ +struct Song { + static constexpr auto link_mode = boost::intrusive::normal_link; + typedef boost::intrusive::link_mode<link_mode> LinkMode; + typedef boost::intrusive::list_member_hook<LinkMode> Hook; + + struct Disposer { + void operator()(Song *song) const { + song->Free(); + } + }; + + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) if this song is + * not in the database. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + Hook siblings; + + Tag tag; + + /** + * The #Directory that contains this song. Must be + * non-nullptr. directory this way. + */ + Directory *const parent; + + time_t mtime; + + /** + * Start of this sub-song within the file in milliseconds. + */ + unsigned start_ms; + + /** + * End of this sub-song within the file in milliseconds. + * Unused if zero. + */ + unsigned end_ms; + + /** + * The file name. + */ + char uri[sizeof(int)]; + + Song(const char *_uri, size_t uri_length, Directory &parent); + ~Song(); + + gcc_malloc + static Song *NewFrom(DetachedSong &&other, Directory &parent); + + /** allocate a new song with a local file name */ + gcc_malloc + static Song *NewFile(const char *path_utf8, Directory &parent); + + /** + * allocate a new song structure with a local file name and attempt to + * load its metadata. If all decoder plugin fail to read its meta + * data, nullptr is returned. + */ + gcc_malloc + static Song *LoadFile(Storage &storage, const char *name_utf8, + Directory &parent); + + void Free(); + + bool UpdateFile(Storage &storage); + bool UpdateFileInArchive(const Storage &storage); + + /** + * Returns the URI of the song in UTF-8 encoding, including its + * location within the music directory. + */ + gcc_pure + std::string GetURI() const; + + gcc_pure + LightSong Export() const; +}; + +typedef boost::intrusive::list<Song, + boost::intrusive::member_hook<Song, Song::Hook, + &Song::siblings>, + boost::intrusive::constant_time_size<false>> SongList; + +#endif diff --git a/src/db/plugins/simple/SongSort.cxx b/src/db/plugins/simple/SongSort.cxx new file mode 100644 index 000000000..4b7144937 --- /dev/null +++ b/src/db/plugins/simple/SongSort.cxx @@ -0,0 +1,108 @@ +/* + * 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 "SongSort.hxx" +#include "Song.hxx" +#include "tag/Tag.hxx" +#include "lib/icu/Collate.hxx" + +#include <stdlib.h> + +static int +compare_utf8_string(const char *a, const char *b) +{ + if (a == nullptr) + return b == nullptr ? 0 : -1; + + if (b == nullptr) + return 1; + + return IcuCollate(a, b); +} + +/** + * Compare two string tag values, ignoring case. Either one may be + * nullptr. + */ +static int +compare_string_tag_item(const Tag &a, const Tag &b, + TagType type) +{ + return compare_utf8_string(a.GetValue(type), + b.GetValue(type)); +} + +/** + * Compare two tag values which should contain an integer value + * (e.g. disc or track number). Either one may be nullptr. + */ +static int +compare_number_string(const char *a, const char *b) +{ + long ai = a == nullptr ? 0 : strtol(a, nullptr, 10); + long bi = b == nullptr ? 0 : strtol(b, nullptr, 10); + + if (ai <= 0) + return bi <= 0 ? 0 : -1; + + if (bi <= 0) + return 1; + + return ai - bi; +} + +static int +compare_tag_item(const Tag &a, const Tag &b, TagType type) +{ + return compare_number_string(a.GetValue(type), + b.GetValue(type)); +} + +/* Only used for sorting/searchin a songvec, not general purpose compares */ +gcc_pure +static bool +song_cmp(const Song &a, const Song &b) +{ + int ret; + + /* first sort by album */ + ret = compare_string_tag_item(a.tag, b.tag, TAG_ALBUM); + if (ret != 0) + return ret < 0; + + /* then sort by disc */ + ret = compare_tag_item(a.tag, b.tag, TAG_DISC); + if (ret != 0) + return ret < 0; + + /* then by track number */ + ret = compare_tag_item(a.tag, b.tag, TAG_TRACK); + if (ret != 0) + return ret < 0; + + /* still no difference? compare file name */ + return IcuCollate(a.uri, b.uri) < 0; +} + +void +song_list_sort(SongList &songs) +{ + songs.sort(song_cmp); +} diff --git a/src/db/plugins/simple/SongSort.hxx b/src/db/plugins/simple/SongSort.hxx new file mode 100644 index 000000000..2a0c4383b --- /dev/null +++ b/src/db/plugins/simple/SongSort.hxx @@ -0,0 +1,30 @@ +/* + * 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_SONG_SORT_HXX +#define MPD_SONG_SORT_HXX + +#include "Song.hxx" + +struct list_head; + +void +song_list_sort(SongList &songs); + +#endif diff --git a/src/db/plugins/upnp/ContentDirectoryService.cxx b/src/db/plugins/upnp/ContentDirectoryService.cxx new file mode 100644 index 000000000..c097f7644 --- /dev/null +++ b/src/db/plugins/upnp/ContentDirectoryService.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 "lib/upnp/ContentDirectoryService.hxx" +#include "lib/upnp/Domain.hxx" +#include "lib/upnp/ixmlwrap.hxx" +#include "lib/upnp/Action.hxx" +#include "Directory.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" + +#include <stdio.h> + +static bool +ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response, Error &error) +{ + const char *p = ixmlwrap::getFirstElementValue(response, "Result"); + if (p == nullptr) + p = ""; + + return dirbuf.parse(p, error); +} + +inline bool +ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, + const char *objectId, unsigned offset, + unsigned count, UPnPDirContent &dirbuf, + unsigned &didreadp, unsigned &totalp, + Error &error) const +{ + // Create request + char ofbuf[100], cntbuf[100]; + sprintf(ofbuf, "%u", offset); + sprintf(cntbuf, "%u", count); + // Some devices require an empty SortCriteria, else bad params + IXML_Document *request = + MakeActionHelper("Browse", m_serviceType.c_str(), + "ObjectID", objectId, + "BrowseFlag", "BrowseDirectChildren", + "Filter", "*", + "SortCriteria", "", + "StartingIndex", ofbuf, + "RequestedCount", cntbuf); + if (request == nullptr) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + const char *value = ixmlwrap::getFirstElementValue(response, "NumberReturned"); + didreadp = value != nullptr + ? ParseUnsigned(value) + : 0; + + value = ixmlwrap::getFirstElementValue(response, "TotalMatches"); + if (value != nullptr) + totalp = ParseUnsigned(value); + + bool success = ReadResultTag(dirbuf, response, error); + ixmlDocument_free(response); + return success; +} + +bool +ContentDirectoryService::readDir(UpnpClient_Handle handle, + const char *objectId, + UPnPDirContent &dirbuf, + Error &error) const +{ + unsigned offset = 0, total = -1, count; + + do { + if (!readDirSlice(handle, objectId, offset, m_rdreqcnt, dirbuf, + count, total, error)) + return false; + + offset += count; + } while (count > 0 && offset < total); + + return true; +} + +bool +ContentDirectoryService::search(UpnpClient_Handle hdl, + const char *objectId, + const char *ss, + UPnPDirContent &dirbuf, + Error &error) const +{ + unsigned offset = 0, total = -1, count; + + do { + char ofbuf[100]; + sprintf(ofbuf, "%d", offset); + + IXML_Document *request = + MakeActionHelper("Search", m_serviceType.c_str(), + "ContainerID", objectId, + "SearchCriteria", ss, + "Filter", "*", + "SortCriteria", "", + "StartingIndex", ofbuf, + "RequestedCount", "0"); // Setting a value here gets twonky into fits + if (request == 0) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + const char *value = + ixmlwrap::getFirstElementValue(response, "NumberReturned"); + count = value != nullptr + ? ParseUnsigned(value) + : 0; + + offset += count; + + value = ixmlwrap::getFirstElementValue(response, "TotalMatches"); + if (value != nullptr) + total = ParseUnsigned(value); + + bool success = ReadResultTag(dirbuf, response, error); + ixmlDocument_free(response); + if (!success) + return false; + } while (count > 0 && offset < total); + + return true; +} + +bool +ContentDirectoryService::getMetadata(UpnpClient_Handle hdl, + const char *objectId, + UPnPDirContent &dirbuf, + Error &error) const +{ + // Create request + IXML_Document *request = + MakeActionHelper("Browse", m_serviceType.c_str(), + "ObjectID", objectId, + "BrowseFlag", "BrowseMetadata", + "Filter", "*", + "SortCriteria", "", + "StartingIndex", "0", + "RequestedCount", "1"); + if (request == nullptr) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + bool success = ReadResultTag(dirbuf, response, error); + ixmlDocument_free(response); + return success; +} diff --git a/src/db/plugins/upnp/Directory.cxx b/src/db/plugins/upnp/Directory.cxx new file mode 100644 index 000000000..e43cd48a6 --- /dev/null +++ b/src/db/plugins/upnp/Directory.cxx @@ -0,0 +1,262 @@ +/* + * 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 "Directory.hxx" +#include "lib/upnp/Util.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Tags.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/TagTable.hxx" +#include "util/NumberParser.hxx" + +#include <algorithm> +#include <string> + +#include <string.h> + +UPnPDirContent::~UPnPDirContent() +{ + /* this destructor exists here just so it won't get inlined */ +} + +gcc_pure gcc_nonnull_all +static bool +CompareStringLiteral(const char *literal, const char *value, size_t length) +{ + return length == strlen(literal) && + memcmp(literal, value, length) == 0; +} + +gcc_pure +static UPnPDirObject::ItemClass +ParseItemClass(const char *name, size_t length) +{ + if (CompareStringLiteral("object.item.audioItem.musicTrack", + name, length)) + return UPnPDirObject::ItemClass::MUSIC; + else if (CompareStringLiteral("object.item.playlistItem", + name, length)) + return UPnPDirObject::ItemClass::PLAYLIST; + else + return UPnPDirObject::ItemClass::UNKNOWN; +} + +gcc_pure +static int +ParseDuration(const char *duration) +{ + char *endptr; + + unsigned result = ParseUnsigned(duration, &endptr); + if (endptr == duration || *endptr != ':') + return 0; + + result *= 60; + duration = endptr + 1; + result += ParseUnsigned(duration, &endptr); + if (endptr == duration || *endptr != ':') + return 0; + + result *= 60; + duration = endptr + 1; + result += ParseUnsigned(duration, &endptr); + if (endptr == duration || *endptr != 0) + return 0; + + return result; +} + +/** + * Transform titles to turn '/' into '_' to make them acceptable path + * elements. There is a very slight risk of collision in doing + * this. Twonky returns directory names (titles) like 'Artist/Album'. + */ +gcc_pure +static std::string +titleToPathElt(std::string &&s) +{ + std::replace(s.begin(), s.end(), '/', '_'); + return s; +} + +/** + * An XML parser which builds directory contents from DIDL lite input. + */ +class UPnPDirParser final : public CommonExpatParser { + UPnPDirContent &m_dir; + + enum { + NONE, + RES, + CLASS, + } state; + + /** + * If not equal to #TAG_NUM_OF_ITEM_TYPES, then we're + * currently reading an element containing a tag value. The + * value is being constructed in #value. + */ + TagType tag_type; + + /** + * The text inside the current element. + */ + std::string value; + + UPnPDirObject m_tobj; + TagBuilder tag; + +public: + UPnPDirParser(UPnPDirContent& dir) + :m_dir(dir), + state(NONE), + tag_type(TAG_NUM_OF_ITEM_TYPES) + { + } + +protected: + virtual void StartElement(const XML_Char *name, const XML_Char **attrs) + { + if (m_tobj.type != UPnPDirObject::Type::UNKNOWN && + tag_type == TAG_NUM_OF_ITEM_TYPES) { + tag_type = tag_table_lookup(upnp_tags, name); + if (tag_type != TAG_NUM_OF_ITEM_TYPES) + return; + } else { + assert(tag_type == TAG_NUM_OF_ITEM_TYPES); + } + + switch (name[0]) { + case 'c': + if (!strcmp(name, "container")) { + m_tobj.clear(); + m_tobj.type = UPnPDirObject::Type::CONTAINER; + + const char *id = GetAttribute(attrs, "id"); + if (id != nullptr) + m_tobj.m_id = id; + + const char *pid = GetAttribute(attrs, "parentID"); + if (pid != nullptr) + m_tobj.m_pid = pid; + } + break; + + case 'i': + if (!strcmp(name, "item")) { + m_tobj.clear(); + m_tobj.type = UPnPDirObject::Type::ITEM; + + const char *id = GetAttribute(attrs, "id"); + if (id != nullptr) + m_tobj.m_id = id; + + const char *pid = GetAttribute(attrs, "parentID"); + if (pid != nullptr) + m_tobj.m_pid = pid; + } + break; + + case 'r': + if (!strcmp(name, "res")) { + // <res protocolInfo="http-get:*:audio/mpeg:*" size="5171496" + // bitrate="24576" duration="00:03:35" sampleFrequency="44100" + // nrAudioChannels="2"> + + const char *duration = + GetAttribute(attrs, "duration"); + if (duration != nullptr) + tag.SetTime(ParseDuration(duration)); + + state = RES; + } + + break; + + case 'u': + if (strcmp(name, "upnp:class") == 0) + state = CLASS; + } + } + + bool checkobjok() { + if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() || + m_tobj.name.empty() || + (m_tobj.type == UPnPDirObject::Type::ITEM && + m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN)) + return false; + + return true; + } + + virtual void EndElement(const XML_Char *name) + { + if (tag_type != TAG_NUM_OF_ITEM_TYPES) { + assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN); + + tag.AddItem(tag_type, value.c_str()); + + if (tag_type == TAG_TITLE) + m_tobj.name = titleToPathElt(std::move(value)); + + value.clear(); + tag_type = TAG_NUM_OF_ITEM_TYPES; + return; + } + + if ((!strcmp(name, "container") || !strcmp(name, "item")) && + checkobjok()) { + tag.Commit(m_tobj.tag); + m_dir.objects.emplace_back(std::move(m_tobj)); + } + + state = NONE; + } + + virtual void CharacterData(const XML_Char *s, int len) + { + if (tag_type != TAG_NUM_OF_ITEM_TYPES) { + assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN); + + value.append(s, len); + return; + } + + switch (state) { + case NONE: + break; + + case RES: + m_tobj.url.assign(s, len); + break; + + case CLASS: + m_tobj.item_class = ParseItemClass(s, len); + break; + } + } +}; + +bool +UPnPDirContent::parse(const char *input, Error &error) +{ + UPnPDirParser parser(*this); + return parser.Parse(input, strlen(input), true, error); +} diff --git a/src/db/plugins/upnp/Directory.hxx b/src/db/plugins/upnp/Directory.hxx new file mode 100644 index 000000000..433979900 --- /dev/null +++ b/src/db/plugins/upnp/Directory.hxx @@ -0,0 +1,66 @@ +/* + * 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_UPNP_DIRECTORY_HXX +#define MPD_UPNP_DIRECTORY_HXX + +#include "Object.hxx" +#include "Compiler.h" + +#include <string> +#include <vector> + +class Error; + +/** + * Image of a MediaServer Directory Service container (directory), + * possibly containing items and subordinate containers. + */ +class UPnPDirContent { +public: + std::vector<UPnPDirObject> objects; + + ~UPnPDirContent(); + + gcc_pure + UPnPDirObject *FindObject(const char *name) { + for (auto &o : objects) + if (o.name == name) + return &o; + + return nullptr; + } + + /** + * Parse from DIDL-Lite XML data. + * + * Normally only used by ContentDirectoryService::readDir() + * This is cumulative: in general, the XML data is obtained in + * several documents corresponding to (offset,count) slices of the + * directory (container). parse() can be called repeatedly with + * the successive XML documents and will accumulate entries in the item + * and container vectors. This makes more sense if the different + * chunks are from the same container, but given that UPnP Ids are + * actually global, nothing really bad will happen if you mix + * up... + */ + bool parse(const char *didltext, Error &error); +}; + +#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/plugins/upnp/Object.cxx b/src/db/plugins/upnp/Object.cxx new file mode 100644 index 000000000..703fb0be4 --- /dev/null +++ b/src/db/plugins/upnp/Object.cxx @@ -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. + */ + +#include "Object.hxx" + +UPnPDirObject::~UPnPDirObject() +{ + /* this destructor exists here just so it won't get inlined */ +} diff --git a/src/db/plugins/upnp/Object.hxx b/src/db/plugins/upnp/Object.hxx new file mode 100644 index 000000000..16a66c774 --- /dev/null +++ b/src/db/plugins/upnp/Object.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_OBJECT_HXX +#define MPD_UPNP_OBJECT_HXX + +#include "tag/Tag.hxx" + +#include <string> + +/** + * UpnP Media Server directory entry, converted from XML data. + * + * This is a dumb data holder class, a struct with helpers. + */ +class UPnPDirObject { +public: + enum class Type { + UNKNOWN, + ITEM, + CONTAINER, + }; + + // There are actually several kinds of containers: + // object.container.storageFolder, object.container.person, + // object.container.playlistContainer etc., but they all seem to + // behave the same as far as we're concerned. Otoh, musicTrack + // items are special to us, and so should playlists, but I've not + // seen one of the latter yet (servers seem to use containers for + // playlists). + enum class ItemClass { + UNKNOWN, + MUSIC, + PLAYLIST, + }; + + std::string m_id; // ObjectId + std::string m_pid; // Parent ObjectId + std::string url; + + /** + * A copy of "dc:title" sanitized as a file name. + */ + std::string name; + + Type type; + ItemClass item_class; + + Tag tag; + + UPnPDirObject() = default; + UPnPDirObject(UPnPDirObject &&) = default; + + ~UPnPDirObject(); + + UPnPDirObject &operator=(UPnPDirObject &&) = default; + + void clear() + { + m_id.clear(); + m_pid.clear(); + url.clear(); + type = Type::UNKNOWN; + item_class = ItemClass::UNKNOWN; + tag.Clear(); + } +}; + +#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/plugins/upnp/Tags.cxx b/src/db/plugins/upnp/Tags.cxx new file mode 100644 index 000000000..fd65df4d0 --- /dev/null +++ b/src/db/plugins/upnp/Tags.cxx @@ -0,0 +1,33 @@ +/* + * 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 "Tags.hxx" +#include "tag/TagTable.hxx" + +const struct tag_table upnp_tags[] = { + { "upnp:artist", TAG_ARTIST }, + { "upnp:album", TAG_ALBUM }, + { "upnp:originalTrackNumber", TAG_TRACK }, + { "upnp:genre", TAG_GENRE }, + { "dc:title", TAG_TITLE }, + + /* sentinel */ + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/db/plugins/upnp/Tags.hxx b/src/db/plugins/upnp/Tags.hxx new file mode 100644 index 000000000..ec6d18478 --- /dev/null +++ b/src/db/plugins/upnp/Tags.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_UPNP_TAGS_HXX +#define MPD_UPNP_TAGS_HXX + +/** + * Map UPnP property names to MPD tags. + */ +extern const struct tag_table upnp_tags[]; + +#endif diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx new file mode 100644 index 000000000..66951f402 --- /dev/null +++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx @@ -0,0 +1,786 @@ +/* + * 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 "UpnpDatabasePlugin.hxx" +#include "Directory.hxx" +#include "Tags.hxx" +#include "lib/upnp/Domain.hxx" +#include "lib/upnp/ClientInit.hxx" +#include "lib/upnp/Discovery.hxx" +#include "lib/upnp/ContentDirectoryService.hxx" +#include "lib/upnp/Util.hxx" +#include "db/Interface.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/Selection.hxx" +#include "db/DatabaseError.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "db/Stats.hxx" +#include "config/ConfigData.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/TagTable.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Traits.hxx" +#include "Log.hxx" +#include "SongFilter.hxx" + +#include <string> +#include <vector> +#include <set> + +#include <assert.h> +#include <string.h> + +static const char *const rootid = "0"; + +class UpnpSong : public LightSong { + std::string uri2, real_uri2; + + Tag tag2; + +public: + UpnpSong(UPnPDirObject &&object, std::string &&_uri) + :uri2(std::move(_uri)), + real_uri2(std::move(object.url)), + tag2(std::move(object.tag)) { + directory = nullptr; + uri = uri2.c_str(); + real_uri = real_uri2.c_str(); + tag = &tag2; + mtime = 0; + start_ms = end_ms = 0; + } +}; + +class UpnpDatabase : public Database { + UpnpClient_Handle handle; + UPnPDeviceDirectory *discovery; + +public: + UpnpDatabase():Database(upnp_db_plugin) {} + + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *song) const; + + virtual bool Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const override; + + virtual bool VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, uint32_t group_mask, + VisitTag visit_tag, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + virtual time_t GetUpdateStamp() const {return 0;} + +protected: + bool Configure(const config_param ¶m, Error &error); + +private: + bool VisitServer(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const; + + /** + * Run an UPnP search according to MPD parameters, and + * visit_song the results. + */ + bool SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) const; + + bool SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + UPnPDirContent& dirbuf, + Error &error) const; + + bool Namei(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + UPnPDirObject &dirent, + Error &error) const; + + /** + * Take server and objid, return metadata. + */ + bool ReadNode(const ContentDirectoryService &server, + const char *objid, UPnPDirObject& dirent, + Error &error) const; + + /** + * Get the path for an object Id. This works much like pwd, + * except easier cause our inodes have a parent id. Not used + * any more actually (see comments in SearchSongs). + */ + bool BuildPath(const ContentDirectoryService &server, + const UPnPDirObject& dirent, std::string &idpath, + Error &error) const; +}; + +Database * +UpnpDatabase::Create(gcc_unused EventLoop &loop, + gcc_unused DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + UpnpDatabase *db = new UpnpDatabase(); + if (!db->Configure(param, error)) { + delete db; + return nullptr; + } + + /* libupnp loses its ability to receive multicast messages + apparently due to daemonization; using the LazyDatabase + wrapper works around this problem */ + return db; +} + +inline bool +UpnpDatabase::Configure(const config_param &, Error &) +{ + return true; +} + +bool +UpnpDatabase::Open(Error &error) +{ + if (!UpnpClientGlobalInit(handle, error)) + return false; + + discovery = new UPnPDeviceDirectory(handle); + if (!discovery->Start(error)) { + delete discovery; + UpnpClientGlobalFinish(); + return false; + } + + return true; +} + +void +UpnpDatabase::Close() +{ + delete discovery; + UpnpClientGlobalFinish(); +} + +void +UpnpDatabase::ReturnSong(const LightSong *_song) const +{ + assert(_song != nullptr); + + UpnpSong *song = (UpnpSong *)const_cast<LightSong *>(_song); + delete song; +} + +// Get song info by path. We can receive either the id path, or the titles +// one +const LightSong * +UpnpDatabase::GetSong(const char *uri, Error &error) const +{ + auto vpath = stringToTokens(uri, "/", true); + if (vpath.size() < 2) { + error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); + return nullptr; + } + + ContentDirectoryService server; + if (!discovery->getServer(vpath.front().c_str(), server, error)) + return nullptr; + + vpath.pop_front(); + + UPnPDirObject dirent; + if (vpath.front() != rootid) { + if (!Namei(server, vpath, dirent, error)) + return nullptr; + } else { + if (!ReadNode(server, vpath.back().c_str(), dirent, + error)) + return nullptr; + } + + return new UpnpSong(std::move(dirent), uri); +} + +/** + * Double-quote a string, adding internal backslash escaping. + */ +static void +dquote(std::string &out, const char *in) +{ + out.push_back('"'); + + for (; *in != 0; ++in) { + switch(*in) { + case '\\': + case '"': + out.push_back('\\'); + break; + } + + out.push_back(*in); + } + + out.push_back('"'); +} + +// Run an UPnP search, according to MPD parameters. Return results as +// UPnP items +bool +UpnpDatabase::SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + UPnPDirContent &dirbuf, + Error &error) const +{ + const SongFilter *filter = selection.filter; + if (selection.filter == nullptr) + return true; + + std::list<std::string> searchcaps; + if (!server.getSearchCapabilities(handle, searchcaps, error)) + return false; + + if (searchcaps.empty()) + return true; + + std::string cond; + for (const auto &item : filter->GetItems()) { + switch (auto tag = item.GetTag()) { + case LOCATE_TAG_ANY_TYPE: + { + if (!cond.empty()) { + cond += " and "; + } + cond += '('; + bool first(true); + for (const auto& cap : searchcaps) { + if (first) + first = false; + else + cond += " or "; + cond += cap; + if (item.GetFoldCase()) { + cond += " contains "; + } else { + cond += " = "; + } + dquote(cond, item.GetValue().c_str()); + } + cond += ')'; + } + break; + + default: + /* Unhandled conditions like + LOCATE_TAG_BASE_TYPE or + LOCATE_TAG_FILE_TYPE won't have a + corresponding upnp prop, so they will be + skipped */ + if (tag == TAG_ALBUM_ARTIST) + tag = TAG_ARTIST; + + // TODO: support LOCATE_TAG_ANY_TYPE etc. + const char *name = tag_table_lookup(upnp_tags, + TagType(tag)); + if (name == nullptr) + continue; + + if (!cond.empty()) { + cond += " and "; + } + cond += name; + + /* FoldCase doubles up as contains/equal + switch. UpNP search is supposed to be + case-insensitive, but at least some servers + have the same convention as mpd (e.g.: + minidlna) */ + if (item.GetFoldCase()) { + cond += " contains "; + } else { + cond += " = "; + } + dquote(cond, item.GetValue().c_str()); + } + } + + return server.search(handle, + objid, cond.c_str(), dirbuf, + error); +} + +static bool +visitSong(const UPnPDirObject &meta, const char *path, + const DatabaseSelection &selection, + VisitSong visit_song, Error& error) +{ + if (!visit_song) + return true; + + LightSong song; + song.directory = nullptr; + song.uri = path; + song.real_uri = meta.url.c_str(); + song.tag = &meta.tag; + song.mtime = 0; + song.start_ms = song.end_ms = 0; + + return !selection.Match(song) || visit_song(song, error); +} + +/** + * Build synthetic path based on object id for search results. The use + * of "rootid" is arbitrary, any name that is not likely to be a top + * directory name would fit. + */ +static std::string +songPath(const std::string &servername, + const std::string &objid) +{ + return servername + "/" + rootid + "/" + objid; +} + +bool +UpnpDatabase::SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) const +{ + UPnPDirContent dirbuf; + if (!visit_song) + return true; + if (!SearchSongs(server, objid, selection, dirbuf, error)) + return false; + + for (auto &dirent : dirbuf.objects) { + if (dirent.type != UPnPDirObject::Type::ITEM || + dirent.item_class != UPnPDirObject::ItemClass::MUSIC) + continue; + + // We get song ids as the result of the UPnP search. But our + // client expects paths (e.g. we get 1$4$3788 from minidlna, + // but we need to translate to /Music/All_Music/Satisfaction). + // We can do this in two ways: + // - Rebuild a normal path using BuildPath() which is a kind of pwd + // - Build a bogus path based on the song id. + // The first method is nice because the returned paths are pretty, but + // it has two big problems: + // - The song paths are ambiguous: e.g. minidlna returns all search + // results as being from the "All Music" directory, which can + // contain several songs with the same title (but different objids) + // - The performance of BuildPath() is atrocious on very big + // directories, even causing timeouts in clients. And of + // course, 'All Music' is very big. + // So we return synthetic and ugly paths based on the object id, + // which we later have to detect. + const std::string path = songPath(server.getFriendlyName(), + dirent.m_id); + if (!visitSong(std::move(dirent), path.c_str(), + selection, visit_song, + error)) + return false; + } + + return true; +} + +bool +UpnpDatabase::ReadNode(const ContentDirectoryService &server, + const char *objid, UPnPDirObject &dirent, + Error &error) const +{ + UPnPDirContent dirbuf; + if (!server.getMetadata(handle, objid, dirbuf, error)) + return false; + + if (dirbuf.objects.size() == 1) { + dirent = std::move(dirbuf.objects.front()); + } else { + error.Format(upnp_domain, "Bad resource"); + return false; + } + + return true; +} + +bool +UpnpDatabase::BuildPath(const ContentDirectoryService &server, + const UPnPDirObject& idirent, + std::string &path, + Error &error) const +{ + const char *pid = idirent.m_id.c_str(); + path.clear(); + UPnPDirObject dirent; + while (strcmp(pid, rootid) != 0) { + if (!ReadNode(server, pid, dirent, error)) + return false; + pid = dirent.m_pid.c_str(); + + if (path.empty()) + path = dirent.name; + else + path = PathTraitsUTF8::Build(dirent.name.c_str(), + path.c_str()); + } + + path = PathTraitsUTF8::Build(server.getFriendlyName(), + path.c_str()); + return true; +} + +// Take server and internal title pathname and return objid and metadata. +bool +UpnpDatabase::Namei(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + UPnPDirObject &odirent, + Error &error) const +{ + if (vpath.empty()) { + // looking for root info + if (!ReadNode(server, rootid, odirent, error)) + return false; + + return true; + } + + std::string objid(rootid); + + // Walk the path elements, read each directory and try to find the next one + for (auto i = vpath.begin(), last = std::prev(vpath.end());; ++i) { + UPnPDirContent dirbuf; + if (!server.readDir(handle, objid.c_str(), dirbuf, error)) + return false; + + // Look for the name in the sub-container list + UPnPDirObject *child = dirbuf.FindObject(i->c_str()); + if (child == nullptr) { + error.Format(db_domain, DB_NOT_FOUND, + "No such object"); + return false; + } + + if (i == last) { + odirent = std::move(*child); + return true; + } + + if (child->type != UPnPDirObject::Type::CONTAINER) { + error.Format(db_domain, DB_NOT_FOUND, + "Not a container"); + return false; + } + + objid = std::move(child->m_id); + } +} + +static bool +VisitItem(const UPnPDirObject &object, const char *uri, + const DatabaseSelection &selection, + VisitSong visit_song, VisitPlaylist visit_playlist, + Error &error) +{ + assert(object.type == UPnPDirObject::Type::ITEM); + + switch (object.item_class) { + case UPnPDirObject::ItemClass::MUSIC: + return !visit_song || + visitSong(object, uri, + selection, visit_song, error); + + case UPnPDirObject::ItemClass::PLAYLIST: + if (visit_playlist) { + /* Note: I've yet to see a + playlist item (playlists + seem to be usually handled + as containers, so I'll + decide what to do when I + see one... */ + } + + return true; + + case UPnPDirObject::ItemClass::UNKNOWN: + return true; + } + + assert(false); + gcc_unreachable(); +} + +static bool +VisitObject(const UPnPDirObject &object, const char *uri, + const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) +{ + switch (object.type) { + case UPnPDirObject::Type::UNKNOWN: + assert(false); + gcc_unreachable(); + + case UPnPDirObject::Type::CONTAINER: + return !visit_directory || + visit_directory(LightDirectory(uri, 0), error); + + case UPnPDirObject::Type::ITEM: + return VisitItem(object, uri, selection, + visit_song, visit_playlist, + error); + } + + assert(false); + gcc_unreachable(); +} + +// vpath is a parsed and writeable version of selection.uri. There is +// really just one path parameter. +bool +UpnpDatabase::VisitServer(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + /* If the path begins with rootid, we know that this is a + song, not a directory (because that's how we set things + up). Just visit it. Note that the choice of rootid is + arbitrary, any value not likely to be the name of a top + directory would be ok. */ + /* !Note: this *can't* be handled by Namei further down, + because the path is not valid for traversal. Besides, it's + just faster to access the target node directly */ + if (!vpath.empty() && vpath.front() == rootid) { + switch (vpath.size()) { + case 1: + return true; + + case 2: + break; + + default: + error.Format(db_domain, DB_NOT_FOUND, + "Not found"); + return false; + } + + if (visit_song) { + UPnPDirObject dirent; + if (!ReadNode(server, vpath.back().c_str(), dirent, + error)) + return false; + + if (dirent.type != UPnPDirObject::Type::ITEM || + dirent.item_class != UPnPDirObject::ItemClass::MUSIC) { + error.Format(db_domain, DB_NOT_FOUND, + "Not found"); + return false; + } + + std::string path = songPath(server.getFriendlyName(), + dirent.m_id); + if (!visitSong(std::move(dirent), path.c_str(), + selection, + visit_song, error)) + return false; + } + return true; + } + + // Translate the target path into an object id and the associated metadata. + UPnPDirObject tdirent; + if (!Namei(server, vpath, tdirent, error)) + return false; + + /* If recursive is set, this is a search... No use sending it + if the filter is empty. In this case, we implement limited + recursion (1-deep) here, which will handle the "add dir" + case. */ + if (selection.recursive && selection.filter) + return SearchSongs(server, tdirent.m_id.c_str(), selection, + visit_song, error); + + const char *const base_uri = selection.uri.empty() + ? server.getFriendlyName() + : selection.uri.c_str(); + + if (tdirent.type == UPnPDirObject::Type::ITEM) { + return VisitItem(tdirent, base_uri, + selection, + visit_song, visit_playlist, + error); + } + + /* Target was a a container. Visit it. We could read slices + and loop here, but it's not useful as mpd will only return + data to the client when we're done anyway. */ + UPnPDirContent dirbuf; + if (!server.readDir(handle, tdirent.m_id.c_str(), dirbuf, + error)) + return false; + + for (auto &dirent : dirbuf.objects) { + const std::string uri = PathTraitsUTF8::Build(base_uri, + dirent.name.c_str()); + if (!VisitObject(dirent, uri.c_str(), + selection, + visit_directory, + visit_song, visit_playlist, + error)) + return false; + } + + return true; +} + +// Deal with the possibly multiple servers, call VisitServer if needed. +bool +UpnpDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + auto vpath = stringToTokens(selection.uri, "/", true); + if (vpath.empty()) { + std::vector<ContentDirectoryService> servers; + if (!discovery->getDirServices(servers, error)) + return false; + + for (const auto &server : servers) { + if (visit_directory) { + const LightDirectory d(server.getFriendlyName(), 0); + if (!visit_directory(d, error)) + return false; + } + + if (selection.recursive && + !VisitServer(server, vpath, selection, + visit_directory, visit_song, visit_playlist, + error)) + return false; + } + + return true; + } + + // We do have a path: the first element selects the server + std::string servername(std::move(vpath.front())); + vpath.pop_front(); + + ContentDirectoryService server; + if (!discovery->getServer(servername.c_str(), server, error)) + return false; + + return VisitServer(server, vpath, selection, + visit_directory, visit_song, visit_playlist, error); +} + +bool +UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag, gcc_unused uint32_t group_mask, + VisitTag visit_tag, + Error &error) const +{ + // TODO: use group_mask + + if (!visit_tag) + return true; + + std::vector<ContentDirectoryService> servers; + if (!discovery->getDirServices(servers, error)) + return false; + + std::set<std::string> values; + for (auto& server : servers) { + UPnPDirContent dirbuf; + if (!SearchSongs(server, rootid, selection, dirbuf, error)) + return false; + + for (const auto &dirent : dirbuf.objects) { + if (dirent.type != UPnPDirObject::Type::ITEM || + dirent.item_class != UPnPDirObject::ItemClass::MUSIC) + continue; + + const char *value = dirent.tag.GetValue(tag); + if (value != nullptr) { +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + values.emplace(value); +#else + values.insert(value); +#endif + } + } + } + + for (const auto& value : values) { + TagBuilder builder; + builder.AddItem(tag, value.c_str()); + if (!visit_tag(builder.Commit(), error)) + return false; + } + + return true; +} + +bool +UpnpDatabase::GetStats(const DatabaseSelection &, + DatabaseStats &stats, Error &) const +{ + /* Note: this gets called before the daemonizing so we can't + reallyopen this would be a problem if we had real stats */ + stats.song_count = 0; + stats.total_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + return true; +} + +const DatabasePlugin upnp_db_plugin = { + "upnp", + 0, + UpnpDatabase::Create, +}; diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.hxx b/src/db/plugins/upnp/UpnpDatabasePlugin.hxx new file mode 100644 index 000000000..0228405cd --- /dev/null +++ b/src/db/plugins/upnp/UpnpDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_DATABASE_PLUGIN_HXX +#define MPD_UPNP_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin upnp_db_plugin; + +#endif diff --git a/src/db/update/Archive.cxx b/src/db/update/Archive.cxx new file mode 100644 index 000000000..fc8f1fcbf --- /dev/null +++ b/src/db/update/Archive.cxx @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Walk.hxx" +#include "UpdateDomain.hxx" +#include "db/DatabaseLock.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "db/plugins/simple/Song.hxx" +#include "storage/StorageInterface.hxx" +#include "fs/AllocatedPath.hxx" +#include "storage/FileInfo.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" +#include "archive/ArchiveFile.hxx" +#include "archive/ArchiveVisitor.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <string> + +#include <sys/stat.h> +#include <string.h> + +void +UpdateWalk::UpdateArchiveTree(Directory &directory, const char *name) +{ + const char *tmp = strchr(name, '/'); + if (tmp) { + const std::string child_name(name, tmp); + //add dir is not there already + db_lock(); + Directory *subdir = + directory.MakeChild(child_name.c_str()); + subdir->device = DEVICE_INARCHIVE; + db_unlock(); + + //create directories first + UpdateArchiveTree(*subdir, tmp + 1); + } else { + if (strlen(name) == 0) { + LogWarning(update_domain, + "archive returned directory only"); + return; + } + + //add file + db_lock(); + Song *song = directory.FindSong(name); + db_unlock(); + if (song == nullptr) { + song = Song::LoadFile(storage, name, directory); + if (song != nullptr) { + db_lock(); + directory.AddSong(song); + db_unlock(); + + modified = true; + FormatDefault(update_domain, "added %s/%s", + directory.GetPath(), name); + } + } + } +} + +class UpdateArchiveVisitor final : public ArchiveVisitor { + UpdateWalk &walk; + Directory *directory; + + public: + UpdateArchiveVisitor(UpdateWalk &_walk, Directory *_directory) + :walk(_walk), directory(_directory) {} + + virtual void VisitArchiveEntry(const char *path_utf8) override { + FormatDebug(update_domain, + "adding archive file: %s", path_utf8); + walk.UpdateArchiveTree(*directory, path_utf8); + } +}; + +/** + * Updates the file listing from an archive file. + * + * @param parent the parent directory the archive file resides in + * @param name the UTF-8 encoded base name of the archive file + * @param st stat() information on the archive file + * @param plugin the archive plugin which fits this archive type + */ +void +UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name, + const FileInfo &info, + const ArchivePlugin &plugin) +{ + db_lock(); + Directory *directory = parent.FindChild(name); + db_unlock(); + + if (directory != nullptr && directory->mtime == info.mtime && + !walk_discard) + /* MPD has already scanned the archive, and it hasn't + changed since - don't consider updating it */ + return; + + const auto path_fs = storage.MapChildFS(parent.GetPath(), name); + if (path_fs.IsNull()) + /* not a local file: skip, because the archive API + supports only local files */ + return; + + /* open archive */ + Error error; + ArchiveFile *file = archive_file_open(&plugin, path_fs, error); + if (file == nullptr) { + LogError(error); + if (directory != nullptr) + editor.LockDeleteDirectory(directory); + return; + } + + FormatDebug(update_domain, "archive %s opened", path_fs.c_str()); + + if (directory == nullptr) { + FormatDebug(update_domain, + "creating archive directory: %s", name); + db_lock(); + directory = parent.CreateChild(name); + /* mark this directory as archive (we use device for + this) */ + directory->device = DEVICE_INARCHIVE; + db_unlock(); + } + + directory->mtime = info.mtime; + + UpdateArchiveVisitor visitor(*this, directory); + file->Visit(visitor); + file->Close(); +} + +bool +UpdateWalk::UpdateArchiveFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info) +{ + const ArchivePlugin *plugin = archive_plugin_from_suffix(suffix); + if (plugin == nullptr) + return false; + + UpdateArchiveFile(directory, name, info, *plugin); + return true; +} diff --git a/src/db/update/Container.cxx b/src/db/update/Container.cxx new file mode 100644 index 000000000..1c420fa99 --- /dev/null +++ b/src/db/update/Container.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Walk.hxx" +#include "UpdateDomain.hxx" +#include "db/DatabaseLock.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "db/plugins/simple/Song.hxx" +#include "storage/StorageInterface.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "decoder/DecoderList.hxx" +#include "fs/AllocatedPath.hxx" +#include "storage/FileInfo.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "Log.hxx" + +#include <sys/stat.h> + +Directory * +UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name, + const FileInfo &info) +{ + Directory *directory = parent.FindChild(name); + + // directory exists already + if (directory != nullptr) { + if (directory->IsMount()) + return nullptr; + + if (directory->mtime == info.mtime && !walk_discard) { + /* not modified */ + return nullptr; + } + + editor.DeleteDirectory(directory); + modified = true; + } + + directory = parent.MakeChild(name); + directory->mtime = info.mtime; + return directory; +} + +static bool +SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix) +{ + return plugin.container_scan != nullptr && + plugin.SupportsSuffix(suffix); +} + +bool +UpdateWalk::UpdateContainerFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info) +{ + const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){ + return SupportsContainerSuffix(plugin, suffix); + }); + if (_plugin == nullptr) + return false; + const DecoderPlugin &plugin = *_plugin; + + db_lock(); + Directory *contdir = MakeDirectoryIfModified(directory, name, info); + if (contdir == nullptr) { + /* not modified */ + db_unlock(); + return true; + } + + contdir->device = DEVICE_CONTAINER; + db_unlock(); + + const auto pathname = storage.MapFS(contdir->GetPath()); + if (pathname.IsNull()) { + /* not a local file: skip, because the container API + supports only local files */ + editor.LockDeleteDirectory(contdir); + return false; + } + + char *vtrack; + unsigned int tnum = 0; + TagBuilder tag_builder; + while ((vtrack = plugin.container_scan(pathname, ++tnum)) != nullptr) { + Song *song = Song::NewFile(vtrack, *contdir); + + // shouldn't be necessary but it's there.. + song->mtime = info.mtime; + + const auto child_path_fs = AllocatedPath::Build(pathname, + vtrack); + plugin.ScanFile(child_path_fs, + add_tag_handler, &tag_builder); + + tag_builder.Commit(song->tag); + + db_lock(); + contdir->AddSong(song); + db_unlock(); + + modified = true; + + FormatDefault(update_domain, "added %s/%s", + directory.GetPath(), vtrack); + delete[] vtrack; + } + + if (tnum == 1) { + editor.LockDeleteDirectory(contdir); + return false; + } else + return true; +} diff --git a/src/db/update/Editor.cxx b/src/db/update/Editor.cxx new file mode 100644 index 000000000..4136ccdad --- /dev/null +++ b/src/db/update/Editor.cxx @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Editor.hxx" +#include "Remove.hxx" +#include "db/PlaylistVector.hxx" +#include "db/DatabaseLock.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "db/plugins/simple/Song.hxx" + +#include <assert.h> +#include <stddef.h> + +void +DatabaseEditor::DeleteSong(Directory &dir, Song *del) +{ + assert(del->parent == &dir); + + /* first, prevent traversers in main task from getting this */ + dir.RemoveSong(del); + + db_unlock(); /* temporary unlock, because update_remove_song() blocks */ + + /* now take it out of the playlist (in the main_task) */ + remove.Remove(del); + + /* finally, all possible references gone, free it */ + del->Free(); + + db_lock(); +} + +void +DatabaseEditor::LockDeleteSong(Directory &parent, Song *song) +{ + db_lock(); + DeleteSong(parent, song); + db_unlock(); +} + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + * + * Caller must lock the #db_mutex. + */ +inline void +DatabaseEditor::ClearDirectory(Directory &directory) +{ + directory.ForEachChildSafe([this](Directory &child){ + DeleteDirectory(&child); + }); + + directory.ForEachSongSafe([this, &directory](Song &song){ + assert(song.parent == &directory); + DeleteSong(directory, &song); + }); +} + +void +DatabaseEditor::DeleteDirectory(Directory *directory) +{ + assert(directory->parent != nullptr); + + ClearDirectory(*directory); + + directory->Delete(); +} + +void +DatabaseEditor::LockDeleteDirectory(Directory *directory) +{ + db_lock(); + DeleteDirectory(directory); + db_unlock(); +} + +bool +DatabaseEditor::DeleteNameIn(Directory &parent, const char *name) +{ + bool modified = false; + + db_lock(); + Directory *directory = parent.FindChild(name); + + if (directory != nullptr) { + DeleteDirectory(directory); + modified = true; + } + + Song *song = parent.FindSong(name); + if (song != nullptr) { + DeleteSong(parent, song); + modified = true; + } + + parent.playlists.erase(name); + + db_unlock(); + + return modified; +} diff --git a/src/db/update/Editor.hxx b/src/db/update/Editor.hxx new file mode 100644 index 000000000..fc08c2659 --- /dev/null +++ b/src/db/update/Editor.hxx @@ -0,0 +1,70 @@ +/* + * 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_UPDATE_DATABASE_HXX +#define MPD_UPDATE_DATABASE_HXX + +#include "check.h" +#include "Remove.hxx" + +struct Directory; +struct Song; +class UpdateRemoveService; + +class DatabaseEditor final { + UpdateRemoveService remove; + +public: + DatabaseEditor(EventLoop &_loop, DatabaseListener &_listener) + :remove(_loop, _listener) {} + + /** + * Caller must lock the #db_mutex. + */ + void DeleteSong(Directory &parent, Song *song); + + /** + * DeleteSong() with automatic locking. + */ + void LockDeleteSong(Directory &parent, Song *song); + + /** + * Recursively free a directory and all its contents. + * + * Caller must lock the #db_mutex. + */ + void DeleteDirectory(Directory *directory); + + /** + * DeleteDirectory() with automatic locking. + */ + void LockDeleteDirectory(Directory *directory); + + /** + * Caller must NOT lock the #db_mutex. + * + * @return true if the database was modified + */ + bool DeleteNameIn(Directory &parent, const char *name); + +private: + void ClearDirectory(Directory &directory); +}; + +#endif diff --git a/src/db/update/ExcludeList.cxx b/src/db/update/ExcludeList.cxx new file mode 100644 index 000000000..cf92ac8f7 --- /dev/null +++ b/src/db/update/ExcludeList.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. + */ + +/* + * The .mpdignore backend code. + * + */ + +#include "config.h" +#include "ExcludeList.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/StringUtil.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <errno.h> + +static constexpr Domain exclude_list_domain("exclude_list"); + +bool +ExcludeList::LoadFile(Path path_fs) +{ +#ifdef HAVE_GLIB + FILE *file = FOpen(path_fs, FOpenMode::ReadText); + if (file == nullptr) { + const int e = errno; + if (e != ENOENT) { + const auto path_utf8 = path_fs.ToUTF8(); + FormatErrno(exclude_list_domain, + "Failed to open %s", + path_utf8.c_str()); + } + + return false; + } + + char line[1024]; + while (fgets(line, sizeof(line), file) != nullptr) { + char *p = strchr(line, '#'); + if (p != nullptr) + *p = 0; + + p = Strip(line); + if (*p != 0) + patterns.emplace_front(p); + } + + fclose(file); +#else + // TODO: implement + (void)path_fs; +#endif + + return true; +} + +bool +ExcludeList::Check(Path name_fs) const +{ + assert(!name_fs.IsNull()); + + /* XXX include full path name in check */ + +#ifdef HAVE_GLIB + for (const auto &i : patterns) + if (i.Check(name_fs.c_str())) + return true; +#else + // TODO: implement + (void)name_fs; +#endif + + return false; +} diff --git a/src/db/update/ExcludeList.hxx b/src/db/update/ExcludeList.hxx new file mode 100644 index 000000000..ef6c4d53e --- /dev/null +++ b/src/db/update/ExcludeList.hxx @@ -0,0 +1,92 @@ +/* + * 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. + */ + +/* + * The .mpdignore backend code. + * + */ + +#ifndef MPD_EXCLUDE_H +#define MPD_EXCLUDE_H + +#include "check.h" +#include "Compiler.h" + +#include <forward_list> + +#ifdef HAVE_GLIB +#include <glib.h> +#endif + +class Path; + +class ExcludeList { +#ifdef HAVE_GLIB + class Pattern { + GPatternSpec *pattern; + + public: + Pattern(const char *_pattern) + :pattern(g_pattern_spec_new(_pattern)) {} + + Pattern(Pattern &&other) + :pattern(other.pattern) { + other.pattern = nullptr; + } + + ~Pattern() { + g_pattern_spec_free(pattern); + } + + gcc_pure + bool Check(const char *name_fs) const { + return g_pattern_match_string(pattern, name_fs); + } + }; + + std::forward_list<Pattern> patterns; +#else + // TODO: implement +#endif + +public: + gcc_pure + bool IsEmpty() const { +#ifdef HAVE_GLIB + return patterns.empty(); +#else + // TODO: implement + return true; +#endif + } + + /** + * Loads and parses a .mpdignore file. + */ + bool LoadFile(Path path_fs); + + /** + * Checks whether one of the patterns in the .mpdignore file matches + * the specified file name. + */ + bool Check(Path name_fs) const; +}; + + +#endif diff --git a/src/db/update/InotifyDomain.cxx b/src/db/update/InotifyDomain.cxx new file mode 100644 index 000000000..4a3ab2d79 --- /dev/null +++ b/src/db/update/InotifyDomain.cxx @@ -0,0 +1,23 @@ +/* + * 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 "InotifyDomain.hxx" +#include "util/Domain.hxx" + +const Domain inotify_domain("inotify"); diff --git a/src/db/update/InotifyDomain.hxx b/src/db/update/InotifyDomain.hxx new file mode 100644 index 000000000..ad6202361 --- /dev/null +++ b/src/db/update/InotifyDomain.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_INOTIFY_DOMAIN_HXX +#define MPD_INOTIFY_DOMAIN_HXX + +extern const class Domain inotify_domain; + +#endif diff --git a/src/db/update/InotifyQueue.cxx b/src/db/update/InotifyQueue.cxx new file mode 100644 index 000000000..013200f98 --- /dev/null +++ b/src/db/update/InotifyQueue.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InotifyQueue.hxx" +#include "InotifyDomain.hxx" +#include "Service.hxx" +#include "Log.hxx" + +#include <string.h> + +/** + * Wait this long after the last change before calling + * update_enqueue(). This increases the probability that updates can + * be bundled. + */ +static constexpr unsigned INOTIFY_UPDATE_DELAY_S = 5; + +void +InotifyQueue::OnTimeout() +{ + unsigned id; + + while (!queue.empty()) { + const char *uri_utf8 = queue.front().c_str(); + + id = update.Enqueue(uri_utf8, false); + if (id == 0) { + /* retry later */ + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); + return; + } + + FormatDebug(inotify_domain, "updating '%s' job=%u", + uri_utf8, id); + + queue.pop_front(); + } +} + +static bool +path_in(const char *path, const char *possible_parent) +{ + size_t length = strlen(possible_parent); + + return path[0] == 0 || + (memcmp(possible_parent, path, length) == 0 && + (path[length] == 0 || path[length] == '/')); +} + +void +InotifyQueue::Enqueue(const char *uri_utf8) +{ + ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); + + for (auto i = queue.begin(), end = queue.end(); i != end;) { + const char *current_uri = i->c_str(); + + if (path_in(uri_utf8, current_uri)) + /* already enqueued */ + return; + + if (path_in(current_uri, uri_utf8)) + /* existing path is a sub-path of the new + path; we can dequeue the existing path and + update the new path instead */ + i = queue.erase(i); + else + ++i; + } + + queue.emplace_back(uri_utf8); +} diff --git a/src/db/update/InotifyQueue.hxx b/src/db/update/InotifyQueue.hxx new file mode 100644 index 000000000..a9abc2969 --- /dev/null +++ b/src/db/update/InotifyQueue.hxx @@ -0,0 +1,46 @@ +/* + * 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_INOTIFY_QUEUE_HXX +#define MPD_INOTIFY_QUEUE_HXX + +#include "event/TimeoutMonitor.hxx" +#include "Compiler.h" + +#include <list> +#include <string> + +class UpdateService; + +class InotifyQueue final : private TimeoutMonitor { + UpdateService &update; + + std::list<std::string> queue; + +public: + InotifyQueue(EventLoop &_loop, UpdateService &_update) + :TimeoutMonitor(_loop), update(_update) {} + + void Enqueue(const char *uri_utf8); + +private: + virtual void OnTimeout() override; +}; + +#endif diff --git a/src/db/update/InotifySource.cxx b/src/db/update/InotifySource.cxx new file mode 100644 index 000000000..233504ad6 --- /dev/null +++ b/src/db/update/InotifySource.cxx @@ -0,0 +1,116 @@ +/* + * 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 "InotifySource.hxx" +#include "InotifyDomain.hxx" +#include "util/Error.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <algorithm> + +#include <sys/inotify.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <limits.h> + +bool +InotifySource::OnSocketReady(gcc_unused unsigned flags) +{ + uint8_t buffer[4096]; + static_assert(sizeof(buffer) >= sizeof(struct inotify_event) + NAME_MAX + 1, + "inotify buffer too small"); + + ssize_t nbytes = read(Get(), buffer, sizeof(buffer)); + if (nbytes < 0) + FatalSystemError("Failed to read from inotify"); + if (nbytes == 0) + FatalError("end of file from inotify"); + + const uint8_t *p = buffer, *const end = p + nbytes; + + while (true) { + const size_t remaining = end - p; + const struct inotify_event *event = + (const struct inotify_event *)p; + if (remaining < sizeof(*event) || + remaining < sizeof(*event) + event->len) + break; + + const char *name; + if (event->len > 0 && event->name[event->len - 1] == 0) + name = event->name; + else + name = nullptr; + + callback(event->wd, event->mask, name, callback_ctx); + p += sizeof(*event) + event->len; + } + + return true; +} + +inline +InotifySource::InotifySource(EventLoop &_loop, + mpd_inotify_callback_t _callback, void *_ctx, + int _fd) + :SocketMonitor(_fd, _loop), + callback(_callback), callback_ctx(_ctx) +{ + ScheduleRead(); + +} + +InotifySource * +InotifySource::Create(EventLoop &loop, + mpd_inotify_callback_t callback, void *callback_ctx, + Error &error) +{ + int fd = inotify_init_cloexec(); + if (fd < 0) { + error.SetErrno("inotify_init() has failed"); + return nullptr; + } + + return new InotifySource(loop, callback, callback_ctx, fd); +} + +int +InotifySource::Add(const char *path_fs, unsigned mask, Error &error) +{ + int wd = inotify_add_watch(Get(), path_fs, mask); + if (wd < 0) + error.SetErrno("inotify_add_watch() has failed"); + + return wd; +} + +void +InotifySource::Remove(unsigned wd) +{ + int ret = inotify_rm_watch(Get(), wd); + if (ret < 0 && errno != EINVAL) + LogErrno(inotify_domain, "inotify_rm_watch() has failed"); + + /* EINVAL may happen here when the file has been deleted; the + kernel seems to auto-unregister deleted files */ +} diff --git a/src/db/update/InotifySource.hxx b/src/db/update/InotifySource.hxx new file mode 100644 index 000000000..081ce10f3 --- /dev/null +++ b/src/db/update/InotifySource.hxx @@ -0,0 +1,71 @@ +/* + * 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_INOTIFY_SOURCE_HXX +#define MPD_INOTIFY_SOURCE_HXX + +#include "event/SocketMonitor.hxx" + +class Error; + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +class InotifySource final : private SocketMonitor { + mpd_inotify_callback_t callback; + void *callback_ctx; + + InotifySource(EventLoop &_loop, + mpd_inotify_callback_t callback, void *ctx, int fd); + +public: + ~InotifySource() { + Close(); + } + + /** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ + static InotifySource *Create(EventLoop &_loop, + mpd_inotify_callback_t callback, + void *ctx, + Error &error); + + /** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ + int Add(const char *path_fs, unsigned mask, Error &error); + + /** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ + void Remove(unsigned wd); + +private: + virtual bool OnSocketReady(unsigned flags) override; +}; + +#endif diff --git a/src/db/update/InotifyUpdate.cxx b/src/db/update/InotifyUpdate.cxx new file mode 100644 index 000000000..26bdddf8d --- /dev/null +++ b/src/db/update/InotifyUpdate.cxx @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "InotifyUpdate.hxx" +#include "InotifySource.hxx" +#include "InotifyQueue.hxx" +#include "InotifyDomain.hxx" +#include "storage/StorageInterface.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <string> +#include <map> +#include <forward_list> + +#include <assert.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <string.h> +#include <dirent.h> + +static constexpr unsigned IN_MASK = +#ifdef IN_ONLYDIR + IN_ONLYDIR| +#endif + IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF; + +struct WatchDirectory { + WatchDirectory *parent; + + AllocatedPath name; + + int descriptor; + + std::forward_list<WatchDirectory> children; + + template<typename N> + WatchDirectory(WatchDirectory *_parent, N &&_name, + int _descriptor) + :parent(_parent), name(std::forward<N>(_name)), + descriptor(_descriptor) {} + + WatchDirectory(const WatchDirectory &) = delete; + WatchDirectory &operator=(const WatchDirectory &) = delete; + + gcc_pure + unsigned GetDepth() const; + + gcc_pure + AllocatedPath GetUriFS() const; +}; + +static InotifySource *inotify_source; +static InotifyQueue *inotify_queue; + +static unsigned inotify_max_depth; +static WatchDirectory *inotify_root; +static std::map<int, WatchDirectory *> inotify_directories; + +static void +tree_add_watch_directory(WatchDirectory *directory) +{ + inotify_directories.insert(std::make_pair(directory->descriptor, + directory)); +} + +static void +tree_remove_watch_directory(WatchDirectory *directory) +{ + auto i = inotify_directories.find(directory->descriptor); + assert(i != inotify_directories.end()); + inotify_directories.erase(i); +} + +static WatchDirectory * +tree_find_watch_directory(int wd) +{ + auto i = inotify_directories.find(wd); + if (i == inotify_directories.end()) + return nullptr; + + return i->second; +} + +static void +disable_watch_directory(WatchDirectory &directory) +{ + tree_remove_watch_directory(&directory); + + for (WatchDirectory &child : directory.children) + disable_watch_directory(child); + + inotify_source->Remove(directory.descriptor); +} + +static void +remove_watch_directory(WatchDirectory *directory) +{ + assert(directory != nullptr); + + if (directory->parent == nullptr) { + LogWarning(inotify_domain, + "music directory was removed - " + "cannot continue to watch it"); + return; + } + + disable_watch_directory(*directory); + + /* remove it from the parent, which effectively deletes it */ + directory->parent->children.remove_if([directory](const WatchDirectory &child){ + return &child == directory; + }); +} + +AllocatedPath +WatchDirectory::GetUriFS() const +{ + if (parent == nullptr) + return AllocatedPath::Null(); + + const auto uri = parent->GetUriFS(); + if (uri.IsNull()) + return name; + + return AllocatedPath::Build(uri, name); +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != nullptr; +} + +static void +recursive_watch_subdirectories(WatchDirectory *directory, + const AllocatedPath &path_fs, unsigned depth) +{ + Error error; + DIR *dir; + struct dirent *ent; + + assert(directory != nullptr); + assert(depth <= inotify_max_depth); + assert(!path_fs.IsNull()); + + ++depth; + + if (depth > inotify_max_depth) + return; + + dir = opendir(path_fs.c_str()); + if (dir == nullptr) { + FormatErrno(inotify_domain, + "Failed to open directory %s", path_fs.c_str()); + return; + } + + while ((ent = readdir(dir))) { + struct stat st; + int ret; + + if (skip_path(ent->d_name)) + continue; + + const auto child_path_fs = + AllocatedPath::Build(path_fs, ent->d_name); + ret = StatFile(child_path_fs, st); + if (ret < 0) { + FormatErrno(inotify_domain, + "Failed to stat %s", + child_path_fs.c_str()); + continue; + } + + if (!S_ISDIR(st.st_mode)) + continue; + + ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK, + error); + if (ret < 0) { + FormatError(error, + "Failed to register %s", + child_path_fs.c_str()); + error.Clear(); + continue; + } + + WatchDirectory *child = tree_find_watch_directory(ret); + if (child != nullptr) + /* already being watched */ + continue; + + directory->children.emplace_front(directory, + AllocatedPath::FromFS(ent->d_name), + ret); + child = &directory->children.front(); + + tree_add_watch_directory(child); + + recursive_watch_subdirectories(child, child_path_fs, depth); + } + + closedir(dir); +} + +gcc_pure +unsigned +WatchDirectory::GetDepth() const +{ + const WatchDirectory *d = this; + unsigned depth = 0; + while ((d = d->parent) != nullptr) + ++depth; + + return depth; +} + +static void +mpd_inotify_callback(int wd, unsigned mask, + gcc_unused const char *name, gcc_unused void *ctx) +{ + WatchDirectory *directory; + + /*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/ + + directory = tree_find_watch_directory(wd); + if (directory == nullptr) + return; + + const auto uri_fs = directory->GetUriFS(); + + if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { + remove_watch_directory(directory); + return; + } + + if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 && + (mask & IN_ISDIR) != 0) { + /* a sub directory was changed: register those in + inotify */ + const auto &root = inotify_root->name; + + const auto path_fs = uri_fs.IsNull() + ? root + : AllocatedPath::Build(root, uri_fs.c_str()); + + recursive_watch_subdirectories(directory, path_fs, + directory->GetDepth()); + } + + if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || + /* at the maximum depth, we watch out for newly created + directories */ + (directory->GetDepth() == inotify_max_depth && + (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { + /* a file was changed, or a directory was + moved/deleted: queue a database update */ + + if (!uri_fs.IsNull()) { + const std::string uri_utf8 = uri_fs.ToUTF8(); + if (!uri_utf8.empty()) + inotify_queue->Enqueue(uri_utf8.c_str()); + } + else + inotify_queue->Enqueue(""); + } +} + +void +mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update, + unsigned max_depth) +{ + LogDebug(inotify_domain, "initializing inotify"); + + const auto path = storage.MapFS(""); + if (path.IsNull()) { + LogDebug(inotify_domain, "no music directory configured"); + return; + } + + Error error; + inotify_source = InotifySource::Create(loop, + mpd_inotify_callback, nullptr, + error); + if (inotify_source == nullptr) { + LogError(error); + return; + } + + inotify_max_depth = max_depth; + + int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error); + if (descriptor < 0) { + LogError(error); + delete inotify_source; + inotify_source = nullptr; + return; + } + + inotify_root = new WatchDirectory(nullptr, path, descriptor); + + tree_add_watch_directory(inotify_root); + + recursive_watch_subdirectories(inotify_root, path, 0); + + inotify_queue = new InotifyQueue(loop, update); + + LogDebug(inotify_domain, "watching music directory"); +} + +void +mpd_inotify_finish(void) +{ + if (inotify_source == nullptr) + return; + + delete inotify_queue; + delete inotify_source; + delete inotify_root; + inotify_directories.clear(); +} diff --git a/src/db/update/InotifyUpdate.hxx b/src/db/update/InotifyUpdate.hxx new file mode 100644 index 000000000..0f78db71f --- /dev/null +++ b/src/db/update/InotifyUpdate.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_UPDATE_HXX +#define MPD_INOTIFY_UPDATE_HXX + +#include "check.h" +#include "Compiler.h" + +class EventLoop; +class Storage; +class UpdateService; + +void +mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update, + unsigned max_depth); + +void +mpd_inotify_finish(void); + +#endif diff --git a/src/db/update/Queue.cxx b/src/db/update/Queue.cxx new file mode 100644 index 000000000..6d6d80131 --- /dev/null +++ b/src/db/update/Queue.cxx @@ -0,0 +1,67 @@ +/* + * 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 "Queue.hxx" + +bool +UpdateQueue::Push(SimpleDatabase &db, Storage &storage, + const char *path, bool discard, unsigned id) +{ + if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE) + return false; + + update_queue.emplace_back(db, storage, path, discard, id); + return true; +} + +UpdateQueueItem +UpdateQueue::Pop() +{ + if (update_queue.empty()) + return UpdateQueueItem(); + + auto i = std::move(update_queue.front()); + update_queue.pop_front(); + return i; +} + +void +UpdateQueue::Erase(SimpleDatabase &db) +{ + for (auto i = update_queue.begin(), end = update_queue.end(); + i != end;) { + if (i->db == &db) + i = update_queue.erase(i); + else + ++i; + } +} + +void +UpdateQueue::Erase(Storage &storage) +{ + for (auto i = update_queue.begin(), end = update_queue.end(); + i != end;) { + if (i->storage == &storage) + i = update_queue.erase(i); + else + ++i; + } +} diff --git a/src/db/update/Queue.hxx b/src/db/update/Queue.hxx new file mode 100644 index 000000000..9064ea481 --- /dev/null +++ b/src/db/update/Queue.hxx @@ -0,0 +1,77 @@ +/* + * 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_UPDATE_QUEUE_HXX +#define MPD_UPDATE_QUEUE_HXX + +#include "check.h" +#include "Compiler.h" + +#include <string> +#include <list> + +class SimpleDatabase; +class Storage; + +struct UpdateQueueItem { + SimpleDatabase *db; + Storage *storage; + + std::string path_utf8; + unsigned id; + bool discard; + + UpdateQueueItem():id(0) {} + + UpdateQueueItem(SimpleDatabase &_db, + Storage &_storage, + const char *_path, bool _discard, + unsigned _id) + :db(&_db), storage(&_storage), path_utf8(_path), + id(_id), discard(_discard) {} + + bool IsDefined() const { + return id != 0; + } +}; + +class UpdateQueue { + static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32; + + std::list<UpdateQueueItem> update_queue; + +public: + gcc_nonnull_all + bool Push(SimpleDatabase &db, Storage &storage, + const char *path, bool discard, unsigned id); + + UpdateQueueItem Pop(); + + void Clear() { + update_queue.clear(); + } + + gcc_nonnull_all + void Erase(SimpleDatabase &db); + + gcc_nonnull_all + void Erase(Storage &storage); +}; + +#endif diff --git a/src/db/update/Remove.cxx b/src/db/update/Remove.cxx new file mode 100644 index 000000000..dfada05b2 --- /dev/null +++ b/src/db/update/Remove.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Remove.hxx" +#include "UpdateDomain.hxx" +#include "db/plugins/simple/Song.hxx" +#include "db/LightSong.hxx" +#include "db/DatabaseListener.hxx" +#include "Log.hxx" + +#include <assert.h> + +/** + * Safely remove a song from the database. This must be done in the + * main task, to be sure that there is no pointer left to it. + */ +void +UpdateRemoveService::RunDeferred() +{ + assert(removed_song != nullptr); + + { + const auto uri = removed_song->GetURI(); + FormatDefault(update_domain, "removing %s", uri.c_str()); + } + + listener.OnDatabaseSongRemoved(removed_song->Export()); + + /* clear "removed_song" and send signal to update thread */ + remove_mutex.lock(); + removed_song = nullptr; + remove_cond.signal(); + remove_mutex.unlock(); +} + +void +UpdateRemoveService::Remove(const Song *song) +{ + assert(removed_song == nullptr); + + removed_song = song; + + DeferredMonitor::Schedule(); + + remove_mutex.lock(); + + while (removed_song != nullptr) + remove_cond.wait(remove_mutex); + + remove_mutex.unlock(); +} diff --git a/src/db/update/Remove.hxx b/src/db/update/Remove.hxx new file mode 100644 index 000000000..f0457efa1 --- /dev/null +++ b/src/db/update/Remove.hxx @@ -0,0 +1,61 @@ +/* + * 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_UPDATE_REMOVE_HXX +#define MPD_UPDATE_REMOVE_HXX + +#include "check.h" +#include "event/DeferredMonitor.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +struct Song; +class DatabaseListener; + +/** + * This class handles #Song removal. It defers the action to the main + * thread to ensure that all references to the #Song are gone. + */ +class UpdateRemoveService final : DeferredMonitor { + DatabaseListener &listener; + + Mutex remove_mutex; + Cond remove_cond; + + const Song *removed_song; + +public: + UpdateRemoveService(EventLoop &_loop, DatabaseListener &_listener) + :DeferredMonitor(_loop), listener(_listener), + removed_song(nullptr){} + + /** + * Sends a signal to the main thread which will in turn remove + * the song: from the sticker database and from the playlist. + * This serialized access is implemented to avoid excessive + * locking. + */ + void Remove(const Song *song); + +private: + /* virtual methods from class DeferredMonitor */ + virtual void RunDeferred() override; +}; + +#endif diff --git a/src/db/update/Service.cxx b/src/db/update/Service.cxx new file mode 100644 index 000000000..e8a1f6b02 --- /dev/null +++ b/src/db/update/Service.cxx @@ -0,0 +1,279 @@ +/* + * 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 "Service.hxx" +#include "Walk.hxx" +#include "UpdateDomain.hxx" +#include "db/DatabaseListener.hxx" +#include "db/DatabaseLock.hxx" +#include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "storage/CompositeStorage.hxx" +#include "Idle.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Instance.hxx" +#include "system/FatalError.hxx" +#include "thread/Id.hxx" +#include "thread/Thread.hxx" +#include "thread/Util.hxx" + +#ifndef NDEBUG +#include "event/Loop.hxx" +#endif + +#include <assert.h> + +UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db, + CompositeStorage &_storage, + DatabaseListener &_listener) + :DeferredMonitor(_loop), + db(_db), storage(_storage), + listener(_listener), + progress(UPDATE_PROGRESS_IDLE), + update_task_id(0), + walk(nullptr) +{ +} + +UpdateService::~UpdateService() +{ + CancelAllAsync(); + + if (update_thread.IsDefined()) + update_thread.Join(); + + delete walk; +} + +void +UpdateService::CancelAllAsync() +{ + assert(GetEventLoop().IsInsideOrNull()); + + queue.Clear(); + + if (walk != nullptr) + walk->Cancel(); +} + +void +UpdateService::CancelMount(const char *uri) +{ + /* determine which (mounted) database will be updated and what + storage will be scanned */ + + db_lock(); + const auto lr = db.GetRoot().LookupDirectory(uri); + db_unlock(); + + if (!lr.directory->IsMount()) + return; + + bool cancel_current = false; + + Storage *storage2 = storage.GetMount(uri); + if (storage2 != nullptr) { + queue.Erase(*storage2); + cancel_current = next.IsDefined() && next.storage == storage2; + } + + Database &_db2 = *lr.directory->mounted_database; + if (_db2.IsPlugin(simple_db_plugin)) { + SimpleDatabase &db2 = static_cast<SimpleDatabase &>(_db2); + queue.Erase(db2); + cancel_current |= next.IsDefined() && next.db == &db2; + } + + if (cancel_current && walk != nullptr) { + walk->Cancel(); + + if (update_thread.IsDefined()) + update_thread.Join(); + } +} + +inline void +UpdateService::Task() +{ + assert(walk != nullptr); + + if (!next.path_utf8.empty()) + FormatDebug(update_domain, "starting: %s", + next.path_utf8.c_str()); + else + LogDebug(update_domain, "starting"); + + SetThreadIdlePriority(); + + modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(), + next.discard); + + if (modified || !next.db->FileExists()) { + Error error; + if (!next.db->Save(error)) + LogError(error, "Failed to save database"); + } + + if (!next.path_utf8.empty()) + FormatDebug(update_domain, "finished: %s", + next.path_utf8.c_str()); + else + LogDebug(update_domain, "finished"); + + progress = UPDATE_PROGRESS_DONE; + DeferredMonitor::Schedule(); +} + +void +UpdateService::Task(void *ctx) +{ + UpdateService &service = *(UpdateService *)ctx; + return service.Task(); +} + +void +UpdateService::StartThread(UpdateQueueItem &&i) +{ + assert(GetEventLoop().IsInsideOrNull()); + assert(walk == nullptr); + + progress = UPDATE_PROGRESS_RUNNING; + modified = false; + + next = std::move(i); + walk = new UpdateWalk(GetEventLoop(), listener, *next.storage); + + Error error; + if (!update_thread.Start(Task, this, error)) + FatalError(error); + + FormatDebug(update_domain, + "spawned thread for update job id %i", next.id); +} + +unsigned +UpdateService::GenerateId() +{ + unsigned id = update_task_id + 1; + if (id > update_task_id_max) + id = 1; + return id; +} + +unsigned +UpdateService::Enqueue(const char *path, bool discard) +{ + assert(GetEventLoop().IsInsideOrNull()); + + /* determine which (mounted) database will be updated and what + storage will be scanned */ + SimpleDatabase *db2; + Storage *storage2; + + db_lock(); + const auto lr = db.GetRoot().LookupDirectory(path); + db_unlock(); + if (lr.directory->IsMount()) { + /* follow the mountpoint, update the mounted + database */ + + Database &_db2 = *lr.directory->mounted_database; + if (!_db2.IsPlugin(simple_db_plugin)) + /* cannot update this type of database */ + return 0; + + db2 = static_cast<SimpleDatabase *>(&_db2); + + if (lr.uri == nullptr) { + storage2 = storage.GetMount(path); + path = ""; + } else { + assert(lr.uri > path); + assert(lr.uri < path + strlen(path)); + assert(lr.uri[-1] == '/'); + + const std::string mountpoint(path, lr.uri - 1); + storage2 = storage.GetMount(mountpoint.c_str()); + path = lr.uri; + } + } else { + /* use the "root" database/storage */ + + db2 = &db; + storage2 = storage.GetMount(""); + } + + if (storage2 == nullptr) + /* no storage found at this mount point - should not + happen */ + return 0; + + if (progress != UPDATE_PROGRESS_IDLE) { + const unsigned id = GenerateId(); + if (!queue.Push(*db2, *storage2, path, discard, id)) + return 0; + + update_task_id = id; + return id; + } + + const unsigned id = update_task_id = GenerateId(); + StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id)); + + idle_add(IDLE_UPDATE); + + return id; +} + +/** + * Called in the main thread after the database update is finished. + */ +void +UpdateService::RunDeferred() +{ + assert(progress == UPDATE_PROGRESS_DONE); + assert(next.IsDefined()); + assert(walk != nullptr); + + /* wait for thread to finish only if it wasn't cancelled by + CancelMount() */ + if (update_thread.IsDefined()) + update_thread.Join(); + + delete walk; + walk = nullptr; + + next = UpdateQueueItem(); + + idle_add(IDLE_UPDATE); + + if (modified) + /* send "idle" events */ + listener.OnDatabaseModified(); + + auto i = queue.Pop(); + if (i.IsDefined()) { + /* schedule the next path */ + StartThread(std::move(i)); + } else { + progress = UPDATE_PROGRESS_IDLE; + } +} diff --git a/src/db/update/Service.hxx b/src/db/update/Service.hxx new file mode 100644 index 000000000..cbb4a3f9d --- /dev/null +++ b/src/db/update/Service.hxx @@ -0,0 +1,115 @@ +/* + * 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_UPDATE_SERVICE_HXX +#define MPD_UPDATE_SERVICE_HXX + +#include "check.h" +#include "Queue.hxx" +#include "event/DeferredMonitor.hxx" +#include "thread/Thread.hxx" + +class SimpleDatabase; +class DatabaseListener; +class UpdateWalk; +class CompositeStorage; + +/** + * This class manages the update queue and runs the update thread. + */ +class UpdateService final : DeferredMonitor { + enum Progress { + UPDATE_PROGRESS_IDLE = 0, + UPDATE_PROGRESS_RUNNING = 1, + UPDATE_PROGRESS_DONE = 2 + }; + + SimpleDatabase &db; + CompositeStorage &storage; + + DatabaseListener &listener; + + Progress progress; + + bool modified; + + Thread update_thread; + + static const unsigned update_task_id_max = 1 << 15; + + unsigned update_task_id; + + UpdateQueue queue; + + UpdateQueueItem next; + + UpdateWalk *walk; + +public: + UpdateService(EventLoop &_loop, SimpleDatabase &_db, + CompositeStorage &_storage, + DatabaseListener &_listener); + + ~UpdateService(); + + /** + * Returns a non-zero job id when we are currently updating + * the database. + */ + unsigned GetId() const { + return next.id; + } + + /** + * Add this path to the database update queue. + * + * @param path a path to update; if an empty string, + * the whole music directory is updated + * @return the job id, or 0 on error + */ + gcc_nonnull_all + unsigned Enqueue(const char *path, bool discard); + + /** + * Clear the queue and cancel the current update. Does not + * wait for the thread to exit. + */ + void CancelAllAsync(); + + /** + * Cancel all updates for the given mount point. If an update + * is already running for it, the method will wait for + * cancellation to complete. + */ + void CancelMount(const char *uri); + +private: + /* virtual methods from class DeferredMonitor */ + virtual void RunDeferred() override; + + /* the update thread */ + void Task(); + static void Task(void *ctx); + + void StartThread(UpdateQueueItem &&i); + + unsigned GenerateId(); +}; + +#endif diff --git a/src/db/update/UpdateDomain.cxx b/src/db/update/UpdateDomain.cxx new file mode 100644 index 000000000..80ad4fd22 --- /dev/null +++ b/src/db/update/UpdateDomain.cxx @@ -0,0 +1,23 @@ +/* + * 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 "UpdateDomain.hxx" +#include "util/Domain.hxx" + +const Domain update_domain("update"); diff --git a/src/db/update/UpdateDomain.hxx b/src/db/update/UpdateDomain.hxx new file mode 100644 index 000000000..a6e994390 --- /dev/null +++ b/src/db/update/UpdateDomain.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_UPDATE_DOMAIN_HXX +#define MPD_UPDATE_DOMAIN_HXX + +extern const class Domain update_domain; + +#endif diff --git a/src/db/update/UpdateIO.cxx b/src/db/update/UpdateIO.cxx new file mode 100644 index 000000000..fa19a8b5a --- /dev/null +++ b/src/db/update/UpdateIO.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "UpdateIO.hxx" +#include "UpdateDomain.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "storage/FileInfo.hxx" +#include "storage/StorageInterface.hxx" +#include "fs/Traits.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <errno.h> +#include <unistd.h> + +bool +GetInfo(Storage &storage, const char *uri_utf8, FileInfo &info) +{ + Error error; + bool success = storage.GetInfo(uri_utf8, true, info, error); + if (!success) + LogError(error); + return success; +} + +bool +GetInfo(StorageDirectoryReader &reader, FileInfo &info) +{ + Error error; + bool success = reader.GetInfo(true, info, error); + if (!success) + LogError(error); + return success; +} + +bool +DirectoryExists(Storage &storage, const Directory &directory) +{ + FileInfo info; + if (!storage.GetInfo(directory.GetPath(), true, info, IgnoreError())) + return false; + + return directory.device == DEVICE_INARCHIVE || + directory.device == DEVICE_CONTAINER + ? info.IsRegular() + : info.IsDirectory(); +} + +static bool +GetDirectoryChildInfo(Storage &storage, const Directory &directory, + const char *name_utf8, FileInfo &info, Error &error) +{ + const auto uri_utf8 = PathTraitsUTF8::Build(directory.GetPath(), + name_utf8); + return storage.GetInfo(uri_utf8.c_str(), true, info, error); +} + +bool +directory_child_is_regular(Storage &storage, const Directory &directory, + const char *name_utf8) +{ + FileInfo info; + return GetDirectoryChildInfo(storage, directory, name_utf8, info, + IgnoreError()) && + info.IsRegular(); +} + +bool +directory_child_access(Storage &storage, const Directory &directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* CheckAccess() is useless on WIN32 */ + (void)storage; + (void)directory; + (void)name; + (void)mode; + return true; +#else + const auto path = storage.MapChildFS(directory.GetPath(), name); + if (path.IsNull()) + /* does not point to local file: silently ignore the + check */ + return true; + + return CheckAccess(path, mode) || errno != EACCES; +#endif +} diff --git a/src/db/update/UpdateIO.hxx b/src/db/update/UpdateIO.hxx new file mode 100644 index 000000000..2dbb4ae83 --- /dev/null +++ b/src/db/update/UpdateIO.hxx @@ -0,0 +1,62 @@ +/* + * 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_UPDATE_IO_HXX +#define MPD_UPDATE_IO_HXX + +#include "check.h" +#include "Compiler.h" + +struct Directory; +struct FileInfo; +class Storage; +class StorageDirectoryReader; + +/** + * Wrapper for Storage::GetInfo() that logs errors instead of + * returning them. + */ +bool +GetInfo(Storage &storage, const char *uri_utf8, FileInfo &info); + +/** + * Wrapper for LocalDirectoryReader::GetInfo() that logs errors + * instead of returning them. + */ +bool +GetInfo(StorageDirectoryReader &reader, FileInfo &info); + +gcc_pure +bool +DirectoryExists(Storage &storage, const Directory &directory); + +gcc_pure +bool +directory_child_is_regular(Storage &storage, const Directory &directory, + const char *name_utf8); + +/** + * Checks if the given permissions on the mapped file are given. + */ +gcc_pure +bool +directory_child_access(Storage &storage, const Directory &directory, + const char *name, int mode); + +#endif diff --git a/src/db/update/UpdateSong.cxx b/src/db/update/UpdateSong.cxx new file mode 100644 index 000000000..005bf8992 --- /dev/null +++ b/src/db/update/UpdateSong.cxx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Walk.hxx" +#include "UpdateIO.hxx" +#include "UpdateDomain.hxx" +#include "db/DatabaseLock.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "db/plugins/simple/Song.hxx" +#include "decoder/DecoderList.hxx" +#include "storage/FileInfo.hxx" +#include "Log.hxx" + +#include <unistd.h> + +inline void +UpdateWalk::UpdateSongFile2(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info) +{ + db_lock(); + Song *song = directory.FindSong(name); + db_unlock(); + + if (!directory_child_access(storage, directory, name, R_OK)) { + FormatError(update_domain, + "no read permissions on %s/%s", + directory.GetPath(), name); + if (song != nullptr) + editor.LockDeleteSong(directory, song); + + return; + } + + if (!(song != nullptr && info.mtime == song->mtime && + !walk_discard) && + UpdateContainerFile(directory, name, suffix, info)) { + if (song != nullptr) + editor.LockDeleteSong(directory, song); + + return; + } + + if (song == nullptr) { + FormatDebug(update_domain, "reading %s/%s", + directory.GetPath(), name); + song = Song::LoadFile(storage, name, directory); + if (song == nullptr) { + FormatDebug(update_domain, + "ignoring unrecognized file %s/%s", + directory.GetPath(), name); + return; + } + + db_lock(); + directory.AddSong(song); + db_unlock(); + + modified = true; + FormatDefault(update_domain, "added %s/%s", + directory.GetPath(), name); + } else if (info.mtime != song->mtime || walk_discard) { + FormatDefault(update_domain, "updating %s/%s", + directory.GetPath(), name); + if (!song->UpdateFile(storage)) { + FormatDebug(update_domain, + "deleting unrecognized file %s/%s", + directory.GetPath(), name); + editor.LockDeleteSong(directory, song); + } + + modified = true; + } +} + +bool +UpdateWalk::UpdateSongFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info) +{ + if (!decoder_plugins_supports_suffix(suffix)) + return false; + + UpdateSongFile2(directory, name, suffix, info); + return true; +} diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx new file mode 100644 index 000000000..f71faa86d --- /dev/null +++ b/src/db/update/Walk.cxx @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Walk.hxx" +#include "UpdateIO.hxx" +#include "Editor.hxx" +#include "UpdateDomain.hxx" +#include "db/DatabaseLock.hxx" +#include "db/PlaylistVector.hxx" +#include "db/Uri.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "db/plugins/simple/Song.hxx" +#include "storage/StorageInterface.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "ExcludeList.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" +#include "fs/FileSystem.hxx" +#include "fs/Charset.hxx" +#include "storage/FileInfo.hxx" +#include "util/Alloc.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <sys/stat.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <memory> + +UpdateWalk::UpdateWalk(EventLoop &_loop, DatabaseListener &_listener, + Storage &_storage) + :cancel(false), + storage(_storage), + editor(_loop, _listener) +{ +#ifndef WIN32 + follow_inside_symlinks = + config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, + DEFAULT_FOLLOW_INSIDE_SYMLINKS); + + follow_outside_symlinks = + config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); +#endif +} + +static void +directory_set_stat(Directory &dir, const FileInfo &info) +{ + dir.inode = info.inode; + dir.device = info.device; +} + +inline void +UpdateWalk::RemoveExcludedFromDirectory(Directory &directory, + const ExcludeList &exclude_list) +{ + db_lock(); + + directory.ForEachChildSafe([&](Directory &child){ + const auto name_fs = + AllocatedPath::FromUTF8(child.GetName()); + + if (name_fs.IsNull() || exclude_list.Check(name_fs)) { + editor.DeleteDirectory(&child); + modified = true; + } + }); + + directory.ForEachSongSafe([&](Song &song){ + assert(song.parent == &directory); + + const auto name_fs = AllocatedPath::FromUTF8(song.uri); + if (name_fs.IsNull() || exclude_list.Check(name_fs)) { + editor.DeleteSong(directory, &song); + modified = true; + } + }); + + db_unlock(); +} + +inline void +UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) +{ + directory.ForEachChildSafe([&](Directory &child){ + if (DirectoryExists(storage, child)) + return; + + editor.LockDeleteDirectory(&child); + + modified = true; + }); + + directory.ForEachSongSafe([&](Song &song){ + if (!directory_child_is_regular(storage, directory, + song.uri)) { + editor.LockDeleteSong(directory, &song); + + modified = true; + } + }); + + for (auto i = directory.playlists.begin(), + end = directory.playlists.end(); + i != end;) { + if (!directory_child_is_regular(storage, directory, + i->name.c_str())) { + db_lock(); + i = directory.playlists.erase(i); + db_unlock(); + } else + ++i; + } +} + +#ifndef WIN32 +static bool +update_directory_stat(Storage &storage, Directory &directory) +{ + FileInfo info; + if (!GetInfo(storage, directory.GetPath(), info)) + return false; + + directory_set_stat(directory, info); + return true; +} +#endif + +/** + * Check the ancestors of the given #Directory and see if there's one + * with the same device/inode number, building a loop. + * + * @return 1 if a loop was found, 0 if not, -1 on I/O error + */ +static int +FindAncestorLoop(Storage &storage, Directory *parent, + unsigned inode, unsigned device) +{ +#ifndef WIN32 + if (device == 0 && inode == 0) + /* can't detect loops if the Storage does not support + these numbers */ + return 0; + + while (parent) { + if (parent->device == 0 && parent->inode == 0 && + !update_directory_stat(storage, *parent)) + return -1; + + if (parent->inode == inode && parent->device == device) { + LogDebug(update_domain, "recursive directory found"); + return 1; + } + + parent = parent->parent; + } +#else + (void)storage; + (void)parent; + (void)inode; + (void)device; +#endif + + return 0; +} + +inline bool +UpdateWalk::UpdatePlaylistFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info) +{ + if (!playlist_suffix_supported(suffix)) + return false; + + PlaylistInfo pi(name, info.mtime); + + db_lock(); + if (directory.playlists.UpdateOrInsert(std::move(pi))) + modified = true; + db_unlock(); + return true; +} + +inline bool +UpdateWalk::UpdateRegularFile(Directory &directory, + const char *name, const FileInfo &info) +{ + const char *suffix = uri_get_suffix(name); + if (suffix == nullptr) + return false; + + return UpdateSongFile(directory, name, suffix, info) || + UpdateArchiveFile(directory, name, suffix, info) || + UpdatePlaylistFile(directory, name, suffix, info); +} + +void +UpdateWalk::UpdateDirectoryChild(Directory &directory, + const char *name, const FileInfo &info) +{ + assert(strchr(name, '/') == nullptr); + + if (info.IsRegular()) { + UpdateRegularFile(directory, name, info); + } else if (info.IsDirectory()) { + if (FindAncestorLoop(storage, &directory, + info.inode, info.device)) + return; + + db_lock(); + Directory *subdir = directory.MakeChild(name); + db_unlock(); + + assert(&directory == subdir->parent); + + if (!UpdateDirectory(*subdir, info)) + editor.LockDeleteDirectory(subdir); + } else { + FormatDebug(update_domain, + "%s is not a directory, archive or music", name); + } +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +gcc_pure +static bool +skip_path(const char *name_utf8) +{ + return strchr(name_utf8, '\n') != nullptr; +} + +gcc_pure +bool +UpdateWalk::SkipSymlink(const Directory *directory, + const char *utf8_name) const +{ +#ifndef WIN32 + const auto path_fs = storage.MapChildFS(directory->GetPath(), + utf8_name); + if (path_fs.IsNull()) + /* not a local file: don't skip */ + return false; + + const auto target = ReadLink(path_fs); + if (target.IsNull()) + /* don't skip if this is not a symlink */ + return errno != EINVAL; + + if (!follow_inside_symlinks && !follow_outside_symlinks) { + /* ignore all symlinks */ + return true; + } else if (follow_inside_symlinks && follow_outside_symlinks) { + /* consider all symlinks */ + return false; + } + + const char *target_str = target.c_str(); + + if (PathTraitsFS::IsAbsolute(target_str)) { + /* if the symlink points to an absolute path, see if + that path is inside the music directory */ + const auto target_utf8 = PathToUTF8(target_str); + if (target_utf8.empty()) + return true; + + const char *relative = + storage.MapToRelativeUTF8(target_utf8.c_str()); + return relative != nullptr + ? !follow_inside_symlinks + : !follow_outside_symlinks; + } + + const char *p = target_str; + while (*p == '.') { + if (p[1] == '.' && PathTraitsFS::IsSeparator(p[2])) { + /* "../" moves to parent directory */ + directory = directory->parent; + if (directory == nullptr) { + /* we have moved outside the music + directory - skip this symlink + if such symlinks are not allowed */ + return !follow_outside_symlinks; + } + p += 3; + } else if (PathTraitsFS::IsSeparator(p[1])) + /* eliminate "./" */ + p += 2; + else + break; + } + + /* we are still in the music directory, so this symlink points + to a song which is already in the database - skip according + to the follow_inside_symlinks param*/ + return !follow_inside_symlinks; +#else + /* no symlink checking on WIN32 */ + + (void)directory; + (void)utf8_name; + + return false; +#endif +} + +bool +UpdateWalk::UpdateDirectory(Directory &directory, const FileInfo &info) +{ + assert(info.IsDirectory()); + + directory_set_stat(directory, info); + + Error error; + const std::auto_ptr<StorageDirectoryReader> reader(storage.OpenDirectory(directory.GetPath(), error)); + if (reader.get() == nullptr) { + LogError(error); + return false; + } + + ExcludeList exclude_list; + + { + const auto exclude_path_fs = + storage.MapChildFS(directory.GetPath(), ".mpdignore"); + if (!exclude_path_fs.IsNull()) + exclude_list.LoadFile(exclude_path_fs); + } + + if (!exclude_list.IsEmpty()) + RemoveExcludedFromDirectory(directory, exclude_list); + + PurgeDeletedFromDirectory(directory); + + const char *name_utf8; + while (!cancel && (name_utf8 = reader->Read()) != nullptr) { + if (skip_path(name_utf8)) + continue; + + { + const auto name_fs = AllocatedPath::FromUTF8(name_utf8); + if (name_fs.IsNull() || exclude_list.Check(name_fs)) + continue; + } + + if (SkipSymlink(&directory, name_utf8)) { + modified |= editor.DeleteNameIn(directory, name_utf8); + continue; + } + + FileInfo info2; + if (!GetInfo(*reader, info2)) { + modified |= editor.DeleteNameIn(directory, name_utf8); + continue; + } + + UpdateDirectoryChild(directory, name_utf8, info2); + } + + directory.mtime = info.mtime; + + return true; +} + +inline Directory * +UpdateWalk::DirectoryMakeChildChecked(Directory &parent, + const char *uri_utf8, + const char *name_utf8) +{ + db_lock(); + Directory *directory = parent.FindChild(name_utf8); + db_unlock(); + + if (directory != nullptr) { + if (directory->IsMount()) + directory = nullptr; + + return directory; + } + + FileInfo info; + if (!GetInfo(storage, uri_utf8, info) || + FindAncestorLoop(storage, &parent, info.inode, info.device)) + return nullptr; + + if (SkipSymlink(&parent, name_utf8)) + return nullptr; + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + db_lock(); + Song *conflicting = parent.FindSong(name_utf8); + if (conflicting) + editor.DeleteSong(parent, conflicting); + + directory = parent.CreateChild(name_utf8); + db_unlock(); + + directory_set_stat(*directory, info); + return directory; +} + +inline Directory * +UpdateWalk::DirectoryMakeUriParentChecked(Directory &root, const char *uri) +{ + Directory *directory = &root; + char *duplicated = xstrdup(uri); + char *name_utf8 = duplicated, *slash; + + while ((slash = strchr(name_utf8, '/')) != nullptr) { + *slash = 0; + + if (*name_utf8 == 0) + continue; + + directory = DirectoryMakeChildChecked(*directory, + duplicated, + name_utf8); + if (directory == nullptr) + break; + + name_utf8 = slash + 1; + } + + free(duplicated); + return directory; +} + +inline void +UpdateWalk::UpdateUri(Directory &root, const char *uri) +{ + Directory *parent = DirectoryMakeUriParentChecked(root, uri); + if (parent == nullptr) + return; + + const char *name = PathTraitsUTF8::GetBase(uri); + + if (SkipSymlink(parent, name)) { + modified |= editor.DeleteNameIn(*parent, name); + return; + } + + FileInfo info; + if (!GetInfo(storage, uri, info)) { + modified |= editor.DeleteNameIn(*parent, name); + return; + } + + UpdateDirectoryChild(*parent, name, info); +} + +bool +UpdateWalk::Walk(Directory &root, const char *path, bool discard) +{ + walk_discard = discard; + modified = false; + + if (path != nullptr && !isRootDirectory(path)) { + UpdateUri(root, path); + } else { + FileInfo info; + if (!GetInfo(storage, "", info)) + return false; + + UpdateDirectory(root, info); + } + + return modified; +} diff --git a/src/db/update/Walk.hxx b/src/db/update/Walk.hxx new file mode 100644 index 000000000..cce276ab0 --- /dev/null +++ b/src/db/update/Walk.hxx @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_WALK_HXX +#define MPD_UPDATE_WALK_HXX + +#include "check.h" +#include "Editor.hxx" + +#include <sys/stat.h> + +struct stat; +struct FileInfo; +struct Directory; +struct ArchivePlugin; +class Storage; +class ExcludeList; + +class UpdateWalk final { +#ifdef ENABLE_ARCHIVE + friend class UpdateArchiveVisitor; +#endif + +#ifndef WIN32 + static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true; + static constexpr bool DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true; + + bool follow_inside_symlinks; + bool follow_outside_symlinks; +#endif + + bool walk_discard; + bool modified; + + /** + * Set to true by the main thread when the update thread shall + * cancel as quickly as possible. Access to this flag is + * unprotected. + */ + volatile bool cancel; + + Storage &storage; + + DatabaseEditor editor; + +public: + UpdateWalk(EventLoop &_loop, DatabaseListener &_listener, + Storage &_storage); + + /** + * Cancel the current update and quit the Walk() method as + * soon as possible. + */ + void Cancel() { + cancel = true; + } + + /** + * Returns true if the database was modified. + */ + bool Walk(Directory &root, const char *path, bool discard); + +private: + gcc_pure + bool SkipSymlink(const Directory *directory, + const char *utf8_name) const; + + void RemoveExcludedFromDirectory(Directory &directory, + const ExcludeList &exclude_list); + + void PurgeDeletedFromDirectory(Directory &directory); + + void UpdateSongFile2(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info); + + bool UpdateSongFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info); + + bool UpdateContainerFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info); + + +#ifdef ENABLE_ARCHIVE + void UpdateArchiveTree(Directory &parent, const char *name); + + bool UpdateArchiveFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info); + + void UpdateArchiveFile(Directory &directory, const char *name, + const FileInfo &info, + const ArchivePlugin &plugin); + + +#else + bool UpdateArchiveFile(gcc_unused Directory &directory, + gcc_unused const char *name, + gcc_unused const char *suffix, + gcc_unused const FileInfo &info) { + return false; + } +#endif + + bool UpdatePlaylistFile(Directory &directory, + const char *name, const char *suffix, + const FileInfo &info); + + bool UpdateRegularFile(Directory &directory, + const char *name, const FileInfo &info); + + void UpdateDirectoryChild(Directory &directory, + const char *name, const FileInfo &info); + + bool UpdateDirectory(Directory &directory, const FileInfo &info); + + /** + * Create the specified directory object if it does not exist + * already or if the #stat object indicates that it has been + * modified since the last update. Returns nullptr when it + * exists already and is unmodified. + * + * The caller must lock the database. + */ + Directory *MakeDirectoryIfModified(Directory &parent, const char *name, + const FileInfo &info); + + Directory *DirectoryMakeChildChecked(Directory &parent, + const char *uri_utf8, + const char *name_utf8); + + Directory *DirectoryMakeUriParentChecked(Directory &root, + const char *uri); + + void UpdateUri(Directory &root, const char *uri); +}; + +#endif diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx deleted file mode 100644 index c79fca5f9..000000000 --- a/src/decoder/AdPlugDecoderPlugin.cxx +++ /dev/null @@ -1,141 +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 "AdPlugDecoderPlugin.h" -#include "tag/TagHandler.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <adplug/adplug.h> -#include <adplug/emuopl.h> - -#include <assert.h> - -static unsigned sample_rate; - -static bool -adplug_init(const config_param ¶m) -{ - Error error; - - sample_rate = param.GetBlockValue("sample_rate", 48000u); - if (!audio_check_sample_rate(sample_rate, error)) { - LogError(error); - return false; - } - - return true; -} - -static void -adplug_file_decode(Decoder &decoder, const char *path_fs) -{ - CEmuopl opl(sample_rate, true, true); - opl.init(); - - CPlayer *player = CAdPlug::factory(path_fs, &opl); - if (player == nullptr) - return; - - const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, false, - player->songlength() / 1000.); - - int16_t buffer[2048]; - const unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2; - DecoderCommand cmd; - - do { - if (!player->update()) - break; - - opl.update(buffer, frames_per_buffer); - cmd = decoder_data(decoder, nullptr, - buffer, sizeof(buffer), - 0); - } while (cmd == DecoderCommand::NONE); - - delete player; -} - -static void -adplug_scan_tag(TagType type, const std::string &value, - const struct tag_handler *handler, void *handler_ctx) -{ - if (!value.empty()) - tag_handler_invoke_tag(handler, handler_ctx, - type, value.c_str()); -} - -static bool -adplug_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - CEmuopl opl(sample_rate, true, true); - opl.init(); - - CPlayer *player = CAdPlug::factory(path_fs, &opl); - if (player == nullptr) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - player->songlength() / 1000); - - if (handler->tag != nullptr) { - adplug_scan_tag(TAG_TITLE, player->gettitle(), - handler, handler_ctx); - adplug_scan_tag(TAG_ARTIST, player->getauthor(), - handler, handler_ctx); - adplug_scan_tag(TAG_COMMENT, player->getdesc(), - handler, handler_ctx); - } - - delete player; - return true; -} - -static const char *const adplug_suffixes[] = { - "amd", - "d00", - "hsc", - "laa", - "rad", - "raw", - "sa2", - nullptr -}; - -const struct DecoderPlugin adplug_decoder_plugin = { - "adplug", - adplug_init, - nullptr, - nullptr, - adplug_file_decode, - adplug_scan_file, - nullptr, - nullptr, - adplug_suffixes, - nullptr, -}; diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/AdPlugDecoderPlugin.h deleted file mode 100644 index a827fdc7d..000000000 --- a/src/decoder/AdPlugDecoderPlugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 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_DECODER_ADPLUG_H -#define MPD_DECODER_ADPLUG_H - -extern const struct DecoderPlugin adplug_decoder_plugin; - -#endif diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx deleted file mode 100644 index 9f097f90b..000000000 --- a/src/decoder/AudiofileDecoderPlugin.cxx +++ /dev/null @@ -1,283 +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 "AudiofileDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <audiofile.h> -#include <af_vfs.h> - -#include <assert.h> -#include <stdio.h> - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -static constexpr Domain audiofile_domain("audiofile"); - -struct AudioFileInputStream { - Decoder *const decoder; - InputStream &is; - - size_t Read(void *buffer, size_t size) { - /* libaudiofile does not like partial reads at all, - and will abort playback; therefore always force full - reads */ - return decoder_read_full(decoder, is, buffer, size) - ? size - : 0; - } -}; - -static int audiofile_get_duration(const char *file) -{ - int total_time; - AFfilehandle af_fp = afOpenFile(file, "r", nullptr); - if (af_fp == AF_NULL_FILEHANDLE) { - return -1; - } - total_time = (int) - ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) - / afGetRate(af_fp, AF_DEFAULT_TRACK)); - afCloseFile(af_fp); - return total_time; -} - -static ssize_t -audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - - return afis.Read(data, length); -} - -static AFfileoffset -audiofile_file_length(AFvirtualfile *vfile) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - InputStream &is = afis.is; - - return is.GetSize(); -} - -static AFfileoffset -audiofile_file_tell(AFvirtualfile *vfile) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - InputStream &is = afis.is; - - return is.GetOffset(); -} - -static void -audiofile_file_destroy(AFvirtualfile *vfile) -{ - assert(vfile->closure != nullptr); - - vfile->closure = nullptr; -} - -static AFfileoffset -audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative) -{ - AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; - InputStream &is = afis.is; - - int whence = (is_relative ? SEEK_CUR : SEEK_SET); - - Error error; - if (is.LockSeek(offset, whence, error)) { - LogError(error, "Seek failed"); - return is.GetOffset(); - } else { - return -1; - } -} - -static AFvirtualfile * -setup_virtual_fops(AudioFileInputStream &afis) -{ - AFvirtualfile *vf = new AFvirtualfile(); - vf->closure = &afis; - vf->write = nullptr; - vf->read = audiofile_file_read; - vf->length = audiofile_file_length; - vf->destroy = audiofile_file_destroy; - vf->seek = audiofile_file_seek; - vf->tell = audiofile_file_tell; - return vf; -} - -static SampleFormat -audiofile_bits_to_sample_format(int bits) -{ - switch (bits) { - case 8: - return SampleFormat::S8; - - case 16: - return SampleFormat::S16; - - case 24: - return SampleFormat::S24_P32; - - case 32: - return SampleFormat::S32; - } - - return SampleFormat::UNDEFINED; -} - -static SampleFormat -audiofile_setup_sample_format(AFfilehandle af_fp) -{ - int fs, bits; - - afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) { - FormatDebug(audiofile_domain, - "input file has %d bit samples, converting to 16", - bits); - bits = 16; - } - - afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, - AF_SAMPFMT_TWOSCOMP, bits); - afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - - return audiofile_bits_to_sample_format(bits); -} - -static void -audiofile_stream_decode(Decoder &decoder, InputStream &is) -{ - AFvirtualfile *vf; - int fs, frame_count; - AFfilehandle af_fp; - AudioFormat audio_format; - float total_time; - uint16_t bit_rate; - int ret; - char chunk[CHUNK_SIZE]; - - if (!is.IsSeekable()) { - LogWarning(audiofile_domain, "not seekable"); - return; - } - - AudioFileInputStream afis{&decoder, is}; - vf = setup_virtual_fops(afis); - - af_fp = afOpenVirtualFile(vf, "r", nullptr); - if (af_fp == AF_NULL_FILEHANDLE) { - LogWarning(audiofile_domain, "failed to input stream"); - return; - } - - Error error; - if (!audio_format_init_checked(audio_format, - afGetRate(af_fp, AF_DEFAULT_TRACK), - audiofile_setup_sample_format(af_fp), - afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK), - error)) { - LogError(error); - afCloseFile(af_fp); - return; - } - - frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - - total_time = ((float)frame_count / (float)audio_format.sample_rate); - - bit_rate = (uint16_t)(is.GetSize() * 8.0 / total_time / 1000.0 + 0.5); - - fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); - - decoder_initialized(decoder, audio_format, true, total_time); - - DecoderCommand cmd; - do { - ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, - CHUNK_SIZE / fs); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, nullptr, - chunk, ret * fs, - bit_rate); - - if (cmd == DecoderCommand::SEEK) { - AFframecount frame = decoder_seek_where(decoder) * - audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); - - decoder_command_finished(decoder); - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); - - afCloseFile(af_fp); -} - -static bool -audiofile_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - int total_time = audiofile_get_duration(file); - - if (total_time < 0) { - FormatWarning(audiofile_domain, - "Failed to get total song time from: %s", - file); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const audiofile_suffixes[] = { - "wav", "au", "aiff", "aif", nullptr -}; - -static const char *const audiofile_mime_types[] = { - "audio/x-wav", - "audio/x-aiff", - nullptr -}; - -const struct DecoderPlugin audiofile_decoder_plugin = { - "audiofile", - nullptr, - nullptr, - audiofile_stream_decode, - nullptr, - audiofile_scan_file, - nullptr, - nullptr, - audiofile_suffixes, - audiofile_mime_types, -}; diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/AudiofileDecoderPlugin.hxx deleted file mode 100644 index 5a17281b0..000000000 --- a/src/decoder/AudiofileDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_AUDIOFILE_HXX -#define MPD_DECODER_AUDIOFILE_HXX - -extern const struct DecoderPlugin audiofile_decoder_plugin; - -#endif diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx new file mode 100644 index 000000000..ae600260f --- /dev/null +++ b/src/decoder/DecoderAPI.cxx @@ -0,0 +1,632 @@ +/* + * 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 "DecoderAPI.hxx" +#include "DecoderError.hxx" +#include "pcm/PcmConvert.hxx" +#include "AudioConfig.hxx" +#include "ReplayGainConfig.hxx" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <math.h> + +void +decoder_initialized(Decoder &decoder, + const AudioFormat audio_format, + bool seekable, float total_time) +{ + DecoderControl &dc = decoder.dc; + struct audio_format_string af_string; + + assert(dc.state == DecoderState::START); + assert(dc.pipe != nullptr); + assert(decoder.convert == nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(!decoder.seeking); + assert(audio_format.IsDefined()); + assert(audio_format.IsValid()); + + dc.in_audio_format = audio_format; + dc.out_audio_format = getOutputAudioFormat(audio_format); + + dc.seekable = seekable; + dc.total_time = total_time; + + FormatDebug(decoder_domain, "audio_format=%s, seekable=%s", + audio_format_to_string(dc.in_audio_format, &af_string), + seekable ? "true" : "false"); + + if (dc.in_audio_format != dc.out_audio_format) { + FormatDebug(decoder_domain, "converting to %s", + audio_format_to_string(dc.out_audio_format, + &af_string)); + + decoder.convert = new PcmConvert(); + + Error error; + if (!decoder.convert->Open(dc.in_audio_format, + dc.out_audio_format, + error)) + decoder.error = std::move(error); + } + + dc.Lock(); + dc.state = DecoderState::DECODE; + dc.client_cond.signal(); + dc.Unlock(); +} + +/** + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. + */ +gcc_pure +static bool +decoder_prepare_initial_seek(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (dc.state != DecoderState::DECODE) + /* wait until the decoder has finished initialisation + (reading file headers etc.) before emitting the + virtual "SEEK" command */ + return false; + + if (decoder.initial_seek_running) + /* initial seek has already begun - override any other + command */ + return true; + + if (decoder.initial_seek_pending) { + if (!dc.seekable) { + /* seeking is not possible */ + decoder.initial_seek_pending = false; + return false; + } + + if (dc.command == DecoderCommand::NONE) { + /* begin initial seek */ + + decoder.initial_seek_pending = false; + decoder.initial_seek_running = true; + return true; + } + + /* skip initial seek when there's another command + (e.g. STOP) */ + + decoder.initial_seek_pending = false; + } + + return false; +} + +/** + * Returns the current decoder command. May return a "virtual" + * synthesized command, e.g. to seek to the beginning of the CUE + * track. + */ +gcc_pure +static DecoderCommand +decoder_get_virtual_command(Decoder &decoder) +{ + if (decoder.error.IsDefined()) + /* an error has occurred: stop the decoder plugin */ + return DecoderCommand::STOP; + + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (decoder_prepare_initial_seek(decoder)) + return DecoderCommand::SEEK; + + return dc.command; +} + +DecoderCommand +decoder_get_command(Decoder &decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void +decoder_command_finished(Decoder &decoder) +{ + DecoderControl &dc = decoder.dc; + + dc.Lock(); + + assert(dc.command != DecoderCommand::NONE || + decoder.initial_seek_running); + assert(dc.command != DecoderCommand::SEEK || + decoder.initial_seek_running || + dc.seek_error || decoder.seeking); + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) { + assert(!decoder.seeking); + assert(decoder.chunk == nullptr); + assert(dc.pipe->IsEmpty()); + + decoder.initial_seek_running = false; + decoder.timestamp = dc.start_ms / 1000.; + dc.Unlock(); + return; + } + + if (decoder.seeking) { + decoder.seeking = false; + + /* delete frames from the old song position */ + + if (decoder.chunk != nullptr) { + dc.buffer->Return(decoder.chunk); + decoder.chunk = nullptr; + } + + dc.pipe->Clear(*dc.buffer); + + decoder.timestamp = dc.seek_where; + } + + dc.command = DecoderCommand::NONE; + dc.client_cond.signal(); + dc.Unlock(); +} + +double decoder_seek_where(gcc_unused Decoder & decoder) +{ + const DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) + return dc.start_ms / 1000.; + + assert(dc.command == DecoderCommand::SEEK); + + decoder.seeking = true; + + return dc.seek_where; +} + +void decoder_seek_error(Decoder & decoder) +{ + DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + if (decoder.initial_seek_running) { + /* d'oh, we can't seek to the sub-song start position, + what now? - no idea, ignoring the problem for now. */ + decoder.initial_seek_running = false; + return; + } + + assert(dc.command == DecoderCommand::SEEK); + + dc.seek_error = true; + decoder.seeking = false; + + decoder_command_finished(decoder); +} + +InputStream * +decoder_open_uri(Decoder &decoder, const char *uri, Error &error) +{ + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + DecoderControl &dc = decoder.dc; + Mutex &mutex = dc.mutex; + Cond &cond = dc.cond; + + InputStream *is = InputStream::Open(uri, mutex, cond, error); + if (is == nullptr) + return nullptr; + + mutex.lock(); + while (true) { + is->Update(); + if (is->IsReady()) { + mutex.unlock(); + return is; + } + + if (dc.command == DecoderCommand::STOP) { + mutex.unlock(); + delete is; + return nullptr; + } + + cond.wait(mutex); + } +} + +/** + * Should be read operation be cancelled? That is the case when the + * player thread has sent a command such as "STOP". + */ +gcc_pure +static inline bool +decoder_check_cancel_read(const Decoder *decoder) +{ + if (decoder == nullptr) + return false; + + const DecoderControl &dc = decoder->dc; + if (dc.command == DecoderCommand::NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc.command == DecoderCommand::SEEK && + (dc.state == DecoderState::START || decoder->seeking)) + return false; + + return true; +} + +size_t +decoder_read(Decoder *decoder, + InputStream &is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==nullptr */ + + assert(decoder == nullptr || + decoder->dc.state == DecoderState::START || + decoder->dc.state == DecoderState::DECODE); + assert(buffer != nullptr); + + if (length == 0) + return 0; + + is.Lock(); + + while (true) { + if (decoder_check_cancel_read(decoder)) { + is.Unlock(); + return 0; + } + + if (is.IsAvailable()) + break; + + is.cond.wait(is.mutex); + } + + Error error; + size_t nbytes = is.Read(buffer, length, error); + assert(nbytes == 0 || !error.IsDefined()); + assert(nbytes > 0 || error.IsDefined() || is.IsEOF()); + + is.Unlock(); + + if (gcc_unlikely(nbytes == 0 && error.IsDefined())) + LogError(error); + + return nbytes; +} + +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *_buffer, size_t size) +{ + uint8_t *buffer = (uint8_t *)_buffer; + + while (size > 0) { + size_t nbytes = decoder_read(decoder, is, buffer, size); + if (nbytes == 0) + return false; + + buffer += nbytes; + size -= nbytes; + } + + return true; +} + +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size) +{ + while (size > 0) { + char buffer[1024]; + size_t nbytes = decoder_read(decoder, is, buffer, + std::min(sizeof(buffer), size)); + if (nbytes == 0) + return false; + + size -= nbytes; + } + + return true; +} + +void +decoder_timestamp(Decoder &decoder, double t) +{ + assert(t >= 0); + + decoder.timestamp = t; +} + +/** + * Sends a #tag as-is to the music pipe. Flushes the current chunk + * (decoder.chunk) if there is one. + */ +static DecoderCommand +do_send_tag(Decoder &decoder, const Tag &tag) +{ + MusicChunk *chunk; + + if (decoder.chunk != nullptr) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder.FlushChunk(); + } + + assert(decoder.chunk == nullptr); + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(decoder.dc.command != DecoderCommand::NONE); + return decoder.dc.command; + } + + chunk->tag = new Tag(tag); + return DecoderCommand::NONE; +} + +static bool +update_stream_tag(Decoder &decoder, InputStream *is) +{ + Tag *tag; + + tag = is != nullptr + ? is->LockReadTag() + : nullptr; + if (tag == nullptr) { + tag = decoder.song_tag; + if (tag == nullptr) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder.song_tag = nullptr; + } + + delete decoder.stream_tag; + decoder.stream_tag = tag; + return true; +} + +DecoderCommand +decoder_data(Decoder &decoder, + InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + assert(length % dc.in_audio_format.GetFrameSize() == 0); + + dc.Lock(); + cmd = decoder_get_virtual_command(decoder); + dc.Unlock(); + + if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK || + length == 0) + return cmd; + + /* send stream tags */ + + if (update_stream_tag(decoder, is)) { + if (decoder.decoder_tag != nullptr) { + /* merge with tag from decoder plugin */ + Tag *tag = Tag::Merge(*decoder.decoder_tag, + *decoder.stream_tag); + cmd = do_send_tag(decoder, *tag); + delete tag; + } else + /* send only the stream tag */ + cmd = do_send_tag(decoder, *decoder.stream_tag); + + if (cmd != DecoderCommand::NONE) + return cmd; + } + + if (decoder.convert != nullptr) { + assert(dc.in_audio_format != dc.out_audio_format); + + Error error; + auto result = decoder.convert->Convert({data, length}, + error); + if (data == nullptr) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + LogError(error); + return DecoderCommand::STOP; + } + + data = result.data; + length = result.size; + } else { + assert(dc.in_audio_format == dc.out_audio_format); + } + + while (length > 0) { + MusicChunk *chunk; + bool full; + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(dc.command != DecoderCommand::NONE); + return dc.command; + } + + const auto dest = + chunk->Write(dc.out_audio_format, + decoder.timestamp - + dc.song->GetStartMS() / 1000.0, + kbit_rate); + if (dest.IsNull()) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + continue; + } + + size_t nbytes = dest.size; + assert(nbytes > 0); + + if (nbytes > length) + nbytes = length; + + /* copy the buffer */ + + memcpy(dest.data, data, nbytes); + + /* expand the music pipe chunk */ + + full = chunk->Expand(dc.out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + } + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + + decoder.timestamp += (double)nbytes / + dc.out_audio_format.GetTimeToSize(); + + if (dc.end_ms > 0 && + decoder.timestamp >= dc.end_ms / 1000.0) + /* the end of this range has been reached: + stop decoding */ + return DecoderCommand::STOP; + } + + return DecoderCommand::NONE; +} + +DecoderCommand +decoder_tag(Decoder &decoder, InputStream *is, + Tag &&tag) +{ + gcc_unused const DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + + /* save the tag */ + + delete decoder.decoder_tag; + decoder.decoder_tag = new Tag(tag); + + /* check for a new stream tag */ + + update_stream_tag(decoder, is); + + /* check if we're seeking */ + + if (decoder_prepare_initial_seek(decoder)) + /* during initial seek, no music chunk must be created + until seeking is finished; skip the rest of the + function here */ + return DecoderCommand::SEEK; + + /* send tag to music pipe */ + + if (decoder.stream_tag != nullptr) { + /* merge with tag from input stream */ + Tag *merged; + + merged = Tag::Merge(*decoder.stream_tag, + *decoder.decoder_tag); + cmd = do_send_tag(decoder, *merged); + delete merged; + } else + /* send only the decoder tag */ + cmd = do_send_tag(decoder, *decoder.decoder_tag); + + return cmd; +} + +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info) +{ + if (replay_gain_info != nullptr) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + ReplayGainMode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + const auto &tuple = replay_gain_info->tuples[rgm]; + const auto scale = + tuple.CalculateScale(replay_gain_preamp, + replay_gain_missing_preamp, + replay_gain_limit); + decoder.dc.replay_gain_db = 20.0 * log10f(scale); + } + + decoder.replay_gain_info = *replay_gain_info; + decoder.replay_gain_serial = serial; + + if (decoder.chunk != nullptr) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder.FlushChunk(); + } + } else + decoder.replay_gain_serial = 0; +} + +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp) +{ + DecoderControl &dc = decoder.dc; + + dc.SetMixRamp(std::move(mix_ramp)); +} diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx new file mode 100644 index 000000000..c57a02e01 --- /dev/null +++ b/src/decoder/DecoderAPI.hxx @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief The MPD Decoder API + * + * This is the public API which is used by decoder plugins to + * communicate with the mpd core. + */ + +#ifndef MPD_DECODER_API_HXX +#define MPD_DECODER_API_HXX + +// IWYU pragma: begin_exports + +#include "check.h" +#include "DecoderCommand.hxx" +#include "DecoderPlugin.hxx" +#include "ReplayGainInfo.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +class Error; + +/** + * Notify the player thread that it has finished initialization and + * that it has read the song's meta data. + * + * @param decoder the decoder object + * @param audio_format the audio format which is going to be sent to + * decoder_data() + * @param seekable true if the song is seekable + * @param total_time the total number of seconds in this song; -1 if unknown + */ +void +decoder_initialized(Decoder &decoder, + AudioFormat audio_format, + bool seekable, float total_time); + +/** + * Determines the pending decoder command. + * + * @param decoder the decoder object + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +gcc_pure +DecoderCommand +decoder_get_command(Decoder &decoder); + +/** + * Called by the decoder when it has performed the requested command + * (dc->command). This function resets dc->command and wakes up the + * player thread. + * + * @param decoder the decoder object + */ +void +decoder_command_finished(Decoder &decoder); + +/** + * Call this when you have received the DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week + */ +gcc_pure +double +decoder_seek_where(Decoder &decoder); + +/** + * Call this instead of decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(Decoder &decoder); + +/** + * Open a new #InputStream and wait until it's ready. Can get + * cancelled by DecoderCommand::STOP (returns nullptr without setting + * #Error). + */ +InputStream * +decoder_open_uri(Decoder &decoder, const char *uri, Error &error); + +/** + * Blocking read from the input stream. + * + * @param decoder the decoder object + * @param is the input stream to read from + * @param buffer the destination buffer + * @param length the maximum number of bytes to read + * @return the number of bytes read, or 0 if one of the following + * occurs: end of file; error; command (like SEEK or STOP). + */ +size_t +decoder_read(Decoder *decoder, InputStream &is, + void *buffer, size_t length); + +static inline size_t +decoder_read(Decoder &decoder, InputStream &is, + void *buffer, size_t length) +{ + return decoder_read(&decoder, is, buffer, length); +} + +/** + * Blocking read from the input stream. Attempts to fill the buffer + * completely; there is no partial result. + * + * @return true on success, false on error or command or not enough + * data + */ +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *buffer, size_t size); + +/** + * Skip data on the #InputStream. + * + * @return true on success, false on error or command + */ +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size); + +/** + * Sets the time stamp for the next data chunk [seconds]. The MPD + * core automatically counts it up, and a decoder plugin only needs to + * use this function if it thinks that adding to the time stamp based + * on the buffer size won't work. + */ +void +decoder_timestamp(Decoder &decoder, double t); + +/** + * This function is called by the decoder plugin when it has + * successfully decoded block of input data. + * + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param data the source buffer + * @param length the number of bytes in the buffer + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_data(Decoder &decoder, InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate); + +static inline DecoderCommand +decoder_data(Decoder &decoder, InputStream &is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + return decoder_data(decoder, &is, data, length, kbit_rate); +} + +/** + * This function is called by the decoder plugin when it has + * successfully decoded a tag. + * + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param tag the tag to send + * @return the current command, or DecoderCommand::NONE if there is no + * command pending + */ +DecoderCommand +decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag); + +static inline DecoderCommand +decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag) +{ + return decoder_tag(decoder, &is, std::move(tag)); +} + +/** + * Set replay gain values for the following chunks. + * + * @param decoder the decoder object + * @param rgi the replay_gain_info object; may be nullptr to invalidate + * the previous replay gain values + */ +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate + * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate + */ +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp); + +#endif diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx new file mode 100644 index 000000000..7902e6e89 --- /dev/null +++ b/src/decoder/DecoderBuffer.cxx @@ -0,0 +1,178 @@ +/* + * 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 "DecoderBuffer.hxx" +#include "DecoderAPI.hxx" +#include "util/ConstBuffer.hxx" +#include "util/VarSize.hxx" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +struct DecoderBuffer { + Decoder *decoder; + InputStream *is; + + /** the allocated size of the buffer */ + size_t size; + + /** the current length of the buffer */ + size_t length; + + /** number of bytes already consumed at the beginning of the + buffer */ + size_t consumed; + + /** the actual buffer (dynamic size) */ + unsigned char data[sizeof(size_t)]; + + DecoderBuffer(Decoder *_decoder, InputStream &_is, + size_t _size) + :decoder(_decoder), is(&_is), + size(_size), length(0), consumed(0) {} +}; + +DecoderBuffer * +decoder_buffer_new(Decoder *decoder, InputStream &is, + size_t size) +{ + assert(size > 0); + + return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data), + size, + decoder, is, size); +} + +void +decoder_buffer_free(DecoderBuffer *buffer) +{ + assert(buffer != nullptr); + + DeleteVarSize(buffer); +} + +const InputStream & +decoder_buffer_get_stream(const DecoderBuffer *buffer) +{ + return *buffer->is; +} + +void +decoder_buffer_clear(DecoderBuffer *buffer) +{ + buffer->length = buffer->consumed = 0; +} + +static void +decoder_buffer_shift(DecoderBuffer *buffer) +{ + assert(buffer->consumed > 0); + + buffer->length -= buffer->consumed; + memmove(buffer->data, buffer->data + buffer->consumed, buffer->length); + buffer->consumed = 0; +} + +bool +decoder_buffer_fill(DecoderBuffer *buffer) +{ + size_t nbytes; + + if (buffer->consumed > 0) + decoder_buffer_shift(buffer); + + if (buffer->length >= buffer->size) + /* buffer is full */ + return false; + + nbytes = decoder_read(buffer->decoder, *buffer->is, + buffer->data + buffer->length, + buffer->size - buffer->length); + if (nbytes == 0) + /* end of file, I/O error or decoder command + received */ + return false; + + buffer->length += nbytes; + assert(buffer->length <= buffer->size); + + return true; +} + +static const void * +decoder_buffer_head(const DecoderBuffer *buffer) +{ + return buffer->data + buffer->consumed; +} + +size_t +decoder_buffer_available(const DecoderBuffer *buffer) +{ + return buffer->length - buffer->consumed; +} + +ConstBuffer<void> +decoder_buffer_read(const DecoderBuffer *buffer) +{ + return { + decoder_buffer_head(buffer), + decoder_buffer_available(buffer), + }; +} + +ConstBuffer<void> +decoder_buffer_need(DecoderBuffer *buffer, size_t min_size) +{ + while (true) { + const auto available = decoder_buffer_available(buffer); + if (available >= min_size) + return { decoder_buffer_head(buffer), available }; + + if (!decoder_buffer_fill(buffer)) + return nullptr; + } +} + +void +decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes) +{ + /* just move the "consumed" pointer - decoder_buffer_shift() + will do the real work later (called by + decoder_buffer_fill()) */ + buffer->consumed += nbytes; + + assert(buffer->consumed <= buffer->length); +} + +bool +decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes) +{ + const size_t available = decoder_buffer_available(buffer); + if (available >= nbytes) { + decoder_buffer_consume(buffer, nbytes); + return true; + } + + decoder_buffer_clear(buffer); + nbytes -= available; + + return decoder_skip(buffer->decoder, *buffer->is, nbytes); +} diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx new file mode 100644 index 000000000..4a482be75 --- /dev/null +++ b/src/decoder/DecoderBuffer.hxx @@ -0,0 +1,121 @@ +/* + * 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_DECODER_BUFFER_HXX +#define MPD_DECODER_BUFFER_HXX + +#include "Compiler.h" + +#include <stddef.h> + +/** + * This objects handles buffered reads in decoder plugins easily. You + * create a buffer object, and use its high-level methods to fill and + * read it. It will automatically handle shifting the buffer. + */ +struct DecoderBuffer; + +struct Decoder; +class InputStream; + +template<typename T> struct ConstBuffer; + +/** + * Creates a new buffer. + * + * @param decoder the decoder object, used for decoder_read(), may be nullptr + * @param is the input stream object where we should read from + * @param size the maximum size of the buffer + * @return the new decoder_buffer object + */ +DecoderBuffer * +decoder_buffer_new(Decoder *decoder, InputStream &is, + size_t size); + +/** + * Frees resources used by the decoder_buffer object. + */ +void +decoder_buffer_free(DecoderBuffer *buffer); + +gcc_pure +const InputStream & +decoder_buffer_get_stream(const DecoderBuffer *buffer); + +void +decoder_buffer_clear(DecoderBuffer *buffer); + +/** + * Read data from the input_stream and append it to the buffer. + * + * @return true if data was appended; false if there is no data + * available (yet), end of file, I/O error or a decoder command was + * received + */ +bool +decoder_buffer_fill(DecoderBuffer *buffer); + +/** + * How many bytes are stored in the buffer? + */ +gcc_pure +size_t +decoder_buffer_available(const DecoderBuffer *buffer); + +/** + * Reads data from the buffer. This data is not yet consumed, you + * have to call decoder_buffer_consume() to do that. The returned + * buffer becomes invalid after a decoder_buffer_fill() or a + * decoder_buffer_consume() call. + * + * @param buffer the decoder_buffer object + */ +gcc_pure +ConstBuffer<void> +decoder_buffer_read(const DecoderBuffer *buffer); + +/** + * Wait until this number of bytes are available. Returns nullptr on + * error. + */ +ConstBuffer<void> +decoder_buffer_need(DecoderBuffer *buffer, size_t min_size); + +/** + * Consume (delete, invalidate) a part of the buffer. The "nbytes" + * parameter must not be larger than the length returned by + * decoder_buffer_read(). + * + * @param buffer the decoder_buffer object + * @param nbytes the number of bytes to consume + */ +void +decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes); + +/** + * Skips the specified number of bytes, discarding its data. + * + * @param buffer the decoder_buffer object + * @param nbytes the number of bytes to skip + * @return true on success, false on error + */ +bool +decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes); + +#endif diff --git a/src/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx new file mode 100644 index 000000000..a00519644 --- /dev/null +++ b/src/decoder/DecoderCommand.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_COMMAND_HXX +#define MPD_DECODER_COMMAND_HXX + +#include <stdint.h> + +enum class DecoderCommand : uint8_t { + NONE = 0, + START, + STOP, + SEEK +}; + +#endif diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx new file mode 100644 index 000000000..d78fc66c9 --- /dev/null +++ b/src/decoder/DecoderControl.cxx @@ -0,0 +1,140 @@ +/* + * 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 "DecoderControl.hxx" +#include "MusicPipe.hxx" +#include "DetachedSong.hxx" + +#include <assert.h> + +DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond) + :mutex(_mutex), client_cond(_client_cond), + state(DecoderState::STOP), + command(DecoderCommand::NONE), + client_is_waiting(false), + song(nullptr), + replay_gain_db(0), replay_gain_prev_db(0) {} + +DecoderControl::~DecoderControl() +{ + ClearError(); + + delete song; +} + +void +DecoderControl::WaitForDecoder() +{ + assert(!client_is_waiting); + client_is_waiting = true; + + client_cond.wait(mutex); + + assert(client_is_waiting); + client_is_waiting = false; +} + +bool +DecoderControl::IsCurrentSong(const DetachedSong &_song) const +{ + switch (state) { + case DecoderState::STOP: + case DecoderState::ERROR: + return false; + + case DecoderState::START: + case DecoderState::DECODE: + return song->IsSame(_song); + } + + assert(false); + gcc_unreachable(); +} + +void +DecoderControl::Start(DetachedSong *_song, + unsigned _start_ms, unsigned _end_ms, + MusicBuffer &_buffer, MusicPipe &_pipe) +{ + assert(_song != nullptr); + assert(_pipe.IsEmpty()); + + delete song; + song = _song; + start_ms = _start_ms; + end_ms = _end_ms; + buffer = &_buffer; + pipe = &_pipe; + + LockSynchronousCommand(DecoderCommand::START); +} + +void +DecoderControl::Stop() +{ + Lock(); + + if (command != DecoderCommand::NONE) + /* Attempt to cancel the current command. If it's too + late and the decoder thread is already executing + the old command, we'll call STOP again in this + function (see below). */ + SynchronousCommandLocked(DecoderCommand::STOP); + + if (state != DecoderState::STOP && state != DecoderState::ERROR) + SynchronousCommandLocked(DecoderCommand::STOP); + + Unlock(); +} + +bool +DecoderControl::Seek(double where) +{ + assert(state != DecoderState::START); + assert(where >= 0.0); + + if (state == DecoderState::STOP || + state == DecoderState::ERROR || !seekable) + return false; + + seek_where = where; + seek_error = false; + LockSynchronousCommand(DecoderCommand::SEEK); + + return !seek_error; +} + +void +DecoderControl::Quit() +{ + assert(thread.IsDefined()); + + quit = true; + LockAsynchronousCommand(DecoderCommand::STOP); + + thread.Join(); +} + +void +DecoderControl::CycleMixRamp() +{ + previous_mix_ramp = std::move(mix_ramp); + mix_ramp.Clear(); +} diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx new file mode 100644 index 000000000..f78ce1a31 --- /dev/null +++ b/src/decoder/DecoderControl.hxx @@ -0,0 +1,395 @@ +/* + * 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_DECODER_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "DecoderCommand.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stdint.h> + +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif + +class DetachedSong; +class MusicBuffer; +class MusicPipe; + +enum class DecoderState : uint8_t { + STOP = 0, + START, + DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + ERROR, +}; + +struct DecoderControl { + /** + * The handle of the decoder thread. + */ + Thread thread; + + /** + * This lock protects #state and #command. + * + * This is usually a reference to PlayerControl::mutex, so + * that both player thread and decoder thread share a mutex. + * This simplifies synchronization with #cond and + * #client_cond. + */ + Mutex &mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + Cond cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + * + * This is usually a reference to PlayerControl::cond. + */ + Cond &client_cond; + + DecoderState state; + DecoderCommand command; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DecoderState::ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DecoderState::START). + */ + Error error; + + bool quit; + + /** + * Is the client currently waiting for the DecoderThread? If + * false, the DecoderThread may omit invoking Cond::signal(), + * reducing the number of system calls. + */ + bool client_is_waiting; + + bool seek_error; + bool seekable; + double seek_where; + + /** the format of the song file */ + AudioFormat in_audio_format; + + /** the format being sent to the music pipe */ + AudioFormat out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DecoderCommand::START + * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + DetachedSong *song; + + /** + * The initial seek position (in milliseconds), e.g. to the + * start of a sub-track described by a CUE file. + * + * This attribute is set by dc_start(). + */ + unsigned start_ms; + + /** + * The decoder will stop when it reaches this position (in + * milliseconds). 0 means don't stop before the end of the + * file. + * + * This attribute is set by dc_start(). + */ + unsigned end_ms; + + float total_time; + + /** the #MusicChunk allocator */ + MusicBuffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + MusicPipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + + MixRampInfo mix_ramp, previous_mix_ramp; + + /** + * @param _mutex see #mutex + * @param _client_cond see #client_cond + */ + DecoderControl(Mutex &_mutex, Cond &_client_cond); + ~DecoderControl(); + + /** + * Locks the object. + */ + void Lock() const { + mutex.lock(); + } + + /** + * Unlocks the object. + */ + void Unlock() const { + mutex.unlock(); + } + + /** + * Signals the object. This function is only valid in the + * player thread. The object should be locked prior to + * calling this function. + */ + void Signal() { + cond.signal(); + } + + /** + * Waits for a signal on the #DecoderControl object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ + void Wait() { + cond.wait(mutex); + } + + /** + * Waits for a signal from the decoder thread. This object + * must be locked prior to calling this function. This method + * is only valid in the player thread. + * + * Caller must hold the lock. + */ + void WaitForDecoder(); + + bool IsIdle() const { + return state == DecoderState::STOP || + state == DecoderState::ERROR; + } + + gcc_pure + bool LockIsIdle() const { + Lock(); + bool result = IsIdle(); + Unlock(); + return result; + } + + bool IsStarting() const { + return state == DecoderState::START; + } + + gcc_pure + bool LockIsStarting() const { + Lock(); + bool result = IsStarting(); + Unlock(); + return result; + } + + bool HasFailed() const { + assert(command == DecoderCommand::NONE); + + return state == DecoderState::ERROR; + } + + gcc_pure + bool LockHasFailed() const { + Lock(); + bool result = HasFailed(); + Unlock(); + return result; + } + + /** + * Checks whether an error has occurred, and if so, returns a + * copy of the #Error object. + * + * Caller must lock the object. + */ + gcc_pure + Error GetError() const { + assert(command == DecoderCommand::NONE); + assert(state != DecoderState::ERROR || error.IsDefined()); + + Error result; + if (state == DecoderState::ERROR) + result.Set(error); + return result; + } + + /** + * Like dc_get_error(), but locks and unlocks the object. + */ + gcc_pure + Error LockGetError() const { + Lock(); + Error result = GetError(); + Unlock(); + return result; + } + + /** + * Clear the error condition and free the #Error object (if any). + * + * Caller must lock the object. + */ + void ClearError() { + if (state == DecoderState::ERROR) { + error.Clear(); + state = DecoderState::STOP; + } + } + + /** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ + gcc_pure + bool IsCurrentSong(const DetachedSong &_song) const; + + gcc_pure + bool LockIsCurrentSong(const DetachedSong &_song) const { + Lock(); + const bool result = IsCurrentSong(_song); + Unlock(); + return result; + } + +private: + /** + * Wait for the command to be finished by the decoder thread. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void WaitCommandLocked() { + while (command != DecoderCommand::NONE) + WaitForDecoder(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. Caller must lock the + * object. + */ + void SynchronousCommandLocked(DecoderCommand cmd) { + command = cmd; + Signal(); + WaitCommandLocked(); + } + + /** + * Send a command to the decoder thread and synchronously wait + * for it to finish. + * + * To be called from the client thread. This method locks the + * object. + */ + void LockSynchronousCommand(DecoderCommand cmd) { + Lock(); + ClearError(); + SynchronousCommandLocked(cmd); + Unlock(); + } + + void LockAsynchronousCommand(DecoderCommand cmd) { + Lock(); + command = cmd; + Signal(); + Unlock(); + } + +public: + /** + * Start the decoder. + * + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_ms see #DecoderControl + * @param end_ms see #DecoderControl + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms, + MusicBuffer &buffer, MusicPipe &pipe); + + void Stop(); + + bool Seek(double where); + + void Quit(); + + const char *GetMixRampStart() const { + return mix_ramp.GetStart(); + } + + const char *GetMixRampEnd() const { + return mix_ramp.GetEnd(); + } + + const char *GetMixRampPreviousEnd() const { + return previous_mix_ramp.GetEnd(); + } + + void SetMixRamp(MixRampInfo &&new_value) { + mix_ramp = std::move(new_value); + } + + /** + * Move mixramp_end to mixramp_prev_end and clear + * mixramp_start/mixramp_end. + */ + void CycleMixRamp(); +}; + +#endif diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx new file mode 100644 index 000000000..bd3842837 --- /dev/null +++ b/src/decoder/DecoderError.cxx @@ -0,0 +1,23 @@ +/* + * 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 "DecoderError.hxx" +#include "util/Domain.hxx" + +const Domain decoder_domain("decoder"); diff --git a/src/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx new file mode 100644 index 000000000..83cf98204 --- /dev/null +++ b/src/decoder/DecoderError.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_DECODER_ERROR_HXX +#define MPD_DECODER_ERROR_HXX + +extern const class Domain decoder_domain; + +#endif diff --git a/src/decoder/DecoderInternal.cxx b/src/decoder/DecoderInternal.cxx new file mode 100644 index 000000000..416a75b75 --- /dev/null +++ b/src/decoder/DecoderInternal.cxx @@ -0,0 +1,101 @@ +/* + * 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 "DecoderInternal.hxx" +#include "DecoderControl.hxx" +#include "pcm/PcmConvert.hxx" +#include "MusicPipe.hxx" +#include "MusicBuffer.hxx" +#include "MusicChunk.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> + +Decoder::~Decoder() +{ + /* caller must flush the chunk */ + assert(chunk == nullptr); + + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + delete song_tag; + delete stream_tag; + delete decoder_tag; +} + +/** + * All chunks are full of decoded data; wait for the player to free + * one. + */ +static DecoderCommand +need_chunks(DecoderControl &dc) +{ + if (dc.command == DecoderCommand::NONE) + dc.Wait(); + + return dc.command; +} + +MusicChunk * +Decoder::GetChunk() +{ + DecoderCommand cmd; + + if (chunk != nullptr) + return chunk; + + do { + chunk = dc.buffer->Allocate(); + if (chunk != nullptr) { + chunk->replay_gain_serial = replay_gain_serial; + if (replay_gain_serial != 0) + chunk->replay_gain_info = replay_gain_info; + + return chunk; + } + + dc.Lock(); + cmd = need_chunks(dc); + dc.Unlock(); + } while (cmd == DecoderCommand::NONE); + + return nullptr; +} + +void +Decoder::FlushChunk() +{ + assert(chunk != nullptr); + + if (chunk->IsEmpty()) + dc.buffer->Return(chunk); + else + dc.pipe->Push(chunk); + + chunk = nullptr; + + dc.Lock(); + if (dc.client_is_waiting) + dc.client_cond.signal(); + dc.Unlock(); +} diff --git a/src/decoder/DecoderInternal.hxx b/src/decoder/DecoderInternal.hxx new file mode 100644 index 000000000..e6c30d071 --- /dev/null +++ b/src/decoder/DecoderInternal.hxx @@ -0,0 +1,126 @@ +/* + * 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_DECODER_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX + +#include "ReplayGainInfo.hxx" +#include "util/Error.hxx" + +class PcmConvert; +struct MusicChunk; +struct DecoderControl; +struct Tag; + +struct Decoder { + DecoderControl &dc; + + /** + * For converting input data to the configured audio format. + * nullptr means no conversion necessary. + */ + PcmConvert *convert; + + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + + /** + * Is the initial seek (to the start position of the sub-song) + * pending, or has it been performed already? + */ + bool initial_seek_pending; + + /** + * Is the initial seek currently running? During this time, + * the decoder command is SEEK. This flag is set by + * decoder_get_virtual_command(), when the virtual SEEK + * command is generated for the first time. + */ + bool initial_seek_running; + + /** + * This flag is set by decoder_seek_where(), and checked by + * decoder_command_finished(). It is used to clean up after + * seeking. + */ + bool seeking; + + /** + * The tag from the song object. This is only used for local + * files, because we expect the stream server to send us a new + * tag each time we play it. + */ + Tag *song_tag; + + /** the last tag received from the stream */ + Tag *stream_tag; + + /** the last tag received from the decoder plugin */ + Tag *decoder_tag; + + /** the chunk currently being written to */ + MusicChunk *chunk; + + ReplayGainInfo replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; + + /** + * An error has occurred (in DecoderAPI.cxx), and the plugin + * will be asked to stop. + */ + Error error; + + Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag) + :dc(_dc), + convert(nullptr), + timestamp(0), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false), + seeking(false), + song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr), + chunk(nullptr), + replay_gain_serial(0) { + } + + ~Decoder(); + + /** + * Returns the current chunk the decoder writes to, or allocates a new + * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command + */ + MusicChunk *GetChunk(); + + /** + * Flushes the current chunk. + * + * Caller must not lock the #DecoderControl object. + */ + void FlushChunk(); +}; + +#endif diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx new file mode 100644 index 000000000..5a13e6694 --- /dev/null +++ b/src/decoder/DecoderList.cxx @@ -0,0 +1,165 @@ +/* + * 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 "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "plugins/AudiofileDecoderPlugin.hxx" +#include "plugins/PcmDecoderPlugin.hxx" +#include "plugins/DsdiffDecoderPlugin.hxx" +#include "plugins/DsfDecoderPlugin.hxx" +#include "plugins/FlacDecoderPlugin.h" +#include "plugins/OpusDecoderPlugin.h" +#include "plugins/VorbisDecoderPlugin.h" +#include "plugins/AdPlugDecoderPlugin.h" +#include "plugins/WavpackDecoderPlugin.hxx" +#include "plugins/FfmpegDecoderPlugin.hxx" +#include "plugins/GmeDecoderPlugin.hxx" +#include "plugins/FaadDecoderPlugin.hxx" +#include "plugins/MadDecoderPlugin.hxx" +#include "plugins/SndfileDecoderPlugin.hxx" +#include "plugins/Mpg123DecoderPlugin.hxx" +#include "plugins/Mp4v2DecoderPlugin.hxx" +#include "plugins/WildmidiDecoderPlugin.hxx" +#include "plugins/MikmodDecoderPlugin.hxx" +#include "plugins/ModplugDecoderPlugin.hxx" +#include "plugins/MpcdecDecoderPlugin.hxx" +#include "plugins/FluidsynthDecoderPlugin.hxx" +#include "plugins/SidplayDecoderPlugin.hxx" +#include "util/Macros.hxx" + +#include <string.h> + +const struct DecoderPlugin *const decoder_plugins[] = { +#ifdef HAVE_MAD + &mad_decoder_plugin, +#endif +#ifdef HAVE_MPG123 + &mpg123_decoder_plugin, +#endif +#ifdef HAVE_MP4V2 + &mp4v2_decoder_plugin, +#endif +#ifdef ENABLE_VORBIS_DECODER + &vorbis_decoder_plugin, +#endif +#if defined(HAVE_FLAC) + &oggflac_decoder_plugin, +#endif +#ifdef HAVE_FLAC + &flac_decoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_decoder_plugin, +#endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif +#ifdef HAVE_AUDIOFILE + &audiofile_decoder_plugin, +#endif + &dsdiff_decoder_plugin, + &dsf_decoder_plugin, +#ifdef HAVE_FAAD + &faad_decoder_plugin, +#endif +#ifdef HAVE_MPCDEC + &mpcdec_decoder_plugin, +#endif +#ifdef HAVE_WAVPACK + &wavpack_decoder_plugin, +#endif +#ifdef HAVE_MODPLUG + &modplug_decoder_plugin, +#endif +#ifdef ENABLE_MIKMOD_DECODER + &mikmod_decoder_plugin, +#endif +#ifdef ENABLE_SIDPLAY + &sidplay_decoder_plugin, +#endif +#ifdef ENABLE_WILDMIDI + &wildmidi_decoder_plugin, +#endif +#ifdef ENABLE_FLUIDSYNTH + &fluidsynth_decoder_plugin, +#endif +#ifdef HAVE_ADPLUG + &adplug_decoder_plugin, +#endif +#ifdef HAVE_FFMPEG + &ffmpeg_decoder_plugin, +#endif +#ifdef HAVE_GME + &gme_decoder_plugin, +#endif + &pcm_decoder_plugin, + nullptr +}; + +static constexpr unsigned num_decoder_plugins = + ARRAY_SIZE(decoder_plugins) - 1; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name) +{ + return decoder_plugins_find([=](const DecoderPlugin &plugin){ + return strcmp(plugin.name, name) == 0; + }); +} + +void decoder_plugin_init_all(void) +{ + struct config_param empty; + + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) { + const DecoderPlugin &plugin = *decoder_plugins[i]; + const struct config_param *param = + config_find_block(CONF_DECODER, "plugin", plugin.name); + + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin.Init(*param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){ + plugin.Finish(); + }); +} + +bool +decoder_plugins_supports_suffix(const char *suffix) +{ + return decoder_plugins_try([suffix](const DecoderPlugin &plugin){ + return plugin.SupportsSuffix(suffix); + }); +} diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx new file mode 100644 index 000000000..47085d4ae --- /dev/null +++ b/src/decoder/DecoderList.hxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_LIST_HXX +#define MPD_DECODER_LIST_HXX + +#include "Compiler.h" + +struct DecoderPlugin; + +extern const struct DecoderPlugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + +/* interface for using plugins */ + +gcc_pure +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name); + +/* this is where we "load" all the "plugins" ;-) */ +void decoder_plugin_init_all(void); + +/* this is where we "unload" all the "plugins" */ +void decoder_plugin_deinit_all(void); + +template<typename F> +static inline const DecoderPlugin * +decoder_plugins_find(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return decoder_plugins[i]; + + return nullptr; +} + +template<typename F> +static inline bool +decoder_plugins_try(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return true; + + return false; +} + +template<typename F> +static inline void +decoder_plugins_for_each(F f) +{ + for (auto i = decoder_plugins; *i != nullptr; ++i) + f(**i); +} + +template<typename F> +static inline void +decoder_plugins_for_each_enabled(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i]) + f(*decoder_plugins[i]); +} + +/** + * Is there at least once #DecoderPlugin that supports the specified + * file name suffix? + */ +gcc_pure gcc_nonnull_all +bool +decoder_plugins_supports_suffix(const char *suffix); + +#endif diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx new file mode 100644 index 000000000..3be812c3b --- /dev/null +++ b/src/decoder/DecoderPlugin.cxx @@ -0,0 +1,42 @@ +/* + * 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 "DecoderPlugin.hxx" +#include "util/StringUtil.hxx" + +#include <assert.h> + +bool +DecoderPlugin::SupportsSuffix(const char *suffix) const +{ + assert(suffix != nullptr); + + return suffixes != nullptr && string_array_contains(suffixes, suffix); + +} + +bool +DecoderPlugin::SupportsMimeType(const char *mime_type) const +{ + assert(mime_type != nullptr); + + return mime_types != nullptr && + string_array_contains(mime_types, mime_type); +} diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx new file mode 100644 index 000000000..dbf3db9aa --- /dev/null +++ b/src/decoder/DecoderPlugin.hxx @@ -0,0 +1,186 @@ +/* + * 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_DECODER_PLUGIN_HXX +#define MPD_DECODER_PLUGIN_HXX + +#include "Compiler.h" + +struct config_param; +class InputStream; +struct tag_handler; +class Path; + +/** + * Opaque handle which the decoder plugin passes to the functions in + * this header. + */ +struct Decoder; + +struct DecoderPlugin { + const char *name; + + /** + * Initialize the decoder plugin. Optional method. + * + * @param param a configuration block for this plugin, or nullptr + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const config_param ¶m); + + /** + * Deinitialize a decoder plugin which was initialized + * successfully. Optional method. + */ + void (*finish)(void); + + /** + * Decode a stream (data read from an #input_stream object). + * + * Either implement this method or file_decode(). If + * possible, it is recommended to implement this method, + * because it is more versatile. + */ + void (*stream_decode)(Decoder &decoder, InputStream &is); + + /** + * Decode a local file. + * + * Either implement this method or stream_decode(). + */ + void (*file_decode)(Decoder &decoder, Path path_fs); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_file)(Path path_fs, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * Scan metadata of a file. + * + * @return false if the operation has failed + */ + bool (*scan_stream)(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx); + + /** + * @brief Return a "virtual" filename for subtracks in + * container formats like flac + * @param const char* pathname full pathname for the file on fs + * @param const unsigned int tnum track number + * + * @return nullptr if there are no multiple files + * a filename for every single track according to tnum (param 2) + * do not include full pathname here, just the "virtual" file + * + * Free the return value with delete[]. + */ + char* (*container_scan)(Path path_fs, const unsigned int tnum); + + /* last element in these arrays must always be a nullptr: */ + const char *const*suffixes; + const char *const*mime_types; + + /** + * Initialize a decoder plugin. + * + * @param param a configuration block for this plugin, or nullptr if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ + bool Init(const config_param ¶m) const { + return init != nullptr + ? init(param) + : true; + } + + /** + * Deinitialize a decoder plugin which was initialized successfully. + */ + void Finish() const { + if (finish != nullptr) + finish(); + } + + /** + * Decode a stream. + */ + void StreamDecode(Decoder &decoder, InputStream &is) const { + stream_decode(decoder, is); + } + + /** + * Decode a file. + */ + template<typename P> + void FileDecode(Decoder &decoder, P path_fs) const { + file_decode(decoder, path_fs); + } + + /** + * Read the tag of a file. + */ + template<typename P> + bool ScanFile(P path_fs, + const tag_handler &handler, void *handler_ctx) const { + return scan_file != nullptr + ? scan_file(path_fs, &handler, handler_ctx) + : false; + } + + /** + * Read the tag of a stream. + */ + bool ScanStream(InputStream &is, + const tag_handler &handler, void *handler_ctx) const { + return scan_stream != nullptr + ? scan_stream(is, &handler, handler_ctx) + : false; + } + + /** + * return "virtual" tracks in a container + */ + template<typename P> + char *ContainerScan(P path, const unsigned int tnum) const { + return container_scan(path, tnum); + } + + /** + * Does the plugin announce the specified file name suffix? + */ + gcc_pure gcc_nonnull_all + bool SupportsSuffix(const char *suffix) const; + + /** + * Does the plugin announce the specified MIME type? + */ + gcc_pure gcc_nonnull_all + bool SupportsMimeType(const char *mime_type) const; +}; + +#endif diff --git a/src/decoder/DecoderPrint.cxx b/src/decoder/DecoderPrint.cxx new file mode 100644 index 000000000..54b89c36c --- /dev/null +++ b/src/decoder/DecoderPrint.cxx @@ -0,0 +1,55 @@ +/* + * 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 "DecoderPrint.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "client/Client.hxx" + +#include <functional> + +#include <assert.h> + +static void +decoder_plugin_print(Client &client, + const DecoderPlugin &plugin) +{ + const char *const*p; + + assert(plugin.name != nullptr); + + client_printf(client, "plugin: %s\n", plugin.name); + + if (plugin.suffixes != nullptr) + for (p = plugin.suffixes; *p != nullptr; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin.mime_types != nullptr) + for (p = plugin.mime_types; *p != nullptr; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(Client &client) +{ + using namespace std::placeholders; + const auto f = std::bind(decoder_plugin_print, std::ref(client), _1); + decoder_plugins_for_each_enabled(f); +} diff --git a/src/decoder/DecoderPrint.hxx b/src/decoder/DecoderPrint.hxx new file mode 100644 index 000000000..695bd099d --- /dev/null +++ b/src/decoder/DecoderPrint.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_DECODER_PRINT_HXX +#define MPD_DECODER_PRINT_HXX + +class Client; + +void +decoder_list_print(Client &client); + +#endif diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx new file mode 100644 index 000000000..06735de83 --- /dev/null +++ b/src/decoder/DecoderThread.cxx @@ -0,0 +1,481 @@ +/* + * 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 "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DecoderError.hxx" +#include "DecoderPlugin.hxx" +#include "DetachedSong.hxx" +#include "system/FatalError.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "DecoderList.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Name.hxx" +#include "tag/ApeReplayGain.hxx" +#include "Log.hxx" + +#include <functional> + +static constexpr Domain decoder_thread_domain("decoder_thread"); + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #DecoderControl object; must be locked + */ +static void +decoder_command_finished_locked(DecoderControl &dc) +{ + assert(dc.command != DecoderCommand::NONE); + + dc.command = DecoderCommand::NONE; + + dc.client_cond.signal(); +} + +/** + * Opens the input stream with input_stream::Open(), and waits until + * the stream gets ready. If a decoder STOP command is received + * during that, it cancels the operation (but does not close the + * stream). + * + * Unlock the decoder before calling this function. + * + * @return an input_stream on success or if #DecoderCommand::STOP is + * received, nullptr on error + */ +static InputStream * +decoder_input_stream_open(DecoderControl &dc, const char *uri) +{ + Error error; + + InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + dc.Lock(); + + is->Update(); + while (!is->IsReady() && + dc.command != DecoderCommand::STOP) { + dc.Wait(); + + is->Update(); + } + + if (!is->Check(error)) { + dc.Unlock(); + + LogError(error); + return nullptr; + } + + dc.Unlock(); + + return is; +} + +static bool +decoder_stream_decode(const DecoderPlugin &plugin, + Decoder &decoder, + InputStream &input_stream) +{ + assert(plugin.stream_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(input_stream.IsReady()); + assert(decoder.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (decoder.dc.command == DecoderCommand::STOP) + return true; + + /* rewind the stream, so each plugin gets a fresh start */ + input_stream.Rewind(IgnoreError()); + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.StreamDecode(decoder, input_stream); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +static bool +decoder_file_decode(const DecoderPlugin &plugin, + Decoder &decoder, Path path) +{ + assert(plugin.file_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(!path.IsNull()); + assert(path.IsAbsolute()); + assert(decoder.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (decoder.dc.command == DecoderCommand::STOP) + return true; + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.FileDecode(decoder, path); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +gcc_pure +static bool +decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is) +{ + assert(plugin.stream_decode != nullptr); + + const char *mime_type = is.GetMimeType(); + return mime_type != nullptr && plugin.SupportsMimeType(mime_type); +} + +gcc_pure +static bool +decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix) +{ + assert(plugin.stream_decode != nullptr); + + return suffix != nullptr && plugin.SupportsSuffix(suffix); +} + +gcc_pure +static bool +decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, + const char *suffix) +{ + return plugin.stream_decode != nullptr && + (decoder_check_plugin_mime(plugin, is) || + decoder_check_plugin_suffix(plugin, suffix)); +} + +static bool +decoder_run_stream_plugin(Decoder &decoder, InputStream &is, + const char *suffix, + const DecoderPlugin &plugin, + bool &tried_r) +{ + if (!decoder_check_plugin(plugin, is, suffix)) + return false; + + tried_r = true; + return decoder_stream_decode(plugin, decoder, is); +} + +static bool +decoder_run_stream_locked(Decoder &decoder, InputStream &is, + const char *uri, bool &tried_r) +{ + const char *const suffix = uri_get_suffix(uri); + + using namespace std::placeholders; + const auto f = std::bind(decoder_run_stream_plugin, + std::ref(decoder), std::ref(is), suffix, + _1, std::ref(tried_r)); + return decoder_plugins_try(f); +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(Decoder &decoder, InputStream &is) +{ + const struct DecoderPlugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != nullptr && plugin->stream_decode != nullptr && + decoder_stream_decode(*plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(Decoder &decoder, const char *uri) +{ + DecoderControl &dc = decoder.dc; + InputStream *input_stream; + bool success; + + dc.Unlock(); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == nullptr) { + dc.Lock(); + return false; + } + + dc.Lock(); + + bool tried = false; + success = dc.command == DecoderCommand::STOP || + decoder_run_stream_locked(decoder, *input_stream, uri, + tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (!tried && + decoder_run_stream_fallback(decoder, *input_stream)); + + dc.Unlock(); + delete input_stream; + dc.Lock(); + + return success; +} + +/** + * Attempt to load replay gain data, and pass it to + * decoder_replay_gain(). + */ +static void +decoder_load_replay_gain(Decoder &decoder, Path path_fs) +{ + ReplayGainInfo info; + if (replay_gain_ape_read(path_fs, info)) + decoder_replay_gain(decoder, &info); +} + +static bool +TryDecoderFile(Decoder &decoder, Path path_fs, const char *suffix, + const DecoderPlugin &plugin) +{ + if (!plugin.SupportsSuffix(suffix)) + return false; + + DecoderControl &dc = decoder.dc; + + if (plugin.file_decode != nullptr) { + dc.Lock(); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + dc.Unlock(); + } else if (plugin.stream_decode != nullptr) { + InputStream *input_stream = + decoder_input_stream_open(dc, path_fs.c_str()); + if (input_stream == nullptr) + return false; + + dc.Lock(); + + bool success = decoder_stream_decode(plugin, decoder, + *input_stream); + + dc.Unlock(); + + delete input_stream; + + if (success) { + dc.Lock(); + return true; + } + } + + return false; +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(Decoder &decoder, const char *uri_utf8, Path path_fs) +{ + const char *suffix = uri_get_suffix(uri_utf8); + if (suffix == nullptr) + return false; + + DecoderControl &dc = decoder.dc; + dc.Unlock(); + + decoder_load_replay_gain(decoder, path_fs); + + if (decoder_plugins_try([&decoder, path_fs, + suffix](const DecoderPlugin &plugin){ + return TryDecoderFile(decoder, + path_fs, suffix, + plugin); + })) + return true; + + dc.Lock(); + return false; +} + +static void +decoder_run_song(DecoderControl &dc, + const DetachedSong &song, const char *uri, Path path_fs) +{ + Decoder decoder(dc, dc.start_ms > 0, + new Tag(song.GetTag())); + int ret; + + dc.state = DecoderState::START; + + decoder_command_finished_locked(dc); + + ret = !path_fs.IsNull() + ? decoder_run_file(decoder, uri, path_fs) + : decoder_run_stream(decoder, uri); + + dc.Unlock(); + + /* flush the last chunk */ + + if (decoder.chunk != nullptr) + decoder.FlushChunk(); + + dc.Lock(); + + if (decoder.error.IsDefined()) { + /* copy the Error from sruct Decoder to + DecoderControl */ + dc.state = DecoderState::ERROR; + dc.error = std::move(decoder.error); + } else if (ret) + dc.state = DecoderState::STOP; + else { + dc.state = DecoderState::ERROR; + + const char *error_uri = song.GetURI(); + const std::string allocated = uri_remove_auth(error_uri); + if (!allocated.empty()) + error_uri = allocated.c_str(); + + dc.error.Format(decoder_domain, + "Failed to decode %s", error_uri); + } + + dc.client_cond.signal(); +} + +static void +decoder_run(DecoderControl &dc) +{ + dc.ClearError(); + + assert(dc.song != nullptr); + const DetachedSong &song = *dc.song; + + const char *const uri_utf8 = song.GetRealURI(); + + Path path_fs = Path::Null(); + AllocatedPath path_buffer = AllocatedPath::Null(); + if (PathTraitsUTF8::IsAbsolute(uri_utf8)) { + path_buffer = AllocatedPath::FromUTF8(uri_utf8, dc.error); + if (path_buffer.IsNull()) { + dc.state = DecoderState::ERROR; + decoder_command_finished_locked(dc); + return; + } + + path_fs = path_buffer; + } + + decoder_run_song(dc, song, uri_utf8, path_fs); + +} + +static void +decoder_task(void *arg) +{ + DecoderControl &dc = *(DecoderControl *)arg; + + SetThreadName("decoder"); + + dc.Lock(); + + do { + assert(dc.state == DecoderState::STOP || + dc.state == DecoderState::ERROR); + + switch (dc.command) { + case DecoderCommand::START: + dc.CycleMixRamp(); + dc.replay_gain_prev_db = dc.replay_gain_db; + dc.replay_gain_db = 0; + + /* fall through */ + + case DecoderCommand::SEEK: + decoder_run(dc); + break; + + case DecoderCommand::STOP: + decoder_command_finished_locked(dc); + break; + + case DecoderCommand::NONE: + dc.Wait(); + break; + } + } while (dc.command != DecoderCommand::NONE || !dc.quit); + + dc.Unlock(); +} + +void +decoder_thread_start(DecoderControl &dc) +{ + assert(!dc.thread.IsDefined()); + + dc.quit = false; + + Error error; + if (!dc.thread.Start(decoder_task, &dc, error)) + FatalError(error); +} diff --git a/src/decoder/DecoderThread.hxx b/src/decoder/DecoderThread.hxx new file mode 100644 index 000000000..d5fde281c --- /dev/null +++ b/src/decoder/DecoderThread.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_DECODER_THREAD_HXX +#define MPD_DECODER_THREAD_HXX + +struct DecoderControl; + +void +decoder_thread_start(DecoderControl &dc); + +#endif diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx deleted file mode 100644 index eafedda8f..000000000 --- a/src/decoder/DsdLib.cxx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * This file contains functions used by the DSF and DSDIFF decoders. - * - */ - -#include "config.h" -#include "DsdLib.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/bit_reverse.h" -#include "tag/TagHandler.hxx" -#include "tag/TagId3.hxx" -#include "util/Error.hxx" - -#include <unistd.h> -#include <string.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -#ifdef HAVE_ID3TAG -#include <id3tag.h> -#endif - -bool -DsdId::Equals(const char *s) const -{ - assert(s != nullptr); - assert(strlen(s) == sizeof(value)); - - return memcmp(value, s, sizeof(value)) == 0; -} - -/** - * Skip the #input_stream to the specified offset. - */ -bool -dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset) -{ - if (is.IsSeekable()) - return is.Seek(offset, SEEK_SET, IgnoreError()); - - if (is.GetOffset() > offset) - return false; - - char buffer[8192]; - while (is.GetOffset() < offset) { - size_t length = sizeof(buffer); - if (offset - is.GetOffset() < (int64_t)length) - length = offset - is.GetOffset(); - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - } - - assert(is.GetOffset() == offset); - return true; -} - -/** - * Skip some bytes from the #input_stream. - */ -bool -dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta) -{ - assert(delta >= 0); - - if (delta == 0) - return true; - - if (is.IsSeekable()) - return is.Seek(delta, SEEK_CUR, IgnoreError()); - - char buffer[8192]; - while (delta > 0) { - size_t length = sizeof(buffer); - if ((int64_t)length > delta) - length = delta; - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - - delta -= nbytes; - } - - return true; -} - -#ifdef HAVE_ID3TAG -void -dsdlib_tag_id3(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, int64_t tagoffset) -{ - assert(tagoffset >= 0); - - if (tagoffset == 0) - return; - - if (!dsdlib_skip_to(nullptr, is, tagoffset)) - return; - - struct id3_tag *id3_tag = nullptr; - id3_length_t count; - - /* Prevent broken files causing problems */ - const auto size = is.GetSize(); - const auto offset = is.GetOffset(); - if (offset >= size) - return; - - count = size - offset; - - /* Check and limit id3 tag size to prevent a stack overflow */ - if (count == 0 || count > 4096) - return; - - id3_byte_t dsdid3[count]; - id3_byte_t *dsdid3data; - dsdid3data = dsdid3; - - if (!decoder_read_full(nullptr, is, dsdid3data, count)) - return; - - id3_tag = id3_tag_parse(dsdid3data, count); - if (id3_tag == nullptr) - return; - - scan_id3_tag(id3_tag, handler, handler_ctx); - - id3_tag_delete(id3_tag); - - return; -} -#endif diff --git a/src/decoder/DsdLib.hxx b/src/decoder/DsdLib.hxx deleted file mode 100644 index 5c6127149..000000000 --- a/src/decoder/DsdLib.hxx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_DSDLIB_HXX -#define MPD_DECODER_DSDLIB_HXX - -#include "system/ByteOrder.hxx" -#include "Compiler.h" - -#include <stddef.h> -#include <stdint.h> - -struct Decoder; -struct InputStream; - -struct DsdId { - char value[4]; - - gcc_pure - bool Equals(const char *s) const; -}; - -class DsdUint64 { - uint32_t lo; - uint32_t hi; - -public: - constexpr uint64_t Read() const { - return (uint64_t(FromLE32(hi)) << 32) | - uint64_t(FromLE32(lo)); - } -}; - -class DffDsdUint64 { - uint32_t hi; - uint32_t lo; - -public: - constexpr uint64_t Read() const { - return (uint64_t(FromBE32(hi)) << 32) | - uint64_t(FromBE32(lo)); - } -}; - -bool -dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset); - -bool -dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta); - -/** - * Add tags from ID3 tag. All tags commonly found in the ID3 tags of - * DSF and DSDIFF files are imported - */ -void -dsdlib_tag_id3(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, int64_t tagoffset); - -#endif diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx deleted file mode 100644 index 60b2e7624..000000000 --- a/src/decoder/DsdiffDecoderPlugin.cxx +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * This plugin decodes DSDIFF data (SACD) embedded in DFF files. - * The DFF code was modeled after the specification found here: - * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf - * - * All functions common to both DSD decoders have been moved to dsdlib - */ - -#include "config.h" -#include "DsdiffDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "util/bit_reverse.h" -#include "util/Error.hxx" -#include "system/ByteOrder.hxx" -#include "tag/TagHandler.hxx" -#include "DsdLib.hxx" -#include "Log.hxx" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -struct DsdiffHeader { - DsdId id; - DffDsdUint64 size; - DsdId format; -}; - -struct DsdiffChunkHeader { - DsdId id; - DffDsdUint64 size; - - /** - * Read the "size" attribute from the specified header, converting it - * to the host byte order if needed. - */ - constexpr - uint64_t GetSize() const { - return size.Read(); - } -}; - -/** struct for DSDIFF native Artist and Title tags */ -struct dsdiff_native_tag { - uint32_t size; -}; - -struct DsdiffMetaData { - unsigned sample_rate, channels; - bool bitreverse; - uint64_t chunk_size; -#ifdef HAVE_ID3TAG - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif - /** offset for artist tag */ - InputStream::offset_type diar_offset; - /** offset for title tag */ - InputStream::offset_type diti_offset; -}; - -static bool lsbitfirst; - -static bool -dsdiff_init(const config_param ¶m) -{ - lsbitfirst = param.GetBlockValue("lsbitfirst", false); - return true; -} - -static bool -dsdiff_read_id(Decoder *decoder, InputStream &is, - DsdId *id) -{ - return decoder_read_full(decoder, is, id, sizeof(*id)); -} - -static bool -dsdiff_read_chunk_header(Decoder *decoder, InputStream &is, - DsdiffChunkHeader *header) -{ - return decoder_read_full(decoder, is, header, sizeof(*header)); -} - -static bool -dsdiff_read_payload(Decoder *decoder, InputStream &is, - const DsdiffChunkHeader *header, - void *data, size_t length) -{ - uint64_t size = header->GetSize(); - if (size != (uint64_t)length) - return false; - - return decoder_read_full(decoder, is, data, length); -} - -/** - * Read and parse a "SND" chunk inside "PROP". - */ -static bool -dsdiff_read_prop_snd(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - InputStream::offset_type end_offset) -{ - DsdiffChunkHeader header; - while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) { - if (!dsdiff_read_chunk_header(decoder, is, &header)) - return false; - - InputStream::offset_type chunk_end_offset = is.GetOffset() - + header.GetSize(); - if (chunk_end_offset > end_offset) - return false; - - if (header.id.Equals("FS ")) { - uint32_t sample_rate; - if (!dsdiff_read_payload(decoder, is, &header, - &sample_rate, - sizeof(sample_rate))) - return false; - - metadata->sample_rate = FromBE32(sample_rate); - } else if (header.id.Equals("CHNL")) { - uint16_t channels; - if (header.GetSize() < sizeof(channels) || - !decoder_read_full(decoder, is, - &channels, sizeof(channels)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - metadata->channels = FromBE16(channels); - } else if (header.id.Equals("CMPR")) { - DsdId type; - if (header.GetSize() < sizeof(type) || - !decoder_read_full(decoder, is, - &type, sizeof(type)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - if (!type.Equals("DSD ")) - /* only uncompressed DSD audio data - is implemented */ - return false; - } else { - /* ignore unknown chunk */ - - if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - } - } - - return is.GetOffset() == end_offset; -} - -/** - * Read and parse a "PROP" chunk. - */ -static bool -dsdiff_read_prop(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - const DsdiffChunkHeader *prop_header) -{ - uint64_t prop_size = prop_header->GetSize(); - InputStream::offset_type end_offset = is.GetOffset() + prop_size; - - DsdId prop_id; - if (prop_size < sizeof(prop_id) || - !dsdiff_read_id(decoder, is, &prop_id)) - return false; - - if (prop_id.Equals("SND ")) - return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); - else - /* ignore unknown PROP chunk */ - return dsdlib_skip_to(decoder, is, end_offset); -} - -static void -dsdiff_handle_native_tag(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, InputStream::offset_type tagoffset, - TagType type) -{ - if (!dsdlib_skip_to(nullptr, is, tagoffset)) - return; - - struct dsdiff_native_tag metatag; - - if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag))) - return; - - uint32_t length = FromBE32(metatag.size); - - /* Check and limit size of the tag to prevent a stack overflow */ - if (length == 0 || length > 60) - return; - - char string[length]; - char *label; - label = string; - - if (!decoder_read_full(nullptr, is, label, (size_t)length)) - return; - - string[length] = '\0'; - tag_handler_invoke_tag(handler, handler_ctx, type, label); - return; -} - -/** - * Read and parse additional metadata chunks for tagging purposes. By default - * dsdiff files only support equivalents for artist and title but some of the - * extract tools add an id3 tag to provide more tags. If such id3 is found - * this will be used for tagging otherwise the native tags (if any) will be - * used - */ - -static bool -dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - DsdiffChunkHeader *chunk_header, - const struct tag_handler *handler, - void *handler_ctx) -{ - - /* skip from DSD data to next chunk header */ - if (!dsdlib_skip(decoder, is, metadata->chunk_size)) - return false; - if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) - return false; - - metadata->diar_offset = 0; - metadata->diti_offset = 0; - -#ifdef HAVE_ID3TAG - metadata->id3_offset = 0; -#endif - - /* Now process all the remaining chunk headers in the stream - and record their position and size */ - - do { - uint64_t chunk_size = chunk_header->GetSize(); - - /* DIIN chunk, is directly followed by other chunks */ - if (chunk_header->id.Equals("DIIN")) - chunk_size = 0; - - /* DIAR chunk - DSDIFF native tag for Artist */ - if (chunk_header->id.Equals("DIAR")) { - chunk_size = chunk_header->GetSize(); - metadata->diar_offset = is.GetOffset(); - } - - /* DITI chunk - DSDIFF native tag for Title */ - if (chunk_header->id.Equals("DITI")) { - chunk_size = chunk_header->GetSize(); - metadata->diti_offset = is.GetOffset(); - } -#ifdef HAVE_ID3TAG - /* 'ID3 ' chunk, offspec. Used by sacdextract */ - if (chunk_header->id.Equals("ID3 ")) { - chunk_size = chunk_header->GetSize(); - metadata->id3_offset = is.GetOffset(); - metadata->id3_size = chunk_size; - } -#endif - - if (!dsdlib_skip(decoder, is, chunk_size)) - break; - } while (dsdiff_read_chunk_header(decoder, is, chunk_header)); - - /* done processing chunk headers, process tags if any */ - -#ifdef HAVE_ID3TAG - if (metadata->id3_offset != 0) - { - /* a ID3 tag has preference over the other tags, do not process - other tags if we have one */ - dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset); - return true; - } -#endif - - if (metadata->diar_offset != 0) - dsdiff_handle_native_tag(is, handler, handler_ctx, - metadata->diar_offset, TAG_ARTIST); - - if (metadata->diti_offset != 0) - dsdiff_handle_native_tag(is, handler, handler_ctx, - metadata->diti_offset, TAG_TITLE); - return true; -} - -/** - * Read and parse all metadata chunks at the beginning. Stop when the - * first "DSD" chunk is seen, and return its header in the - * "chunk_header" parameter. - */ -static bool -dsdiff_read_metadata(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - DsdiffChunkHeader *chunk_header) -{ - DsdiffHeader header; - if (!decoder_read_full(decoder, is, &header, sizeof(header)) || - !header.id.Equals("FRM8") || - !header.format.Equals("DSD ")) - return false; - - while (true) { - if (!dsdiff_read_chunk_header(decoder, is, - chunk_header)) - return false; - - if (chunk_header->id.Equals("PROP")) { - if (!dsdiff_read_prop(decoder, is, metadata, - chunk_header)) - return false; - } else if (chunk_header->id.Equals("DSD ")) { - const uint64_t chunk_size = chunk_header->GetSize(); - metadata->chunk_size = chunk_size; - return true; - } else { - /* ignore unknown chunk */ - const uint64_t chunk_size = chunk_header->GetSize(); - InputStream::offset_type chunk_end_offset = - is.GetOffset() + chunk_size; - - if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - } - } -} - -static void -bit_reverse_buffer(uint8_t *p, uint8_t *end) -{ - for (; p < end; ++p) - *p = bit_reverse(*p); -} - -/** - * Decode one "DSD" chunk. - */ -static bool -dsdiff_decode_chunk(Decoder &decoder, InputStream &is, - unsigned channels, - uint64_t chunk_size) -{ - uint8_t buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size > 0) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned now_frames = - (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; - } - - if (!decoder_read_full(&decoder, is, buffer, now_size)) - return false; - - const size_t nbytes = now_size; - chunk_size -= nbytes; - - if (lsbitfirst) - bit_reverse_buffer(buffer, buffer + nbytes); - - const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DecoderCommand::NONE: - break; - - case DecoderCommand::START: - case DecoderCommand::STOP: - return false; - - case DecoderCommand::SEEK: - - /* Not implemented yet */ - decoder_seek_error(decoder); - break; - } - } - return dsdlib_skip(&decoder, is, chunk_size); -} - -static void -dsdiff_stream_decode(Decoder &decoder, InputStream &is) -{ - DsdiffMetaData metadata; - - DsdiffChunkHeader chunk_header; - /* check if it is is a proper DFF file */ - if (!dsdiff_read_metadata(&decoder, is, &metadata, &chunk_header)) - return; - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, error)) { - LogError(error); - return; - } - - /* calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; - - /* success: file was recognized */ - decoder_initialized(decoder, audio_format, false, songtime); - - /* every iteration of the following loop decodes one "DSD" - chunk from a DFF file */ - - while (true) { - chunk_size = chunk_header.GetSize(); - - if (chunk_header.id.Equals("DSD ")) { - if (!dsdiff_decode_chunk(decoder, is, - metadata.channels, - chunk_size)) - break; - } else { - /* ignore other chunks */ - if (!dsdlib_skip(&decoder, is, chunk_size)) - break; - } - - /* read next chunk header; the first one was read by - dsdiff_read_metadata() */ - if (!dsdiff_read_chunk_header(&decoder, - is, &chunk_header)) - break; - } -} - -static bool -dsdiff_scan_stream(InputStream &is, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - DsdiffMetaData metadata; - DsdiffChunkHeader chunk_header; - - /* First check for DFF metadata */ - if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header)) - return false; - - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, IgnoreError())) - /* refuse to parse files which we cannot play anyway */ - return false; - - /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; - tag_handler_invoke_duration(handler, handler_ctx, songtime); - - /* Read additional metadata and created tags if available */ - dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, - handler, handler_ctx); - - return true; -} - -static const char *const dsdiff_suffixes[] = { - "dff", - nullptr -}; - -static const char *const dsdiff_mime_types[] = { - "application/x-dff", - nullptr -}; - -const struct DecoderPlugin dsdiff_decoder_plugin = { - "dsdiff", - dsdiff_init, - nullptr, - dsdiff_stream_decode, - nullptr, - nullptr, - dsdiff_scan_stream, - nullptr, - dsdiff_suffixes, - dsdiff_mime_types, -}; diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/DsdiffDecoderPlugin.hxx deleted file mode 100644 index be14fc9cd..000000000 --- a/src/decoder/DsdiffDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_DSDIFF_H -#define MPD_DECODER_DSDIFF_H - -extern const struct DecoderPlugin dsdiff_decoder_plugin; - -#endif diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx deleted file mode 100644 index ad5483c32..000000000 --- a/src/decoder/DsfDecoderPlugin.cxx +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* \file - * - * This plugin decodes DSDIFF data (SACD) embedded in DSF files. - * - * The DSF code was created using the specification found here: - * http://dsd-guide.com/sonys-dsf-file-format-spec - * - * All functions common to both DSD decoders have been moved to dsdlib - */ - -#include "config.h" -#include "DsfDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "util/bit_reverse.h" -#include "util/Error.hxx" -#include "system/ByteOrder.hxx" -#include "DsdLib.hxx" -#include "tag/TagHandler.hxx" -#include "Log.hxx" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -struct DsfMetaData { - unsigned sample_rate, channels; - bool bitreverse; - uint64_t chunk_size; -#ifdef HAVE_ID3TAG - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif -}; - -struct DsfHeader { - /** DSF header id: "DSD " */ - DsdId id; - /** DSD chunk size, including id = 28 */ - DsdUint64 size; - /** total file size */ - DsdUint64 fsize; - /** pointer to id3v2 metadata, should be at the end of the file */ - DsdUint64 pmeta; -}; - -/** DSF file fmt chunk */ -struct DsfFmtChunk { - /** id: "fmt " */ - DsdId id; - /** fmt chunk size, including id, normally 52 */ - DsdUint64 size; - /** version of this format = 1 */ - uint32_t version; - /** 0: DSD raw */ - uint32_t formatid; - /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */ - uint32_t channeltype; - /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */ - uint32_t channelnum; - /** sample frequency: 2822400, 5644800 */ - uint32_t sample_freq; - /** bits per sample 1 or 8 */ - uint32_t bitssample; - /** Sample count per channel in bytes */ - DsdUint64 scnt; - /** block size per channel = 4096 */ - uint32_t block_size; - /** reserved, should be all zero */ - uint32_t reserved; -}; - -struct DsfDataChunk { - DsdId id; - /** "data" chunk size, includes header (id+size) */ - DsdUint64 size; -}; - -/** - * Read and parse all needed metadata chunks for DSF files. - */ -static bool -dsf_read_metadata(Decoder *decoder, InputStream &is, - DsfMetaData *metadata) -{ - DsfHeader dsf_header; - if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) || - !dsf_header.id.Equals("DSD ")) - return false; - - const uint64_t chunk_size = dsf_header.size.Read(); - if (sizeof(dsf_header) != chunk_size) - return false; - -#ifdef HAVE_ID3TAG - const uint64_t metadata_offset = dsf_header.pmeta.Read(); -#endif - - /* read the 'fmt ' chunk of the DSF file */ - DsfFmtChunk dsf_fmt_chunk; - if (!decoder_read_full(decoder, is, - &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || - !dsf_fmt_chunk.id.Equals("fmt ")) - return false; - - const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read(); - if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) - return false; - - uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); - - /* for now, only support version 1 of the standard, DSD raw stereo - files with a sample freq of 2822400 or 5644800 Hz */ - - if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 - || dsf_fmt_chunk.channeltype != 2 - || dsf_fmt_chunk.channelnum != 2 - || (samplefreq != 2822400 && samplefreq != 5644800)) - return false; - - uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size); - /* according to the spec block size should always be 4096 */ - if (chblksize != 4096) - return false; - - /* read the 'data' chunk of the DSF file */ - DsfDataChunk data_chunk; - if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) || - !data_chunk.id.Equals("data")) - return false; - - /* data size of DSF files are padded to multiple of 4096, - we use the actual data size as chunk size */ - - uint64_t data_size = data_chunk.size.Read(); - if (data_size < sizeof(data_chunk)) - return false; - - data_size -= sizeof(data_chunk); - - /* data_size cannot be bigger or equal to total file size */ - const uint64_t size = (uint64_t)is.GetSize(); - if (data_size >= size) - return false; - - /* use the sample count from the DSF header as the upper - bound, because some DSF files contain junk at the end of - the "data" chunk */ - const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); - const uint64_t playable_size = samplecnt * 2 / 8; - if (data_size > playable_size) - data_size = playable_size; - - metadata->chunk_size = data_size; - metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; - metadata->sample_rate = samplefreq; -#ifdef HAVE_ID3TAG - /* metada_offset cannot be bigger then or equal to total file size */ - if (metadata_offset >= size) - metadata->id3_offset = 0; - else - metadata->id3_offset = (InputStream::offset_type)metadata_offset; -#endif - /* check bits per sample format, determine if bitreverse is needed */ - metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; - return true; -} - -static void -bit_reverse_buffer(uint8_t *p, uint8_t *end) -{ - for (; p < end; ++p) - *p = bit_reverse(*p); -} - -/** - * DSF data is build up of alternating 4096 blocks of DSD samples for left and - * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 - * block of 4096 DSD right samples to 8k of samples in normal PCM left/right - * order. - */ -static void -dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) -{ - for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { - scratch[i] = *(dest+j); - j++; - } - - for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { - scratch[i] = *(dest+4096+j); - j++; - } - - for (unsigned i = 0; i < (unsigned)nrbytes; i++) { - *dest = scratch[i]; - dest++; - } -} - -/** - * Decode one complete DSF 'data' chunk i.e. a complete song - */ -static bool -dsf_decode_chunk(Decoder &decoder, InputStream &is, - unsigned channels, - uint64_t chunk_size, - bool bitreverse) -{ - uint8_t buffer[8192]; - - /* scratch buffer for DSF samples to convert to the needed - normal left/right regime of samples */ - uint8_t dsf_scratch_buffer[8192]; - - const size_t sample_size = sizeof(buffer[0]); - const size_t frame_size = channels * sample_size; - const unsigned buffer_frames = sizeof(buffer) / frame_size; - const unsigned buffer_samples = buffer_frames * frame_size; - const size_t buffer_size = buffer_samples * sample_size; - - while (chunk_size > 0) { - /* see how much aligned data from the remaining chunk - fits into the local buffer */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned now_frames = - (unsigned)chunk_size / frame_size; - now_size = now_frames * frame_size; - } - - if (!decoder_read_full(&decoder, is, buffer, now_size)) - return false; - - const size_t nbytes = now_size; - chunk_size -= nbytes; - - if (bitreverse) - bit_reverse_buffer(buffer, buffer + nbytes); - - dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes); - - const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); - switch (cmd) { - case DecoderCommand::NONE: - break; - - case DecoderCommand::START: - case DecoderCommand::STOP: - return false; - - case DecoderCommand::SEEK: - - /* not implemented yet */ - decoder_seek_error(decoder); - break; - } - } - return dsdlib_skip(&decoder, is, chunk_size); -} - -static void -dsf_stream_decode(Decoder &decoder, InputStream &is) -{ - /* check if it is a proper DSF file */ - DsfMetaData metadata; - if (!dsf_read_metadata(&decoder, is, &metadata)) - return; - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, error)) { - LogError(error); - return; - } - /* Calculate song time from DSD chunk size and sample frequency */ - uint64_t chunk_size = metadata.chunk_size; - float songtime = ((chunk_size / metadata.channels) * 8) / - (float) metadata.sample_rate; - - /* success: file was recognized */ - decoder_initialized(decoder, audio_format, false, songtime); - - if (!dsf_decode_chunk(decoder, is, metadata.channels, - chunk_size, - metadata.bitreverse)) - return; -} - -static bool -dsf_scan_stream(InputStream &is, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - /* check DSF metadata */ - DsfMetaData metadata; - if (!dsf_read_metadata(nullptr, is, &metadata)) - return false; - - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, - SampleFormat::DSD, - metadata.channels, IgnoreError())) - /* refuse to parse files which we cannot play anyway */ - return false; - - /* calculate song time and add as tag */ - unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / - metadata.sample_rate; - tag_handler_invoke_duration(handler, handler_ctx, songtime); - -#ifdef HAVE_ID3TAG - /* Add available tags from the ID3 tag */ - dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); -#endif - return true; -} - -static const char *const dsf_suffixes[] = { - "dsf", - nullptr -}; - -static const char *const dsf_mime_types[] = { - "application/x-dsf", - nullptr -}; - -const struct DecoderPlugin dsf_decoder_plugin = { - "dsf", - nullptr, - nullptr, - dsf_stream_decode, - nullptr, - nullptr, - dsf_scan_stream, - nullptr, - dsf_suffixes, - dsf_mime_types, -}; diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/DsfDecoderPlugin.hxx deleted file mode 100644 index 921c94698..000000000 --- a/src/decoder/DsfDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_DSF_H -#define MPD_DECODER_DSF_H - -extern const struct DecoderPlugin dsf_decoder_plugin; - -#endif diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx deleted file mode 100644 index b446ac5be..000000000 --- a/src/decoder/FaadDecoderPlugin.cxx +++ /dev/null @@ -1,498 +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 "FaadDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "DecoderBuffer.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <neaacdec.h> - -#include <assert.h> -#include <string.h> -#include <unistd.h> - -static const unsigned adts_sample_rates[] = - { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, 7350, 0, 0, 0 -}; - -static constexpr Domain faad_decoder_domain("faad_decoder"); - -/** - * Check whether the buffer head is an AAC frame, and return the frame - * length. Returns 0 if it is not a frame. - */ -static size_t -adts_check_frame(const unsigned char *data) -{ - /* check syncword */ - if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) - return 0; - - return (((unsigned int)data[3] & 0x3) << 11) | - (((unsigned int)data[4]) << 3) | - (data[5] >> 5); -} - -/** - * Find the next AAC frame in the buffer. Returns 0 if no frame is - * found or if not enough data is available. - */ -static size_t -adts_find_frame(DecoderBuffer *buffer) -{ - while (true) { - size_t length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr || length < 8) { - /* not enough data yet */ - if (!decoder_buffer_fill(buffer)) - /* failed */ - return 0; - - continue; - } - - /* find the 0xff marker */ - const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length); - if (p == nullptr) { - /* no marker - discard the buffer */ - decoder_buffer_clear(buffer); - continue; - } - - if (p > data) { - /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data); - continue; - } - - /* is it a frame? */ - const size_t frame_length = adts_check_frame(data); - if (frame_length == 0) { - /* it's just some random 0xff byte; discard it - and continue searching */ - decoder_buffer_consume(buffer, 1); - continue; - } - - if (length < frame_length) { - /* available buffer size is smaller than the - frame will be - attempt to read more - data */ - if (!decoder_buffer_fill(buffer)) { - /* not enough data; discard this frame - to prevent a possible buffer - overflow */ - decoder_buffer_clear(buffer); - } - - continue; - } - - /* found a full frame! */ - return frame_length; - } -} - -static float -adts_song_duration(DecoderBuffer *buffer) -{ - const InputStream &is = decoder_buffer_get_stream(buffer); - const bool estimate = !is.CheapSeeking(); - const auto file_size = is.GetSize(); - if (estimate && file_size <= 0) - return -1; - - unsigned sample_rate = 0; - - /* Read all frames to ensure correct time and bitrate */ - unsigned frames; - for (frames = 0;; frames++) { - const unsigned frame_length = adts_find_frame(buffer); - if (frame_length == 0) - break; - - - if (frames == 0) { - size_t buffer_length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &buffer_length); - assert(data != nullptr); - assert(frame_length <= buffer_length); - - sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; - if (sample_rate == 0) - break; - } - - decoder_buffer_consume(buffer, frame_length); - - if (estimate && frames == 128) { - /* if this is a remote file, don't slurp the - whole file just for checking the song - duration; instead, stop after some time and - extrapolate the song duration from what we - have until now */ - - const auto offset = is.GetOffset() - - decoder_buffer_available(buffer); - if (offset <= 0) - return -1; - - frames = (frames * file_size) / offset; - break; - } - } - - if (sample_rate == 0) - return -1; - - float frames_per_second = (float)sample_rate / 1024.0; - assert(frames_per_second > 0); - - return (float)frames / frames_per_second; -} - -static float -faad_song_duration(DecoderBuffer *buffer, InputStream &is) -{ - const auto size = is.GetSize(); - const size_t fileread = size >= 0 ? size : 0; - - decoder_buffer_fill(buffer); - size_t length; - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) - return -1; - - size_t tagsize = 0; - if (length >= 10 && !memcmp(data, "ID3", 3)) { - /* skip the ID3 tag */ - - tagsize = (data[6] << 21) | (data[7] << 14) | - (data[8] << 7) | (data[9] << 0); - - tagsize += 10; - - const bool success = decoder_buffer_skip(buffer, tagsize) && - decoder_buffer_fill(buffer); - if (!success) - return -1; - - data = (const uint8_t *)decoder_buffer_read(buffer, &length); - if (data == nullptr) - return -1; - } - - if (length >= 8 && adts_check_frame(data) > 0) { - /* obtain the duration from the ADTS header */ - - if (!is.IsSeekable()) - return -1; - - float song_length = adts_song_duration(buffer); - - is.LockSeek(tagsize, SEEK_SET, IgnoreError()); - decoder_buffer_clear(buffer); - - return song_length; - } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { - /* obtain the duration from the ADIF header */ - size_t skip_size = (data[4] & 0x80) ? 9 : 0; - - if (8 + skip_size > length) - /* not enough data yet; skip parsing this - header */ - return -1; - - unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) | - (data[5 + skip_size] << 11) | - (data[6 + skip_size] << 3) | - (data[7 + skip_size] & 0xE0); - - if (fileread != 0 && bit_rate != 0) - return fileread * 8.0 / bit_rate; - else - return fileread; - } else - return -1; -} - -static NeAACDecHandle -faad_decoder_new() -{ - const NeAACDecHandle decoder = NeAACDecOpen(); - - NeAACDecConfigurationPtr config = - NeAACDecGetCurrentConfiguration(decoder); - config->outputFormat = FAAD_FMT_16BIT; - config->downMatrix = 1; - config->dontUpSampleImplicitSBR = 0; - NeAACDecSetConfiguration(decoder, config); - - return decoder; -} - -/** - * Wrapper for NeAACDecInit() which works around some API - * inconsistencies in libfaad. - */ -static bool -faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, - AudioFormat &audio_format, Error &error) -{ - - size_t length; - const unsigned char *data = (const unsigned char *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) { - error.Set(faad_decoder_domain, "Empty file"); - return false; - } - - uint8_t channels; - uint32_t sample_rate; -#ifdef HAVE_FAAD_LONG - /* neaacdec.h declares all arguments as "unsigned long", but - internally expects uint32_t pointers. To avoid gcc - warnings, use this workaround. */ - unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate; -#else - uint32_t *sample_rate_p = &sample_rate; -#endif - long nbytes = NeAACDecInit(decoder, - /* deconst hack, libfaad requires this */ - const_cast<unsigned char *>(data), - length, - sample_rate_p, &channels); - if (nbytes < 0) { - error.Set(faad_decoder_domain, "Not an AAC stream"); - return false; - } - - decoder_buffer_consume(buffer, nbytes); - - return audio_format_init_checked(audio_format, sample_rate, - SampleFormat::S16, channels, error); -} - -/** - * Wrapper for NeAACDecDecode() which works around some API - * inconsistencies in libfaad. - */ -static const void * -faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, - NeAACDecFrameInfo *frame_info) -{ - size_t length; - const unsigned char *data = (const unsigned char *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) - return nullptr; - - return NeAACDecDecode(decoder, frame_info, - /* deconst hack, libfaad requires this */ - const_cast<unsigned char *>(data), - length); -} - -/** - * Get a song file's total playing time in seconds, as a float. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static float -faad_get_file_time_float(InputStream &is) -{ - DecoderBuffer *const buffer = - decoder_buffer_new(nullptr, is, - FAAD_MIN_STREAMSIZE * MAX_CHANNELS); - - float length = faad_song_duration(buffer, is); - - if (length < 0) { - AudioFormat audio_format; - - NeAACDecHandle decoder = faad_decoder_new(); - - decoder_buffer_fill(buffer); - - if (faad_decoder_init(decoder, buffer, audio_format, - IgnoreError())) - length = 0; - - NeAACDecClose(decoder); - } - - decoder_buffer_free(buffer); - - return length; -} - -/** - * Get a song file's total playing time in seconds, as an int. - * Returns 0 if the duration is unknown, and a negative value if the - * file is invalid. - */ -static int -faad_get_file_time(InputStream &is) -{ - float length = faad_get_file_time_float(is); - if (length < 0) - return -1; - - return int(length + 0.5); -} - -static void -faad_stream_decode(Decoder &mpd_decoder, InputStream &is) -{ - DecoderBuffer *const buffer = - decoder_buffer_new(&mpd_decoder, is, - FAAD_MIN_STREAMSIZE * MAX_CHANNELS); - - const float total_time = faad_song_duration(buffer, is); - - /* create the libfaad decoder */ - - const NeAACDecHandle decoder = faad_decoder_new(); - - while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() && - decoder_get_command(mpd_decoder) == DecoderCommand::NONE) { - adts_find_frame(buffer); - decoder_buffer_fill(buffer); - } - - /* initialize it */ - - Error error; - AudioFormat audio_format; - if (!faad_decoder_init(decoder, buffer, audio_format, error)) { - LogError(error); - NeAACDecClose(decoder); - decoder_buffer_free(buffer); - return; - } - - /* initialize the MPD core */ - - decoder_initialized(mpd_decoder, audio_format, false, total_time); - - /* the decoder loop */ - - DecoderCommand cmd; - uint16_t bit_rate = 0; - do { - /* find the next frame */ - - const size_t frame_size = adts_find_frame(buffer); - if (frame_size == 0) - /* end of file */ - break; - - /* decode it */ - - NeAACDecFrameInfo frame_info; - const void *const decoded = - faad_decoder_decode(decoder, buffer, &frame_info); - - if (frame_info.error > 0) { - FormatWarning(faad_decoder_domain, - "error decoding AAC stream: %s", - NeAACDecGetErrorMessage(frame_info.error)); - break; - } - - if (frame_info.channels != audio_format.channels) { - FormatDefault(faad_decoder_domain, - "channel count changed from %u to %u", - audio_format.channels, frame_info.channels); - break; - } - - if (frame_info.samplerate != audio_format.sample_rate) { - FormatDefault(faad_decoder_domain, - "sample rate changed from %u to %lu", - audio_format.sample_rate, - (unsigned long)frame_info.samplerate); - break; - } - - decoder_buffer_consume(buffer, frame_info.bytesconsumed); - - /* update bit rate and position */ - - if (frame_info.samples > 0) { - bit_rate = frame_info.bytesconsumed * 8.0 * - frame_info.channels * audio_format.sample_rate / - frame_info.samples / 1000 + 0.5; - } - - /* send PCM samples to MPD */ - - cmd = decoder_data(mpd_decoder, is, decoded, - (size_t)frame_info.samples * 2, - bit_rate); - } while (cmd != DecoderCommand::STOP); - - /* cleanup */ - - NeAACDecClose(decoder); - decoder_buffer_free(buffer); -} - -static bool -faad_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - int file_time = faad_get_file_time(is); - if (file_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, file_time); - return true; -} - -static const char *const faad_suffixes[] = { "aac", nullptr }; -static const char *const faad_mime_types[] = { - "audio/aac", "audio/aacp", nullptr -}; - -const struct DecoderPlugin faad_decoder_plugin = { - "faad", - nullptr, - nullptr, - faad_stream_decode, - nullptr, - nullptr, - faad_scan_stream, - nullptr, - faad_suffixes, - faad_mime_types, -}; diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/FaadDecoderPlugin.hxx deleted file mode 100644 index 817927d5e..000000000 --- a/src/decoder/FaadDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FAAD_DECODER_PLUGIN_HXX -#define MPD_FAAD_DECODER_PLUGIN_HXX - -extern const struct DecoderPlugin faad_decoder_plugin; - -#endif diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx deleted file mode 100644 index 5133f91ac..000000000 --- a/src/decoder/FfmpegDecoderPlugin.cxx +++ /dev/null @@ -1,718 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* necessary because libavutil/common.h uses UINT64_C */ -#define __STDC_CONSTANT_MACROS - -#include "config.h" -#include "FfmpegDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "FfmpegMetaData.hxx" -#include "tag/TagHandler.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "LogV.hxx" - -extern "C" { -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libavformat/avio.h> -#include <libavutil/avutil.h> -#include <libavutil/log.h> -#include <libavutil/mathematics.h> - -#if LIBAVUTIL_VERSION_MAJOR >= 53 -#include <libavutil/frame.h> -#endif -} - -#include <assert.h> -#include <string.h> - -static constexpr Domain ffmpeg_domain("ffmpeg"); - -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - -static LogLevel -import_ffmpeg_level(int level) -{ - if (level <= AV_LOG_FATAL) - return LogLevel::ERROR; - - if (level <= AV_LOG_WARNING) - return LogLevel::WARNING; - - if (level <= AV_LOG_INFO) - return LogLevel::INFO; - - return LogLevel::DEBUG; -} - -static void -mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, - const char *fmt, va_list vl) -{ - const AVClass * cls = nullptr; - - if (ptr != nullptr) - cls = *(const AVClass *const*)ptr; - - if (cls != nullptr) { - char domain[64]; - snprintf(domain, sizeof(domain), "%s/%s", - ffmpeg_domain.GetName(), cls->item_name(ptr)); - const Domain d(domain); - LogFormatV(d, import_ffmpeg_level(level), fmt, vl); - } -} - -struct AvioStream { - Decoder *const decoder; - InputStream &input; - - AVIOContext *io; - - unsigned char buffer[8192]; - - AvioStream(Decoder *_decoder, InputStream &_input) - :decoder(_decoder), input(_input), io(nullptr) {} - - ~AvioStream() { - if (io != nullptr) - av_free(io); - } - - bool Open(); -}; - -static int -mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) -{ - AvioStream *stream = (AvioStream *)opaque; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t -mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) -{ - AvioStream *stream = (AvioStream *)opaque; - - if (whence == AVSEEK_SIZE) - return stream->input.size; - - if (!stream->input.LockSeek(pos, whence, IgnoreError())) - return -1; - - return stream->input.offset; -} - -bool -AvioStream::Open() -{ - io = avio_alloc_context(buffer, sizeof(buffer), - false, this, - mpd_ffmpeg_stream_read, nullptr, - input.seekable - ? mpd_ffmpeg_stream_seek : nullptr); - return io != nullptr; -} - -/** - * API compatibility wrapper for av_open_input_stream() and - * avformat_open_input(). - */ -static int -mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, - AVIOContext *pb, - const char *filename, - AVInputFormat *fmt) -{ - AVFormatContext *context = avformat_alloc_context(); - if (context == nullptr) - return AVERROR(ENOMEM); - - context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, nullptr); -} - -static bool -ffmpeg_init(gcc_unused const config_param ¶m) -{ - av_log_set_callback(mpd_ffmpeg_log_callback); - - av_register_all(); - return true; -} - -static int -ffmpeg_find_audio_stream(const AVFormatContext *format_context) -{ - for (unsigned i = 0; i < format_context->nb_streams; ++i) - if (format_context->streams[i]->codec->codec_type == - AVMEDIA_TYPE_AUDIO) - return i; - - return -1; -} - -gcc_const -static double -time_from_ffmpeg(int64_t t, const AVRational time_base) -{ - assert(t != (int64_t)AV_NOPTS_VALUE); - - return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) - / (double)1024; -} - -gcc_const -static int64_t -time_to_ffmpeg(double t, const AVRational time_base) -{ - return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, - time_base); -} - -/** - * Replace #AV_NOPTS_VALUE with the given fallback. - */ -static constexpr int64_t -timestamp_fallback(int64_t t, int64_t fallback) -{ - return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) - ? t - : fallback; -} - -/** - * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with - * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply - * assume that the stream's start time is zero, which appears to be - * the best way out of that situation. - */ -static int64_t -start_time_fallback(const AVStream &stream) -{ - return timestamp_fallback(stream.start_time, 0); -} - -static void -copy_interleave_frame2(uint8_t *dest, uint8_t **src, - unsigned nframes, unsigned nchannels, - unsigned sample_size) -{ - for (unsigned frame = 0; frame < nframes; ++frame) { - for (unsigned channel = 0; channel < nchannels; ++channel) { - memcpy(dest, src[channel] + frame * sample_size, - sample_size); - dest += sample_size; - } - } -} - -/** - * Copy PCM data from a AVFrame to an interleaved buffer. - */ -static int -copy_interleave_frame(const AVCodecContext *codec_context, - const AVFrame *frame, - uint8_t **output_buffer, - uint8_t **global_buffer, int *global_buffer_size) -{ - int plane_size; - const int data_size = - av_samples_get_buffer_size(&plane_size, - codec_context->channels, - frame->nb_samples, - codec_context->sample_fmt, 1); - if (data_size <= 0) - return data_size; - - if (av_sample_fmt_is_planar(codec_context->sample_fmt) && - codec_context->channels > 1) { - if(*global_buffer_size < data_size) { - av_freep(global_buffer); - - *global_buffer = (uint8_t*)av_malloc(data_size); - - if (!*global_buffer) - /* Not enough memory - shouldn't happen */ - return AVERROR(ENOMEM); - *global_buffer_size = data_size; - } - *output_buffer = *global_buffer; - copy_interleave_frame2(*output_buffer, frame->extended_data, - frame->nb_samples, - codec_context->channels, - av_get_bytes_per_sample(codec_context->sample_fmt)); - } else { - *output_buffer = frame->extended_data[0]; - } - - return data_size; -} - -static DecoderCommand -ffmpeg_send_packet(Decoder &decoder, InputStream &is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVStream *stream, - AVFrame *frame, - uint8_t **buffer, int *buffer_size) -{ - if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) - decoder_timestamp(decoder, - time_from_ffmpeg(packet->pts - start_time_fallback(*stream), - stream->time_base)); - - AVPacket packet2 = *packet; - - uint8_t *output_buffer; - - DecoderCommand cmd = DecoderCommand::NONE; - while (packet2.size > 0 && cmd == DecoderCommand::NONE) { - int audio_size = 0; - int got_frame = 0; - int len = avcodec_decode_audio4(codec_context, - frame, &got_frame, - &packet2); - if (len >= 0 && got_frame) { - audio_size = copy_interleave_frame(codec_context, - frame, - &output_buffer, - buffer, buffer_size); - if (audio_size < 0) - len = audio_size; - } - - if (len < 0) { - /* if error, we skip the frame */ - LogDefault(ffmpeg_domain, - "decoding failed, frame skipped"); - break; - } - - packet2.data += len; - packet2.size -= len; - - if (audio_size <= 0) - continue; - - cmd = decoder_data(decoder, is, - output_buffer, audio_size, - codec_context->bit_rate / 1000); - } - return cmd; -} - -gcc_const -static SampleFormat -ffmpeg_sample_format(enum AVSampleFormat sample_fmt) -{ - switch (sample_fmt) { - case AV_SAMPLE_FMT_S16: - case AV_SAMPLE_FMT_S16P: - return SampleFormat::S16; - - case AV_SAMPLE_FMT_S32: - case AV_SAMPLE_FMT_S32P: - return SampleFormat::S32; - - case AV_SAMPLE_FMT_FLTP: - return SampleFormat::FLOAT; - - default: - break; - } - - char buffer[64]; - const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), - sample_fmt); - if (name != nullptr) - FormatError(ffmpeg_domain, - "Unsupported libavcodec SampleFormat value: %s (%d)", - name, sample_fmt); - else - FormatError(ffmpeg_domain, - "Unsupported libavcodec SampleFormat value: %d", - sample_fmt); - return SampleFormat::UNDEFINED; -} - -static AVInputFormat * -ffmpeg_probe(Decoder *decoder, InputStream &is) -{ - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; - - unsigned char buffer[BUFFER_SIZE]; - size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || !is.LockRewind(IgnoreError())) - return nullptr; - - /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes - beyond the declared buffer limit, which makes valgrind - angry; this workaround removes some padding from the buffer - size */ - nbytes -= PADDING; - - AVProbeData avpd; - avpd.buf = buffer; - avpd.buf_size = nbytes; - avpd.filename = is.uri.c_str(); - - return av_probe_input_format(&avpd, true); -} - -static void -ffmpeg_decode(Decoder &decoder, InputStream &input) -{ - AVInputFormat *input_format = ffmpeg_probe(&decoder, input); - if (input_format == nullptr) - return; - - FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", - input_format->name, input_format->long_name); - - AvioStream stream(&decoder, input); - if (!stream.Open()) { - LogError(ffmpeg_domain, "Failed to open stream"); - return; - } - - //ffmpeg works with ours "fileops" helper - AVFormatContext *format_context = nullptr; - if (mpd_ffmpeg_open_input(&format_context, stream.io, - input.uri.c_str(), - input_format) != 0) { - LogError(ffmpeg_domain, "Open failed"); - return; - } - - const int find_result = - avformat_find_stream_info(format_context, nullptr); - if (find_result < 0) { - LogError(ffmpeg_domain, "Couldn't find stream info"); - avformat_close_input(&format_context); - return; - } - - int audio_stream = ffmpeg_find_audio_stream(format_context); - if (audio_stream == -1) { - LogError(ffmpeg_domain, "No audio stream inside"); - avformat_close_input(&format_context); - return; - } - - AVStream *av_stream = format_context->streams[audio_stream]; - - AVCodecContext *codec_context = av_stream->codec; - -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) - const AVCodecDescriptor *codec_descriptor = - avcodec_descriptor_get(codec_context->codec_id); - if (codec_descriptor != nullptr) - FormatDebug(ffmpeg_domain, "codec '%s'", - codec_descriptor->name); -#else - if (codec_context->codec_name[0] != 0) - FormatDebug(ffmpeg_domain, "codec '%s'", - codec_context->codec_name); -#endif - - AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); - - if (!codec) { - LogError(ffmpeg_domain, "Unsupported audio codec"); - avformat_close_input(&format_context); - return; - } - - const SampleFormat sample_format = - ffmpeg_sample_format(codec_context->sample_fmt); - if (sample_format == SampleFormat::UNDEFINED) - return; - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - codec_context->sample_rate, - sample_format, - codec_context->channels, error)) { - LogError(error); - avformat_close_input(&format_context); - return; - } - - /* the audio format must be read from AVCodecContext by now, - because avcodec_open() has been demonstrated to fill bogus - values into AVCodecContext.channels - a change that will be - reverted later by avcodec_decode_audio3() */ - - const int open_result = avcodec_open2(codec_context, codec, nullptr); - if (open_result < 0) { - LogError(ffmpeg_domain, "Could not open codec"); - avformat_close_input(&format_context); - return; - } - - int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE - ? format_context->duration / AV_TIME_BASE - : 0; - - decoder_initialized(decoder, audio_format, - input.seekable, total_time); - -#if LIBAVUTIL_VERSION_MAJOR >= 53 - AVFrame *frame = av_frame_alloc(); -#else - AVFrame *frame = avcodec_alloc_frame(); -#endif - if (!frame) { - LogError(ffmpeg_domain, "Could not allocate frame"); - avformat_close_input(&format_context); - return; - } - - uint8_t *interleaved_buffer = nullptr; - int interleaved_buffer_size = 0; - - DecoderCommand cmd; - do { - AVPacket packet; - if (av_read_frame(format_context, &packet) < 0) - /* end of file */ - break; - - if (packet.stream_index == audio_stream) - cmd = ffmpeg_send_packet(decoder, input, - &packet, codec_context, - av_stream, - frame, - &interleaved_buffer, &interleaved_buffer_size); - else - cmd = decoder_get_command(decoder); - - av_free_packet(&packet); - - if (cmd == DecoderCommand::SEEK) { - int64_t where = - time_to_ffmpeg(decoder_seek_where(decoder), - av_stream->time_base) + - start_time_fallback(*av_stream); - - if (av_seek_frame(format_context, audio_stream, where, - AVSEEK_FLAG_ANY) < 0) - decoder_seek_error(decoder); - else { - avcodec_flush_buffers(codec_context); - decoder_command_finished(decoder); - } - } - } while (cmd != DecoderCommand::STOP); - -#if LIBAVUTIL_VERSION_MAJOR >= 53 - av_frame_free(&frame); -#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) - avcodec_free_frame(&frame); -#else - av_freep(&frame); -#endif - av_freep(&interleaved_buffer); - - avcodec_close(codec_context); - avformat_close_input(&format_context); -} - -//no tag reading in ffmpeg, check if playable -static bool -ffmpeg_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - AVInputFormat *input_format = ffmpeg_probe(nullptr, is); - if (input_format == nullptr) - return false; - - AvioStream stream(nullptr, is); - if (!stream.Open()) - return false; - - AVFormatContext *f = nullptr; - if (mpd_ffmpeg_open_input(&f, stream.io, is.uri.c_str(), - input_format) != 0) - return false; - - const int find_result = - avformat_find_stream_info(f, nullptr); - if (find_result < 0) { - avformat_close_input(&f); - return false; - } - - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag_handler_invoke_duration(handler, handler_ctx, - f->duration / AV_TIME_BASE); - - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); - int idx = ffmpeg_find_audio_stream(f); - if (idx >= 0) - ffmpeg_scan_dictionary(f->streams[idx]->metadata, - handler, handler_ctx); - - avformat_close_input(&f); - return true; -} - -/** - * A list of extensions found for the formats supported by ffmpeg. - * This list is current as of 02-23-09; To find out if there are more - * supported formats, check the ffmpeg changelog since this date for - * more formats. - */ -static const char *const ffmpeg_suffixes[] = { - "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", - "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", - "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", - "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", - "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", - "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", - "m4a", "m4b", "m4v", - "mad", - "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", - "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", - "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", - "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", - "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", - "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", - "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", - "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", - "wve", - nullptr -}; - -static const char *const ffmpeg_mime_types[] = { - "application/flv", - "application/m4a", - "application/mp4", - "application/octet-stream", - "application/ogg", - "application/x-ms-wmz", - "application/x-ms-wmd", - "application/x-ogg", - "application/x-shockwave-flash", - "application/x-shorten", - "audio/8svx", - "audio/16sv", - "audio/aac", - "audio/ac3", - "audio/aiff" - "audio/amr", - "audio/basic", - "audio/flac", - "audio/m4a", - "audio/mp4", - "audio/mpeg", - "audio/musepack", - "audio/ogg", - "audio/qcelp", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-8svx", - "audio/x-16sv", - "audio/x-aac", - "audio/x-ac3", - "audio/x-aiff" - "audio/x-alaw", - "audio/x-au", - "audio/x-dca", - "audio/x-eac3", - "audio/x-flac", - "audio/x-gsm", - "audio/x-mace", - "audio/x-matroska", - "audio/x-monkeys-audio", - "audio/x-mpeg", - "audio/x-ms-wma", - "audio/x-ms-wax", - "audio/x-musepack", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - "audio/x-pn-realaudio", - "audio/x-pn-multirate-realaudio", - "audio/x-speex", - "audio/x-tta" - "audio/x-voc", - "audio/x-wav", - "audio/x-wma", - "audio/x-wv", - "video/anim", - "video/quicktime", - "video/msvideo", - "video/ogg", - "video/theora", - "video/webm", - "video/x-dv", - "video/x-flv", - "video/x-matroska", - "video/x-mjpeg", - "video/x-mpeg", - "video/x-ms-asf", - "video/x-msvideo", - "video/x-ms-wmv", - "video/x-ms-wvx", - "video/x-ms-wm", - "video/x-ms-wmx", - "video/x-nut", - "video/x-pva", - "video/x-theora", - "video/x-vid", - "video/x-wmv", - "video/x-xvid", - - /* special value for the "ffmpeg" input plugin: all streams by - the "ffmpeg" input plugin shall be decoded by this - plugin */ - "audio/x-mpd-ffmpeg", - - nullptr -}; - -const struct DecoderPlugin ffmpeg_decoder_plugin = { - "ffmpeg", - ffmpeg_init, - nullptr, - ffmpeg_decode, - nullptr, - nullptr, - ffmpeg_scan_stream, - nullptr, - ffmpeg_suffixes, - ffmpeg_mime_types -}; diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/FfmpegDecoderPlugin.hxx deleted file mode 100644 index 23bf74fce..000000000 --- a/src/decoder/FfmpegDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_FFMPEG_HXX -#define MPD_DECODER_FFMPEG_HXX - -extern const struct DecoderPlugin ffmpeg_decoder_plugin; - -#endif diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx deleted file mode 100644 index 6e92b4a13..000000000 --- a/src/decoder/FfmpegMetaData.cxx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* necessary because libavutil/common.h uses UINT64_C */ -#define __STDC_CONSTANT_MACROS - -#include "config.h" -#include "FfmpegMetaData.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagHandler.hxx" - -static const struct tag_table ffmpeg_tags[] = { - { "year", TAG_DATE }, - { "author-sort", TAG_ARTIST_SORT }, - { "album_artist", TAG_ALBUM_ARTIST }, - { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, - - /* sentinel */ - { nullptr, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -ffmpeg_copy_metadata(TagType type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *mt = nullptr; - - while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - type, mt->value); -} - -static void -ffmpeg_scan_pairs(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *i = nullptr; - - while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) - tag_handler_invoke_pair(handler, handler_ctx, - i->key, i->value); -} - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], - handler, handler_ctx); - - for (const struct tag_table *i = ffmpeg_tags; - i->name != nullptr; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); - - if (handler->pair != nullptr) - ffmpeg_scan_pairs(dict, handler, handler_ctx); -} diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx deleted file mode 100644 index 998cdf5a8..000000000 --- a/src/decoder/FfmpegMetaData.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FFMPEG_METADATA_HXX -#define MPD_FFMPEG_METADATA_HXX - -extern "C" { -#include <libavutil/dict.h> -} - -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - -struct tag_handler; - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx deleted file mode 100644 index e4b906c12..000000000 --- a/src/decoder/FlacCommon.cxx +++ /dev/null @@ -1,194 +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. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#include "config.h" -#include "FlacCommon.hxx" -#include "FlacMetadata.hxx" -#include "FlacPcm.hxx" -#include "MixRampInfo.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> - -flac_data::flac_data(Decoder &_decoder, - InputStream &_input_stream) - :FlacInput(_input_stream, &_decoder), - initialized(false), unsupported(false), - total_frames(0), first_frame(0), next_frame(0), position(0), - decoder(_decoder), input_stream(_input_stream) -{ -} - -static SampleFormat -flac_sample_format(unsigned bits_per_sample) -{ - switch (bits_per_sample) { - case 8: - return SampleFormat::S8; - - case 16: - return SampleFormat::S16; - - case 24: - return SampleFormat::S24_P32; - - case 32: - return SampleFormat::S32; - - default: - return SampleFormat::UNDEFINED; - } -} - -static void -flac_got_stream_info(struct flac_data *data, - const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - if (data->initialized || data->unsupported) - return; - - Error error; - if (!audio_format_init_checked(data->audio_format, - stream_info->sample_rate, - flac_sample_format(stream_info->bits_per_sample), - stream_info->channels, error)) { - LogError(error); - data->unsupported = true; - return; - } - - data->frame_size = data->audio_format.GetFrameSize(); - - if (data->total_frames == 0) - data->total_frames = stream_info->total_samples; - - data->initialized = true; -} - -void flac_metadata_common_cb(const FLAC__StreamMetadata * block, - struct flac_data *data) -{ - if (data->unsupported) - return; - - ReplayGainInfo rgi; - - switch (block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - flac_got_stream_info(data, &block->data.stream_info); - break; - - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - if (flac_parse_replay_gain(rgi, block)) - decoder_replay_gain(data->decoder, &rgi); - - decoder_mixramp(data->decoder, flac_parse_mixramp(block)); - - flac_vorbis_comments_to_tag(data->tag, - &block->data.vorbis_comment); - - default: - break; - } -} - -/** - * This function attempts to call decoder_initialized() in case there - * was no STREAMINFO block. This is allowed for nonseekable streams, - * where the server sends us only a part of the file, without - * providing the STREAMINFO block from the beginning of the file - * (e.g. when seeking with SqueezeBox Server). - */ -static bool -flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) -{ - if (data->unsupported) - return false; - - Error error; - if (!audio_format_init_checked(data->audio_format, - header->sample_rate, - flac_sample_format(header->bits_per_sample), - header->channels, error)) { - LogError(error); - data->unsupported = true; - return false; - } - - data->frame_size = data->audio_format.GetFrameSize(); - - decoder_initialized(data->decoder, data->audio_format, - data->input_stream.seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - - data->initialized = true; - - return true; -} - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes) -{ - void *buffer; - unsigned bit_rate; - - if (!data->initialized && !flac_got_first_frame(data, &frame->header)) - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - size_t buffer_size = frame->header.blocksize * data->frame_size; - buffer = data->buffer.Get(buffer_size); - - flac_convert(buffer, frame->header.channels, - data->audio_format.format, buf, - 0, frame->header.blocksize); - - if (nbytes > 0) - bit_rate = nbytes * 8 * frame->header.sample_rate / - (1000 * frame->header.blocksize); - else - bit_rate = 0; - - auto cmd = decoder_data(data->decoder, data->input_stream, - buffer, buffer_size, - bit_rate); - data->next_frame += frame->header.blocksize; - switch (cmd) { - case DecoderCommand::NONE: - case DecoderCommand::START: - break; - - case DecoderCommand::STOP: - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DecoderCommand::SEEK: - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx deleted file mode 100644 index de000dfa1..000000000 --- a/src/decoder/FlacCommon.hxx +++ /dev/null @@ -1,94 +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. - */ - -/* - * Common data structures and functions used by FLAC and OggFLAC - */ - -#ifndef MPD_FLAC_COMMON_HXX -#define MPD_FLAC_COMMON_HXX - -#include "FlacInput.hxx" -#include "DecoderAPI.hxx" -#include "pcm/PcmBuffer.hxx" - -#include <FLAC/stream_decoder.h> -#include <FLAC/metadata.h> - -struct flac_data : public FlacInput { - PcmBuffer buffer; - - /** - * The size of one frame in the output buffer. - */ - unsigned frame_size; - - /** - * Has decoder_initialized() been called yet? - */ - bool initialized; - - /** - * Does the FLAC file contain an unsupported audio format? - */ - bool unsupported; - - /** - * The validated audio format of the FLAC file. This - * attribute is defined if "initialized" is true. - */ - AudioFormat audio_format; - - /** - * The total number of frames in this song. The decoder - * plugin may initialize this attribute to override the value - * provided by libFLAC (e.g. for sub songs from a CUE sheet). - */ - FLAC__uint64 total_frames; - - /** - * The number of the first frame in this song. This is only - * non-zero if playing sub songs from a CUE sheet. - */ - FLAC__uint64 first_frame; - - /** - * The number of the next frame which is going to be decoded. - */ - FLAC__uint64 next_frame; - - FLAC__uint64 position; - - Decoder &decoder; - InputStream &input_stream; - - Tag tag; - - flac_data(Decoder &decoder, InputStream &input_stream); -}; - -void flac_metadata_common_cb(const FLAC__StreamMetadata * block, - struct flac_data *data); - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[], - FLAC__uint64 nbytes); - -#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx deleted file mode 100644 index 1b5734434..000000000 --- a/src/decoder/FlacDecoderPlugin.cxx +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "FlacDecoderPlugin.h" -#include "FlacDomain.hxx" -#include "FlacCommon.hxx" -#include "FlacMetadata.hxx" -#include "OggCodec.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -#error libFLAC is too old -#endif - -static void flacPrintErroredState(FLAC__StreamDecoderState state) -{ - switch (state) { - case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: - case FLAC__STREAM_DECODER_READ_METADATA: - case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: - case FLAC__STREAM_DECODER_READ_FRAME: - case FLAC__STREAM_DECODER_END_OF_STREAM: - return; - - case FLAC__STREAM_DECODER_OGG_ERROR: - case FLAC__STREAM_DECODER_SEEK_ERROR: - case FLAC__STREAM_DECODER_ABORTED: - case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - case FLAC__STREAM_DECODER_UNINITIALIZED: - break; - } - - LogError(flac_domain, FLAC__StreamDecoderStateString[state]); -} - -static void flacMetadata(gcc_unused const FLAC__StreamDecoder * dec, - const FLAC__StreamMetadata * block, void *vdata) -{ - flac_metadata_common_cb(block, (struct flac_data *) vdata); -} - -static FLAC__StreamDecoderWriteStatus -flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, - const FLAC__int32 *const buf[], void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - FLAC__uint64 nbytes = 0; - - if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { - if (data->position > 0 && nbytes > data->position) { - nbytes -= data->position; - data->position += nbytes; - } else { - data->position = nbytes; - nbytes = 0; - } - } else - nbytes = 0; - - return flac_common_write(data, frame, buf, nbytes); -} - -static bool -flac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.Read(file)) { - FormatDebug(flac_domain, - "Failed to read FLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -static bool -flac_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.Read(is)) { - FormatDebug(flac_domain, - "Failed to read FLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -/** - * Some glue code around FLAC__stream_decoder_new(). - */ -static FLAC__StreamDecoder * -flac_decoder_new(void) -{ - FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); - if (sd == nullptr) { - LogError(flac_domain, - "FLAC__stream_decoder_new() failed"); - return nullptr; - } - - if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - LogDebug(flac_domain, - "FLAC__stream_decoder_set_metadata_respond() has failed"); - - return sd; -} - -static bool -flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, - FLAC__uint64 duration) -{ - data->total_frames = duration; - - if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { - LogWarning(flac_domain, "problem reading metadata"); - return false; - } - - if (data->initialized) { - /* done */ - decoder_initialized(data->decoder, data->audio_format, - data->input_stream.seekable, - (float)data->total_frames / - (float)data->audio_format.sample_rate); - return true; - } - - if (data->input_stream.seekable) - /* allow the workaround below only for nonseekable - streams*/ - return false; - - /* no stream_info packet found; try to initialize the decoder - from the first frame header */ - FLAC__stream_decoder_process_single(sd); - return data->initialized; -} - -static void -flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, - FLAC__uint64 t_start, FLAC__uint64 t_end) -{ - Decoder &decoder = data->decoder; - - data->first_frame = t_start; - - while (true) { - DecoderCommand cmd; - if (!data->tag.IsEmpty()) { - cmd = decoder_tag(data->decoder, data->input_stream, - std::move(data->tag)); - data->tag.Clear(); - } else - cmd = decoder_get_command(decoder); - - if (cmd == DecoderCommand::SEEK) { - FLAC__uint64 seek_sample = t_start + - decoder_seek_where(decoder) * - data->audio_format.sample_rate; - if (seek_sample >= t_start && - (t_end == 0 || seek_sample <= t_end) && - FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { - data->next_frame = seek_sample; - data->position = 0; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else if (cmd == DecoderCommand::STOP || - FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) - break; - - if (t_end != 0 && data->next_frame >= t_end) - /* end of this sub track */ - break; - - if (!FLAC__stream_decoder_process_single(flac_dec) && - decoder_get_command(decoder) == DecoderCommand::NONE) { - /* a failure that was not triggered by a - decoder command */ - flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); - break; - } - } -} - -static FLAC__StreamDecoderInitStatus -stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ - return FLAC__stream_decoder_init_ogg_stream(flac_dec, - FlacInput::Read, - FlacInput::Seek, - FlacInput::Tell, - FlacInput::Length, - FlacInput::Eof, - flac_write_cb, - flacMetadata, - FlacInput::Error, - data); -} - -static FLAC__StreamDecoderInitStatus -stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) -{ - return FLAC__stream_decoder_init_stream(flac_dec, - FlacInput::Read, - FlacInput::Seek, - FlacInput::Tell, - FlacInput::Length, - FlacInput::Eof, - flac_write_cb, - flacMetadata, - FlacInput::Error, - data); -} - -static FLAC__StreamDecoderInitStatus -stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) -{ - return is_ogg - ? stream_init_oggflac(flac_dec, data) - : stream_init_flac(flac_dec, data); -} - -static void -flac_decode_internal(Decoder &decoder, - InputStream &input_stream, - bool is_ogg) -{ - FLAC__StreamDecoder *flac_dec; - - flac_dec = flac_decoder_new(); - if (flac_dec == nullptr) - return; - - struct flac_data data(decoder, input_stream); - - FLAC__StreamDecoderInitStatus status = - stream_init(flac_dec, &data, is_ogg); - if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - FLAC__stream_decoder_delete(flac_dec); - LogWarning(flac_domain, - FLAC__StreamDecoderInitStatusString[status]); - return; - } - - if (!flac_decoder_initialize(&data, flac_dec, 0)) { - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); - return; - } - - flac_decoder_loop(&data, flac_dec, 0, 0); - - FLAC__stream_decoder_finish(flac_dec); - FLAC__stream_decoder_delete(flac_dec); -} - -static void -flac_decode(Decoder &decoder, InputStream &input_stream) -{ - flac_decode_internal(decoder, input_stream, false); -} - -static bool -oggflac_init(gcc_unused const config_param ¶m) -{ - return !!FLAC_API_SUPPORTS_OGG_FLAC; -} - -static bool -oggflac_scan_file(const char *file, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.ReadOgg(file)) { - FormatDebug(flac_domain, - "Failed to read OggFLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -static bool -oggflac_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - FlacMetadataChain chain; - if (!chain.ReadOgg(is)) { - FormatDebug(flac_domain, - "Failed to read OggFLAC tags: %s", - chain.GetStatusString()); - return false; - } - - chain.Scan(handler, handler_ctx); - return true; -} - -static void -oggflac_decode(Decoder &decoder, InputStream &input_stream) -{ - if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_FLAC) - return; - - /* rewind the stream, because ogg_codec_detect() has - moved it */ - input_stream.LockRewind(IgnoreError()); - - flac_decode_internal(decoder, input_stream, true); -} - -static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; -static const char *const oggflac_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/x-flac+ogg", - "audio/x-ogg", - nullptr -}; - -const struct DecoderPlugin oggflac_decoder_plugin = { - "oggflac", - oggflac_init, - nullptr, - oggflac_decode, - nullptr, - oggflac_scan_file, - oggflac_scan_stream, - nullptr, - oggflac_suffixes, - oggflac_mime_types, -}; - -static const char *const flac_suffixes[] = { "flac", nullptr }; -static const char *const flac_mime_types[] = { - "application/flac", - "application/x-flac", - "audio/flac", - "audio/x-flac", - nullptr -}; - -const struct DecoderPlugin flac_decoder_plugin = { - "flac", - nullptr, - nullptr, - flac_decode, - nullptr, - flac_scan_file, - flac_scan_stream, - nullptr, - flac_suffixes, - flac_mime_types, -}; diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/FlacDecoderPlugin.h deleted file mode 100644 index 936423fbf..000000000 --- a/src/decoder/FlacDecoderPlugin.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2012 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_DECODER_FLAC_H -#define MPD_DECODER_FLAC_H - -extern const struct DecoderPlugin flac_decoder_plugin; -extern const struct DecoderPlugin oggflac_decoder_plugin; - -#endif diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/FlacDomain.cxx deleted file mode 100644 index 5858004de..000000000 --- a/src/decoder/FlacDomain.cxx +++ /dev/null @@ -1,24 +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 "FlacDomain.hxx" -#include "util/Domain.hxx" - -const Domain flac_domain("flac"); diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/FlacDomain.hxx deleted file mode 100644 index cf357332f..000000000 --- a/src/decoder/FlacDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_DOMAIN_HXX -#define MPD_FLAC_DOMAIN_HXX - -#include "check.h" - -extern const class Domain flac_domain; - -#endif diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/FlacIOHandle.cxx deleted file mode 100644 index b471ecf64..000000000 --- a/src/decoder/FlacIOHandle.cxx +++ /dev/null @@ -1,114 +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 "FlacIOHandle.hxx" -#include "util/Error.hxx" -#include "Compiler.h" - -#include <errno.h> - -static size_t -FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - uint8_t *const p0 = (uint8_t *)ptr, *p = p0, - *const end = p0 + size * nmemb; - - /* libFLAC is very picky about short reads, and expects the IO - callback to fill the whole buffer (undocumented!) */ - - Error error; - while (p < end) { - size_t nbytes = is->LockRead(p, end - p, error); - if (nbytes == 0) { - if (!error.IsDefined()) - /* end of file */ - break; - - if (error.IsDomain(errno_domain)) - errno = error.GetCode(); - else - /* just some random non-zero - errno value */ - errno = EINVAL; - return 0; - } - - p += nbytes; - } - - /* libFLAC expects a clean errno after returning from the IO - callbacks (undocumented!) */ - errno = 0; - return (p - p0) / size; -} - -static int -FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence) -{ - InputStream *is = (InputStream *)handle; - - Error error; - return is->LockSeek(offset, whence, error) ? 0 : -1; -} - -static FLAC__int64 -FlacIOTell(FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - return is->offset; -} - -static int -FlacIOEof(FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - return is->LockIsEOF(); -} - -static int -FlacIOClose(gcc_unused FLAC__IOHandle handle) -{ - /* no-op because the libFLAC caller is repsonsible for closing - the #InputStream */ - - return 0; -} - -const FLAC__IOCallbacks flac_io_callbacks = { - FlacIORead, - nullptr, - nullptr, - nullptr, - FlacIOEof, - FlacIOClose, -}; - -const FLAC__IOCallbacks flac_io_callbacks_seekable = { - FlacIORead, - nullptr, - FlacIOSeek, - FlacIOTell, - FlacIOEof, - FlacIOClose, -}; diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/FlacIOHandle.hxx deleted file mode 100644 index b6e563fa3..000000000 --- a/src/decoder/FlacIOHandle.hxx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_IO_HANDLE_HXX -#define MPD_FLAC_IO_HANDLE_HXX - -#include "Compiler.h" -#include "InputStream.hxx" - -#include <FLAC/callback.h> - -extern const FLAC__IOCallbacks flac_io_callbacks; -extern const FLAC__IOCallbacks flac_io_callbacks_seekable; - -static inline FLAC__IOHandle -ToFlacIOHandle(InputStream &is) -{ - return (FLAC__IOHandle)&is; -} - -static inline const FLAC__IOCallbacks & -GetFlacIOCallbacks(const InputStream &is) -{ - return is.seekable - ? flac_io_callbacks_seekable - : flac_io_callbacks; -} - -#endif diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx deleted file mode 100644 index ce193101d..000000000 --- a/src/decoder/FlacInput.cxx +++ /dev/null @@ -1,154 +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 "FlacInput.hxx" -#include "FlacDomain.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "Log.hxx" -#include "Compiler.h" - -FLAC__StreamDecoderReadStatus -FlacInput::Read(FLAC__byte buffer[], size_t *bytes) -{ - size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); - *bytes = r; - - if (r == 0) { - if (input_stream.LockIsEOF() || - (decoder != nullptr && - decoder_get_command(*decoder) != DecoderCommand::NONE)) - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - else - return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; -} - -FLAC__StreamDecoderSeekStatus -FlacInput::Seek(FLAC__uint64 absolute_byte_offset) -{ - if (!input_stream.seekable) - return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - - ::Error error; - if (!input_stream.LockSeek(absolute_byte_offset, SEEK_SET, error)) { - LogError(error); - return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; - } - - return FLAC__STREAM_DECODER_SEEK_STATUS_OK; -} - -FLAC__StreamDecoderTellStatus -FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) -{ - if (!input_stream.seekable) - return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; - - *absolute_byte_offset = (FLAC__uint64)input_stream.offset; - return FLAC__STREAM_DECODER_TELL_STATUS_OK; -} - -FLAC__StreamDecoderLengthStatus -FlacInput::Length(FLAC__uint64 *stream_length) -{ - if (input_stream.size < 0) - return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; - - *stream_length = (FLAC__uint64)input_stream.size; - return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; -} - -FLAC__bool -FlacInput::Eof() -{ - return (decoder != nullptr && - decoder_get_command(*decoder) != DecoderCommand::NONE && - decoder_get_command(*decoder) != DecoderCommand::SEEK) || - input_stream.LockIsEOF(); -} - -void -FlacInput::Error(FLAC__StreamDecoderErrorStatus status) -{ - if (decoder == nullptr || - decoder_get_command(*decoder) != DecoderCommand::STOP) - LogWarning(flac_domain, - FLAC__StreamDecoderErrorStatusString[status]); -} - -FLAC__StreamDecoderReadStatus -FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__byte buffer[], size_t *bytes, - void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Read(buffer, bytes); -} - -FLAC__StreamDecoderSeekStatus -FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 absolute_byte_offset, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Seek(absolute_byte_offset); -} - -FLAC__StreamDecoderTellStatus -FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *absolute_byte_offset, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Tell(absolute_byte_offset); -} - -FLAC__StreamDecoderLengthStatus -FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *stream_length, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Length(stream_length); -} - -FLAC__bool -FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder, - void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - return i->Eof(); -} - -void -FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder, - FLAC__StreamDecoderErrorStatus status, void *client_data) -{ - FlacInput *i = (FlacInput *)client_data; - - i->Error(status); -} - diff --git a/src/decoder/FlacInput.hxx b/src/decoder/FlacInput.hxx deleted file mode 100644 index ddd5649f8..000000000 --- a/src/decoder/FlacInput.hxx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_INPUT_HXX -#define MPD_FLAC_INPUT_HXX - -#include <FLAC/stream_decoder.h> - -struct Decoder; -struct InputStream; - -/** - * This class wraps an #InputStream in libFLAC stream decoder - * callbacks. - */ -class FlacInput { - Decoder *const decoder; - - InputStream &input_stream; - -public: - FlacInput(InputStream &_input_stream, - Decoder *_decoder=nullptr) - :decoder(_decoder), input_stream(_input_stream) {} - -protected: - FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); - FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); - FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); - FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); - FLAC__bool Eof(); - void Error(FLAC__StreamDecoderErrorStatus status); - -public: - static FLAC__StreamDecoderReadStatus - Read(const FLAC__StreamDecoder *flac_decoder, - FLAC__byte buffer[], size_t *bytes, void *client_data); - - static FLAC__StreamDecoderSeekStatus - Seek(const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 absolute_byte_offset, void *client_data); - - static FLAC__StreamDecoderTellStatus - Tell(const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *absolute_byte_offset, void *client_data); - - static FLAC__StreamDecoderLengthStatus - Length(const FLAC__StreamDecoder *flac_decoder, - FLAC__uint64 *stream_length, void *client_data); - - static FLAC__bool - Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); - - static void - Error(const FLAC__StreamDecoder *decoder, - FLAC__StreamDecoderErrorStatus status, void *client_data); -}; - -#endif diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/FlacMetadata.cxx deleted file mode 100644 index 17cc4cd8d..000000000 --- a/src/decoder/FlacMetadata.cxx +++ /dev/null @@ -1,245 +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 "FlacMetadata.hxx" -#include "XiphTags.hxx" -#include "MixRampInfo.hxx" -#include "tag/Tag.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagBuilder.hxx" -#include "ReplayGainInfo.hxx" -#include "util/ASCII.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static bool -flac_find_float_comment(const FLAC__StreamMetadata *block, - const char *cmnt, float *fl) -{ - int offset; - size_t pos; - int len; - unsigned char tmp, *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return false; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return false; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[len] = tmp; - - return true; -} - -bool -flac_parse_replay_gain(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block) -{ - rgi.Clear(); - - bool found = false; - if (flac_find_float_comment(block, "replaygain_album_gain", - &rgi.tuples[REPLAY_GAIN_ALBUM].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_album_peak", - &rgi.tuples[REPLAY_GAIN_ALBUM].peak)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_gain", - &rgi.tuples[REPLAY_GAIN_TRACK].gain)) - found = true; - if (flac_find_float_comment(block, "replaygain_track_peak", - &rgi.tuples[REPLAY_GAIN_TRACK].peak)) - found = true; - - return found; -} - -gcc_pure -static std::string -flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt) -{ - int offset; - size_t pos; - int len; - const unsigned char *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return std::string(); - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return std::string(); - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - return std::string((const char *)p, len); -} - -MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block) -{ - MixRampInfo mix_ramp; - mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start")); - mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end")); - return mix_ramp; -} - -/** - * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). - */ -static const char * -flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, size_t *length_r) -{ - size_t name_length = strlen(name); - const char *comment = (const char*)entry->entry; - - if (entry->length <= name_length || - !StringEqualsCaseASCII(comment, name, name_length)) - return nullptr; - - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } - - return nullptr; -} - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, TagType tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - size_t value_length; - - value = flac_comment_value(entry, name, &value_length); - if (value != nullptr) { - char *p = g_strndup(value, value_length); - tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); - g_free(p); - return true; - } - - return false; -} - -static void -flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != nullptr) { - char *name = g_strdup((const char*)entry->entry); - char *value = strchr(name, '='); - - if (value != nullptr && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) - if (flac_copy_comment(entry, i->name, i->type, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (flac_copy_comment(entry, - tag_item_names[i], (TagType)i, - handler, handler_ctx)) - return; -} - -static void -flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - for (unsigned i = 0; i < comment->num_comments; ++i) - flac_scan_comment(&comment->comments[i], - handler, handler_ctx); -} - -void -flac_scan_metadata(const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx) -{ - switch (block->type) { - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_scan_comments(&block->data.vorbis_comment, - handler, handler_ctx); - break; - - case FLAC__METADATA_TYPE_STREAMINFO: - if (block->data.stream_info.sample_rate > 0) - tag_handler_invoke_duration(handler, handler_ctx, - flac_duration(&block->data.stream_info)); - break; - - default: - break; - } -} - -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment) -{ - TagBuilder tag_builder; - flac_scan_comments(comment, &add_tag_handler, &tag_builder); - tag_builder.Commit(tag); -} - -void -FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) -{ - FLACMetadataIterator iterator(*this); - - do { - FLAC__StreamMetadata *block = iterator.GetBlock(); - if (block == nullptr) - break; - - flac_scan_metadata(block, handler, handler_ctx); - } while (iterator.Next()); -} diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/FlacMetadata.hxx deleted file mode 100644 index 96c61b8e6..000000000 --- a/src/decoder/FlacMetadata.hxx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_METADATA_H -#define MPD_FLAC_METADATA_H - -#include "Compiler.h" -#include "FlacIOHandle.hxx" - -#include <FLAC/metadata.h> - -#include <assert.h> - -class MixRampInfo; - -class FlacMetadataChain { - FLAC__Metadata_Chain *chain; - -public: - FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {} - - ~FlacMetadataChain() { - ::FLAC__metadata_chain_delete(chain); - } - - explicit operator FLAC__Metadata_Chain *() { - return chain; - } - - bool Read(const char *path) { - return ::FLAC__metadata_chain_read(chain, path); - } - - bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { - return ::FLAC__metadata_chain_read_with_callbacks(chain, - handle, - callbacks); - } - - bool Read(InputStream &is) { - return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); - } - - bool ReadOgg(const char *path) { - return ::FLAC__metadata_chain_read_ogg(chain, path); - } - - bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { - return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain, - handle, - callbacks); - } - - bool ReadOgg(InputStream &is) { - return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); - } - - gcc_pure - FLAC__Metadata_ChainStatus GetStatus() const { - return ::FLAC__metadata_chain_status(chain); - } - - gcc_pure - const char *GetStatusString() const { - return FLAC__Metadata_ChainStatusString[GetStatus()]; - } - - void Scan(const struct tag_handler *handler, void *handler_ctx); -}; - -class FLACMetadataIterator { - FLAC__Metadata_Iterator *iterator; - -public: - FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {} - - FLACMetadataIterator(FlacMetadataChain &chain) - :iterator(::FLAC__metadata_iterator_new()) { - ::FLAC__metadata_iterator_init(iterator, - (FLAC__Metadata_Chain *)chain); - } - - ~FLACMetadataIterator() { - ::FLAC__metadata_iterator_delete(iterator); - } - - bool Next() { - return ::FLAC__metadata_iterator_next(iterator); - } - - gcc_pure - FLAC__StreamMetadata *GetBlock() { - return ::FLAC__metadata_iterator_get_block(iterator); - } -}; - -struct tag_handler; -struct Tag; -struct ReplayGainInfo; - -static inline unsigned -flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) -{ - assert(stream_info->sample_rate > 0); - - return (stream_info->total_samples + stream_info->sample_rate - 1) / - stream_info->sample_rate; -} - -bool -flac_parse_replay_gain(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block); - -MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block); - -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment); - -void -flac_scan_metadata(const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/FlacPcm.cxx deleted file mode 100644 index 569879371..000000000 --- a/src/decoder/FlacPcm.cxx +++ /dev/null @@ -1,110 +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 "FlacPcm.hxx" - -#include <assert.h> - -static void flac_convert_stereo16(int16_t *dest, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - for (; position < end; ++position) { - *dest++ = buf[0][position]; - *dest++ = buf[1][position]; - } -} - -static void -flac_convert_16(int16_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -/** - * Note: this function also handles 24 bit files! - */ -static void -flac_convert_32(int32_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void -flac_convert_8(int8_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -void -flac_convert(void *dest, - unsigned int num_channels, SampleFormat sample_format, - const FLAC__int32 *const buf[], - unsigned int position, unsigned int end) -{ - switch (sample_format) { - case SampleFormat::S16: - if (num_channels == 2) - flac_convert_stereo16((int16_t*)dest, buf, - position, end); - else - flac_convert_16((int16_t*)dest, num_channels, buf, - position, end); - break; - - case SampleFormat::S24_P32: - case SampleFormat::S32: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; - - case SampleFormat::S8: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); - break; - - case SampleFormat::FLOAT: - case SampleFormat::DSD: - case SampleFormat::UNDEFINED: - assert(false); - gcc_unreachable(); - } -} diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/FlacPcm.hxx deleted file mode 100644 index 6cb2d5062..000000000 --- a/src/decoder/FlacPcm.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FLAC_PCM_HXX -#define MPD_FLAC_PCM_HXX - -#include "AudioFormat.hxx" - -#include <FLAC/ordinals.h> - -void -flac_convert(void *dest, - unsigned int num_channels, SampleFormat sample_format, - const FLAC__int32 *const buf[], - unsigned int position, unsigned int end); - -#endif diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/FluidsynthDecoderPlugin.cxx deleted file mode 100644 index fa946f219..000000000 --- a/src/decoder/FluidsynthDecoderPlugin.cxx +++ /dev/null @@ -1,224 +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 "FluidsynthDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <fluidsynth.h> - -static constexpr Domain fluidsynth_domain("fluidsynth"); - -static unsigned sample_rate; -static const char *soundfont_path; - -/** - * Convert a fluidsynth log level to a GLib log level. - */ -static LogLevel -fluidsynth_level_to_mpd(enum fluid_log_level level) -{ - switch (level) { - case FLUID_PANIC: - case FLUID_ERR: - return LogLevel::ERROR; - - case FLUID_WARN: - return LogLevel::WARNING; - - case FLUID_INFO: - return LogLevel::INFO; - - case FLUID_DBG: - case LAST_LOG_LEVEL: - return LogLevel::DEBUG; - } - - /* invalid fluidsynth log level */ - return LogLevel::INFO; -} - -/** - * The fluidsynth logging callback. It forwards messages to the GLib - * logging library. - */ -static void -fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data) -{ - Log(fluidsynth_domain, - fluidsynth_level_to_mpd(fluid_log_level(level)), - message); -} - -static bool -fluidsynth_init(const config_param ¶m) -{ - Error error; - - sample_rate = param.GetBlockValue("sample_rate", 48000u); - if (!audio_check_sample_rate(sample_rate, error)) { - LogError(error); - return false; - } - - soundfont_path = param.GetBlockValue("soundfont", - "/usr/share/sounds/sf2/FluidR3_GM.sf2"); - - fluid_set_log_function(LAST_LOG_LEVEL, - fluidsynth_mpd_log_function, nullptr); - - return true; -} - -static void -fluidsynth_file_decode(Decoder &decoder, const char *path_fs) -{ - char setting_sample_rate[] = "synth.sample-rate"; - /* - char setting_verbose[] = "synth.verbose"; - char setting_yes[] = "yes"; - */ - fluid_settings_t *settings; - fluid_synth_t *synth; - fluid_player_t *player; - int ret; - - /* set up fluid settings */ - - settings = new_fluid_settings(); - if (settings == nullptr) - return; - - fluid_settings_setnum(settings, setting_sample_rate, sample_rate); - - /* - fluid_settings_setstr(settings, setting_verbose, setting_yes); - */ - - /* create the fluid synth */ - - synth = new_fluid_synth(settings); - if (synth == nullptr) { - delete_fluid_settings(settings); - return; - } - - ret = fluid_synth_sfload(synth, soundfont_path, true); - if (ret < 0) { - LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed"); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* create the fluid player */ - - player = new_fluid_player(synth); - if (player == nullptr) { - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - ret = fluid_player_add(player, path_fs); - if (ret != 0) { - LogWarning(fluidsynth_domain, "fluid_player_add() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* start the player */ - - ret = fluid_player_play(player); - if (ret != 0) { - LogWarning(fluidsynth_domain, "fluid_player_play() failed"); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return; - } - - /* initialization complete - announce the audio format to the - MPD core */ - - const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); - decoder_initialized(decoder, audio_format, false, -1); - - DecoderCommand cmd; - while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { - int16_t buffer[2048]; - const unsigned max_frames = ARRAY_SIZE(buffer) / 2; - - /* read samples from fluidsynth and send them to the - MPD core */ - - ret = fluid_synth_write_s16(synth, max_frames, - buffer, 0, 2, - buffer, 1, 2); - if (ret != 0) - break; - - cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer), - 0); - if (cmd != DecoderCommand::NONE) - break; - } - - /* clean up */ - - fluid_player_stop(player); - fluid_player_join(player); - - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); -} - -static bool -fluidsynth_scan_file(const char *file, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - return fluid_is_midifile(file); -} - -static const char *const fluidsynth_suffixes[] = { - "mid", - nullptr -}; - -const struct DecoderPlugin fluidsynth_decoder_plugin = { - "fluidsynth", - fluidsynth_init, - nullptr, - nullptr, - fluidsynth_file_decode, - fluidsynth_scan_file, - nullptr, - nullptr, - fluidsynth_suffixes, - nullptr, -}; diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/FluidsynthDecoderPlugin.hxx deleted file mode 100644 index 9771898a5..000000000 --- a/src/decoder/FluidsynthDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_FLUIDSYNTH_HXX -#define MPD_DECODER_FLUIDSYNTH_HXX - -extern const struct DecoderPlugin fluidsynth_decoder_plugin; - -#endif diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx deleted file mode 100644 index d67ee4b42..000000000 --- a/src/decoder/GmeDecoderPlugin.cxx +++ /dev/null @@ -1,295 +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 "GmeDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/FormatString.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include <gme/gme.h> - -#define SUBTUNE_PREFIX "tune_" - -static constexpr Domain gme_domain("gme"); - -static constexpr unsigned GME_SAMPLE_RATE = 44100; -static constexpr unsigned GME_CHANNELS = 2; -static constexpr unsigned GME_BUFFER_FRAMES = 2048; -static constexpr unsigned GME_BUFFER_SAMPLES = - GME_BUFFER_FRAMES * GME_CHANNELS; - -/** - * returns the file path stripped of any /tune_xxx.* subtune - * suffix - */ -static char * -get_container_name(const char *path_fs) -{ - const char *subtune_suffix = uri_get_suffix(path_fs); - char *path_container = g_strdup(path_fs); - - char pat[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); - if (!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, nullptr)) { - g_pattern_spec_free(path_with_subtune); - return path_container; - } - - char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if (ptr != nullptr) - *ptr='\0'; - - g_pattern_spec_free(path_with_subtune); - return path_container; -} - -/** - * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune - * is appended. - */ -static int -get_song_num(const char *path_fs) -{ - const char *subtune_suffix = uri_get_suffix(path_fs); - - char pat[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); - - if (g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - g_pattern_spec_free(path_with_subtune); - if (!sub) - return 0; - - sub += strlen("/" SUBTUNE_PREFIX); - int song_num = strtol(sub, nullptr, 10); - - return song_num - 1; - } else { - g_pattern_spec_free(path_with_subtune); - return 0; - } -} - -static char * -gme_container_scan(const char *path_fs, const unsigned int tnum) -{ - Music_Emu *emu; - const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return nullptr; - } - - const unsigned num_songs = gme_track_count(emu); - gme_delete(emu); - /* if it only contains a single tune, don't treat as container */ - if (num_songs < 2) - return nullptr; - - const char *subtune_suffix = uri_get_suffix(path_fs); - if (tnum <= num_songs){ - return FormatNew(SUBTUNE_PREFIX "%03u.%s", - tnum, subtune_suffix); - } else - return nullptr; -} - -static void -gme_file_decode(Decoder &decoder, const char *path_fs) -{ - char *path_container = get_container_name(path_fs); - - Music_Emu *emu; - const char *gme_err = - gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return; - } - - gme_info_t *ti; - const int song_num = get_song_num(path_fs); - gme_err = gme_track_info(emu, &ti, song_num); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - gme_delete(emu); - return; - } - - const float song_len = ti->length > 0 - ? ti->length / 1000.0 - : -1.0; - - /* initialize the MPD decoder */ - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE, - SampleFormat::S16, GME_CHANNELS, - error)) { - LogError(error); - gme_free_info(ti); - gme_delete(emu); - return; - } - - decoder_initialized(decoder, audio_format, true, song_len); - - gme_err = gme_start_track(emu, song_num); - if (gme_err != nullptr) - LogWarning(gme_domain, gme_err); - - if (ti->length > 0) - gme_set_fade(emu, ti->length); - - /* play */ - DecoderCommand cmd; - do { - short buf[GME_BUFFER_SAMPLES]; - gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return; - } - - cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0); - if (cmd == DecoderCommand::SEEK) { - float where = decoder_seek_where(decoder); - gme_err = gme_seek(emu, int(where * 1000)); - if (gme_err != nullptr) - LogWarning(gme_domain, gme_err); - decoder_command_finished(decoder); - } - - if (gme_track_ended(emu)) - break; - } while (cmd != DecoderCommand::STOP); - - gme_free_info(ti); - gme_delete(emu); -} - -static bool -gme_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - char *path_container = get_container_name(path_fs); - - Music_Emu *emu; - const char *gme_err = - gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return false; - } - - const int song_num = get_song_num(path_fs); - - gme_info_t *ti; - gme_err = gme_track_info(emu, &ti, song_num); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - gme_delete(emu); - return false; - } - - assert(ti != nullptr); - - if (ti->length > 0) - tag_handler_invoke_duration(handler, handler_ctx, - ti->length / 100); - - if (ti->song != nullptr) { - if (gme_track_count(emu) > 1) { - /* start numbering subtunes from 1 */ - char tag_title[1024]; - snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - ti->song, song_num + 1, - gme_track_count(emu)); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, tag_title); - } else - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, ti->song); - } - - if (ti->author != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, ti->author); - - if (ti->game != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ALBUM, ti->game); - - if (ti->comment != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_COMMENT, ti->comment); - - if (ti->copyright != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, ti->copyright); - - gme_free_info(ti); - gme_delete(emu); - - return true; -} - -static const char *const gme_suffixes[] = { - "ay", "gbs", "gym", "hes", "kss", "nsf", - "nsfe", "sap", "spc", "vgm", "vgz", - nullptr -}; - -extern const struct DecoderPlugin gme_decoder_plugin; -const struct DecoderPlugin gme_decoder_plugin = { - "gme", - nullptr, - nullptr, - nullptr, - gme_file_decode, - gme_scan_file, - nullptr, - gme_container_scan, - gme_suffixes, - nullptr, -}; diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/GmeDecoderPlugin.hxx deleted file mode 100644 index e46dc766a..000000000 --- a/src/decoder/GmeDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_GME_HXX -#define MPD_DECODER_GME_HXX - -extern const struct DecoderPlugin gme_decoder_plugin; - -#endif diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx deleted file mode 100644 index 6f619b34b..000000000 --- a/src/decoder/MadDecoderPlugin.cxx +++ /dev/null @@ -1,1155 +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 "MadDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "ConfigGlobal.hxx" -#include "tag/TagId3.hxx" -#include "tag/TagRva2.hxx" -#include "tag/TagHandler.hxx" -#include "CheckAudioFormat.hxx" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <unistd.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <glib.h> -#include <mad.h> - -#ifdef HAVE_ID3TAG -#include <id3tag.h> -#endif - -#define FRAMES_CUSHION 2000 - -#define READ_BUFFER_SIZE 40960 - -enum mp3_action { - DECODE_SKIP = -3, - DECODE_BREAK = -2, - DECODE_CONT = -1, - DECODE_OK = 0 -}; - -enum muteframe { - MUTEFRAME_NONE, - MUTEFRAME_SKIP, - MUTEFRAME_SEEK -}; - -/* the number of samples of silence the decoder inserts at start */ -#define DECODERDELAY 529 - -#define DEFAULT_GAPLESS_MP3_PLAYBACK true - -static constexpr Domain mad_domain("mad"); - -static bool gapless_playback; - -static inline int32_t -mad_fixed_to_24_sample(mad_fixed_t sample) -{ - enum { - bits = 24, - MIN = -MAD_F_ONE, - MAX = MAD_F_ONE - 1 - }; - - /* round */ - sample = sample + (1L << (MAD_F_FRACBITS - bits)); - - /* clip */ - if (gcc_unlikely(sample > MAX)) - sample = MAX; - else if (gcc_unlikely(sample < MIN)) - sample = MIN; - - /* quantize */ - return sample >> (MAD_F_FRACBITS + 1 - bits); -} - -static void -mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, - unsigned int start, unsigned int end, - unsigned int num_channels) -{ - unsigned int i, c; - - for (i = start; i < end; ++i) { - for (c = 0; c < num_channels; ++c) - *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); - } -} - -static bool -mp3_plugin_init(gcc_unused const config_param ¶m) -{ - gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, - DEFAULT_GAPLESS_MP3_PLAYBACK); - return true; -} - -#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 - -struct MadDecoder { - struct mad_stream stream; - struct mad_frame frame; - struct mad_synth synth; - mad_timer_t timer; - unsigned char input_buffer[READ_BUFFER_SIZE]; - int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; - float total_time; - float elapsed_time; - float seek_where; - enum muteframe mute_frame; - long *frame_offsets; - mad_timer_t *times; - unsigned long highest_frame; - unsigned long max_frames; - unsigned long current_frame; - unsigned int drop_start_frames; - unsigned int drop_end_frames; - unsigned int drop_start_samples; - unsigned int drop_end_samples; - bool found_replay_gain; - bool found_xing; - bool found_first_frame; - bool decoded_first_frame; - unsigned long bit_rate; - Decoder *const decoder; - InputStream &input_stream; - enum mad_layer layer; - - MadDecoder(Decoder *decoder, InputStream &input_stream); - ~MadDecoder(); - - bool Seek(long offset); - bool FillBuffer(); - void ParseId3(size_t tagsize, Tag **mpd_tag); - enum mp3_action DecodeNextFrameHeader(Tag **tag); - enum mp3_action DecodeNextFrame(); - - gcc_pure - InputStream::offset_type ThisFrameOffset() const; - - gcc_pure - InputStream::offset_type RestIncludingThisFrame() const; - - /** - * Attempt to calulcate the length of the song from filesize - */ - void FileSizeToSongLength(); - - bool DecodeFirstFrame(Tag **tag); - - gcc_pure - long TimeToFrame(double t) const; - - void UpdateTimerNextFrame(); - - /** - * Sends the synthesized current frame via decoder_data(). - */ - DecoderCommand SendPCM(unsigned i, unsigned pcm_length); - - /** - * Synthesize the current frame and send it via - * decoder_data(). - */ - DecoderCommand SyncAndSend(); - - bool Read(); -}; - -MadDecoder::MadDecoder(Decoder *_decoder, - InputStream &_input_stream) - :mute_frame(MUTEFRAME_NONE), - frame_offsets(nullptr), - times(nullptr), - highest_frame(0), max_frames(0), current_frame(0), - drop_start_frames(0), drop_end_frames(0), - drop_start_samples(0), drop_end_samples(0), - found_replay_gain(false), found_xing(false), - found_first_frame(false), decoded_first_frame(false), - decoder(_decoder), input_stream(_input_stream), - layer(mad_layer(0)) -{ - mad_stream_init(&stream); - mad_stream_options(&stream, MAD_OPTION_IGNORECRC); - mad_frame_init(&frame); - mad_synth_init(&synth); - mad_timer_reset(&timer); -} - -inline bool -MadDecoder::Seek(long offset) -{ - Error error; - if (!input_stream.LockSeek(offset, SEEK_SET, error)) - return false; - - mad_stream_buffer(&stream, input_buffer, 0); - stream.error = MAD_ERROR_NONE; - - return true; -} - -inline bool -MadDecoder::FillBuffer() -{ - size_t remaining, length; - unsigned char *dest; - - if (stream.next_frame != nullptr) { - remaining = stream.bufend - stream.next_frame; - memmove(input_buffer, stream.next_frame, remaining); - dest = input_buffer + remaining; - length = READ_BUFFER_SIZE - remaining; - } else { - remaining = 0; - length = READ_BUFFER_SIZE; - dest = input_buffer; - } - - /* we've exhausted the read buffer, so give up!, these potential - * mp3 frames are way too big, and thus unlikely to be mp3 frames */ - if (length == 0) - return false; - - length = decoder_read(decoder, input_stream, dest, length); - if (length == 0) - return false; - - mad_stream_buffer(&stream, input_buffer, length + remaining); - stream.error = MAD_ERROR_NONE; - - return true; -} - -#ifdef HAVE_ID3TAG -static bool -parse_id3_replay_gain_info(ReplayGainInfo &rgi, - struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - bool found = false; - - rgi.Clear(); - - for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { - if (frame->nfields < 3) - continue; - - key = (char *) - id3_ucs4_latin1duplicate(id3_field_getstring - (&frame->fields[1])); - value = (char *) - id3_ucs4_latin1duplicate(id3_field_getstring - (&frame->fields[2])); - - if (StringEqualsCaseASCII(key, "replaygain_track_gain")) { - rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) { - rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) { - rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) { - rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(value); - found = true; - } - - free(key); - free(value); - } - - return found || - /* fall back on RVA2 if no replaygain tags found */ - tag_rva2_parse(tag, rgi); -} -#endif - -#ifdef HAVE_ID3TAG -gcc_pure -static MixRampInfo -parse_id3_mixramp(struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - - MixRampInfo result; - - for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { - if (frame->nfields < 3) - continue; - - key = (char *) - id3_ucs4_latin1duplicate(id3_field_getstring - (&frame->fields[1])); - value = (char *) - id3_ucs4_latin1duplicate(id3_field_getstring - (&frame->fields[2])); - - if (StringEqualsCaseASCII(key, "mixramp_start")) { - result.SetStart(value); - } else if (StringEqualsCaseASCII(key, "mixramp_end")) { - result.SetEnd(value); - } - - free(key); - free(value); - } - - return result; -} -#endif - -inline void -MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) -{ -#ifdef HAVE_ID3TAG - struct id3_tag *id3_tag = nullptr; - id3_length_t count; - id3_byte_t const *id3_data; - id3_byte_t *allocated = nullptr; - - count = stream.bufend - stream.this_frame; - - if (tagsize <= count) { - id3_data = stream.this_frame; - mad_stream_skip(&(stream), tagsize); - } else { - allocated = (id3_byte_t *)g_malloc(tagsize); - memcpy(allocated, stream.this_frame, count); - mad_stream_skip(&(stream), count); - - if (!decoder_read_full(decoder, input_stream, - allocated + count, tagsize - count)) { - LogDebug(mad_domain, "error parsing ID3 tag"); - g_free(allocated); - return; - } - - id3_data = allocated; - } - - id3_tag = id3_tag_parse(id3_data, tagsize); - if (id3_tag == nullptr) { - g_free(allocated); - return; - } - - if (mpd_tag) { - Tag *tmp_tag = tag_id3_import(id3_tag); - if (tmp_tag != nullptr) { - delete *mpd_tag; - *mpd_tag = tmp_tag; - } - } - - if (decoder != nullptr) { - ReplayGainInfo rgi; - - if (parse_id3_replay_gain_info(rgi, id3_tag)) { - decoder_replay_gain(*decoder, &rgi); - found_replay_gain = true; - } - - decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag)); - } - - id3_tag_delete(id3_tag); - - g_free(allocated); -#else /* !HAVE_ID3TAG */ - (void)mpd_tag; - - /* This code is enabled when libid3tag is disabled. Instead - of parsing the ID3 frame, it just skips it. */ - - size_t count = stream.bufend - stream.this_frame; - - if (tagsize <= count) { - mad_stream_skip(&stream, tagsize); - } else { - mad_stream_skip(&stream, count); - decoder_skip(decoder, input_stream, tagsize - count); - } -#endif -} - -#ifndef HAVE_ID3TAG -/** - * This function emulates libid3tag when it is disabled. Instead of - * doing a real analyzation of the frame, it just checks whether the - * frame begins with the string "ID3". If so, it returns the length - * of the ID3 frame. - */ -static signed long -id3_tag_query(const void *p0, size_t length) -{ - const char *p = (const char *)p0; - - return length >= 10 && memcmp(p, "ID3", 3) == 0 - ? (p[8] << 7) + p[9] + 10 - : 0; -} -#endif /* !HAVE_ID3TAG */ - -enum mp3_action -MadDecoder::DecodeNextFrameHeader(Tag **tag) -{ - if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && - !FillBuffer()) - return DECODE_BREAK; - - if (mad_header_decode(&frame.header, &stream)) { - if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) { - signed long tagsize = id3_tag_query(stream.this_frame, - stream.bufend - - stream.this_frame); - - if (tagsize > 0) { - if (tag && !(*tag)) { - ParseId3((size_t)tagsize, tag); - } else { - mad_stream_skip(&stream, tagsize); - } - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE(stream.error)) { - return DECODE_SKIP; - } else { - if (stream.error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - FormatWarning(mad_domain, - "unrecoverable frame level error: %s", - mad_stream_errorstr(&stream)); - return DECODE_BREAK; - } - } - } - - enum mad_layer new_layer = frame.header.layer; - if (layer == (mad_layer)0) { - if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) { - /* Only layer 2 and 3 have been tested to work */ - return DECODE_SKIP; - } - - layer = new_layer; - } else if (new_layer != layer) { - /* Don't decode frames with a different layer than the first */ - return DECODE_SKIP; - } - - return DECODE_OK; -} - -enum mp3_action -MadDecoder::DecodeNextFrame() -{ - if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && - !FillBuffer()) - return DECODE_BREAK; - - if (mad_frame_decode(&frame, &stream)) { - if (stream.error == MAD_ERROR_LOSTSYNC) { - signed long tagsize = id3_tag_query(stream.this_frame, - stream.bufend - - stream.this_frame); - if (tagsize > 0) { - mad_stream_skip(&stream, tagsize); - return DECODE_CONT; - } - } - if (MAD_RECOVERABLE(stream.error)) { - return DECODE_SKIP; - } else { - if (stream.error == MAD_ERROR_BUFLEN) - return DECODE_CONT; - else { - FormatWarning(mad_domain, - "unrecoverable frame level error: %s", - mad_stream_errorstr(&stream)); - return DECODE_BREAK; - } - } - } - - return DECODE_OK; -} - -/* xing stuff stolen from alsaplayer, and heavily modified by jat */ -#define XI_MAGIC (('X' << 8) | 'i') -#define NG_MAGIC (('n' << 8) | 'g') -#define IN_MAGIC (('I' << 8) | 'n') -#define FO_MAGIC (('f' << 8) | 'o') - -enum xing_magic { - XING_MAGIC_XING, /* VBR */ - XING_MAGIC_INFO /* CBR */ -}; - -struct xing { - long flags; /* valid fields (see below) */ - unsigned long frames; /* total number of frames */ - unsigned long bytes; /* total number of bytes */ - unsigned char toc[100]; /* 100-point seek table */ - long scale; /* VBR quality */ - enum xing_magic magic; /* header magic */ -}; - -enum { - XING_FRAMES = 0x00000001L, - XING_BYTES = 0x00000002L, - XING_TOC = 0x00000004L, - XING_SCALE = 0x00000008L -}; - -struct lame_version { - unsigned major; - unsigned minor; -}; - -struct lame { - char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ - struct lame_version version; /* struct containing just the version */ - float peak; /* replaygain peak */ - float track_gain; /* replaygain track gain */ - float album_gain; /* replaygain album gain */ - int encoder_delay; /* # of added samples at start of mp3 */ - int encoder_padding; /* # of added samples at end of mp3 */ - int crc; /* CRC of the first 190 bytes of this frame */ -}; - -static bool -parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) -{ - unsigned long bits; - int bitlen; - int bitsleft; - int i; - - bitlen = *oldbitlen; - - if (bitlen < 16) - return false; - - bits = mad_bit_read(ptr, 16); - bitlen -= 16; - - if (bits == XI_MAGIC) { - if (bitlen < 16) - return false; - - if (mad_bit_read(ptr, 16) != NG_MAGIC) - return false; - - bitlen -= 16; - xing->magic = XING_MAGIC_XING; - } else if (bits == IN_MAGIC) { - if (bitlen < 16) - return false; - - if (mad_bit_read(ptr, 16) != FO_MAGIC) - return false; - - bitlen -= 16; - xing->magic = XING_MAGIC_INFO; - } - else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; - else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; - else - return false; - - if (bitlen < 32) - return false; - xing->flags = mad_bit_read(ptr, 32); - bitlen -= 32; - - if (xing->flags & XING_FRAMES) { - if (bitlen < 32) - return false; - xing->frames = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - if (xing->flags & XING_BYTES) { - if (bitlen < 32) - return false; - xing->bytes = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - if (xing->flags & XING_TOC) { - if (bitlen < 800) - return false; - for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); - bitlen -= 800; - } - - if (xing->flags & XING_SCALE) { - if (bitlen < 32) - return false; - xing->scale = mad_bit_read(ptr, 32); - bitlen -= 32; - } - - /* Make sure we consume no less than 120 bytes (960 bits) in hopes that - * the LAME tag is found there, and not right after the Xing header */ - bitsleft = 960 - ((*oldbitlen) - bitlen); - if (bitsleft < 0) - return false; - else if (bitsleft > 0) { - mad_bit_read(ptr, bitsleft); - bitlen -= bitsleft; - } - - *oldbitlen = bitlen; - - return true; -} - -static bool -parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) -{ - int adj = 0; - int name; - int orig; - int sign; - int gain; - int i; - - /* Unlike the xing header, the lame tag has a fixed length. Fail if - * not all 36 bytes (288 bits) are there. */ - if (*bitlen < 288) - return false; - - for (i = 0; i < 9; i++) - lame->encoder[i] = (char)mad_bit_read(ptr, 8); - lame->encoder[9] = '\0'; - - *bitlen -= 72; - - /* This is technically incorrect, since the encoder might not be lame. - * But there's no other way to determine if this is a lame tag, and we - * wouldn't want to go reading a tag that's not there. */ - if (!g_str_has_prefix(lame->encoder, "LAME")) - return false; - - if (sscanf(lame->encoder+4, "%u.%u", - &lame->version.major, &lame->version.minor) != 2) - return false; - - FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")", - lame->version.major, lame->version.minor, lame->encoder); - - /* The reference volume was changed from the 83dB used in the - * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older - * versions, since everyone else uses 89dB instead of 83dB. - * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so - * it's impossible to make the proper adjustment for 3.95. - * Fortunately, 3.95 was only out for about a day before 3.95.1 was - * released. -- tmz */ - if (lame->version.major < 3 || - (lame->version.major == 3 && lame->version.minor < 95)) - adj = 6; - - mad_bit_read(ptr, 16); - - lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ - FormatDebug(mad_domain, "LAME peak found: %f", lame->peak); - - lame->track_gain = 0; - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 1 && orig != 0) { - lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; - FormatDebug(mad_domain, "LAME track gain found: %f", - lame->track_gain); - } - - /* tmz reports that this isn't currently written by any version of lame - * (as of 3.97). Since we have no way of testing it, don't use it. - * Wouldn't want to go blowing someone's ears just because we read it - * wrong. :P -- jat */ - lame->album_gain = 0; -#if 0 - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 2 && orig != 0) { - lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj; - FormatDebug(mad_domain, "LAME album gain found: %f", - lame->track_gain); - } -#else - mad_bit_read(ptr, 16); -#endif - - mad_bit_read(ptr, 16); - - lame->encoder_delay = mad_bit_read(ptr, 12); - lame->encoder_padding = mad_bit_read(ptr, 12); - - FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i", - lame->encoder_delay, lame->encoder_padding); - - mad_bit_read(ptr, 80); - - lame->crc = mad_bit_read(ptr, 16); - - *bitlen -= 216; - - return true; -} - -static inline float -mp3_frame_duration(const struct mad_frame *frame) -{ - return mad_timer_count(frame->header.duration, - MAD_UNITS_MILLISECONDS) / 1000.0; -} - -inline InputStream::offset_type -MadDecoder::ThisFrameOffset() const -{ - auto offset = input_stream.GetOffset(); - - if (stream.this_frame != nullptr) - offset -= stream.bufend - stream.this_frame; - else - offset -= stream.bufend - stream.buffer; - - return offset; -} - -inline InputStream::offset_type -MadDecoder::RestIncludingThisFrame() const -{ - return input_stream.GetSize() - ThisFrameOffset(); -} - -inline void -MadDecoder::FileSizeToSongLength() -{ - InputStream::offset_type rest = RestIncludingThisFrame(); - - if (rest > 0) { - float frame_duration = mp3_frame_duration(&frame); - - total_time = (rest * 8.0) / frame.header.bitrate; - max_frames = total_time / frame_duration + FRAMES_CUSHION; - } else { - max_frames = FRAMES_CUSHION; - total_time = 0; - } -} - -inline bool -MadDecoder::DecodeFirstFrame(Tag **tag) -{ - struct xing xing; - struct lame lame; - struct mad_bitptr ptr; - int bitlen; - enum mp3_action ret; - - /* stfu gcc */ - memset(&xing, 0, sizeof(struct xing)); - xing.flags = 0; - - while (true) { - do { - ret = DecodeNextFrameHeader(tag); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_SKIP) continue; - - do { - ret = DecodeNextFrame(); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - if (ret == DECODE_OK) break; - } - - ptr = stream.anc_ptr; - bitlen = stream.anc_bitlen; - - FileSizeToSongLength(); - - /* - * if an xing tag exists, use that! - */ - if (parse_xing(&xing, &ptr, &bitlen)) { - found_xing = true; - mute_frame = MUTEFRAME_SKIP; - - if ((xing.flags & XING_FRAMES) && xing.frames) { - mad_timer_t duration = frame.header.duration; - mad_timer_multiply(&duration, xing.frames); - total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; - max_frames = xing.frames; - } - - if (parse_lame(&lame, &ptr, &bitlen)) { - if (gapless_playback && input_stream.IsSeekable()) { - drop_start_samples = lame.encoder_delay + - DECODERDELAY; - drop_end_samples = lame.encoder_padding; - } - - /* Album gain isn't currently used. See comment in - * parse_lame() for details. -- jat */ - if (decoder != nullptr && !found_replay_gain && - lame.track_gain) { - ReplayGainInfo rgi; - rgi.Clear(); - rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; - rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak; - decoder_replay_gain(*decoder, &rgi); - } - } - } - - if (!max_frames) - return false; - - if (max_frames > 8 * 1024 * 1024) { - FormatWarning(mad_domain, - "mp3 file header indicates too many frames: %lu", - max_frames); - return false; - } - - frame_offsets = new long[max_frames]; - times = new mad_timer_t[max_frames]; - - return true; -} - -MadDecoder::~MadDecoder() -{ - mad_synth_finish(&synth); - mad_frame_finish(&frame); - mad_stream_finish(&stream); - - delete[] frame_offsets; - delete[] times; -} - -/* this is primarily used for getting total time for tags */ -static int -mad_decoder_total_file_time(InputStream &is) -{ - MadDecoder data(nullptr, is); - return data.DecodeFirstFrame(nullptr) - ? data.total_time + 0.5 - : -1; -} - -long -MadDecoder::TimeToFrame(double t) const -{ - unsigned long i; - - for (i = 0; i < highest_frame; ++i) { - double frame_time = - mad_timer_count(times[i], - MAD_UNITS_MILLISECONDS) / 1000.; - if (frame_time >= t) - break; - } - - return i; -} - -void -MadDecoder::UpdateTimerNextFrame() -{ - if (current_frame >= highest_frame) { - /* record this frame's properties in frame_offsets - (for seeking) and times */ - bit_rate = frame.header.bitrate; - - if (current_frame >= max_frames) - /* cap current_frame */ - current_frame = max_frames - 1; - else - highest_frame++; - - frame_offsets[current_frame] = ThisFrameOffset(); - - mad_timer_add(&timer, frame.header.duration); - times[current_frame] = timer; - } else - /* get the new timer value from "times" */ - timer = times[current_frame]; - - current_frame++; - elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0; -} - -DecoderCommand -MadDecoder::SendPCM(unsigned i, unsigned pcm_length) -{ - unsigned max_samples; - - max_samples = sizeof(output_buffer) / - sizeof(output_buffer[0]) / - MAD_NCHANNELS(&frame.header); - - while (i < pcm_length) { - unsigned int num_samples = pcm_length - i; - if (num_samples > max_samples) - num_samples = max_samples; - - i += num_samples; - - mad_fixed_to_24_buffer(output_buffer, &synth, - i - num_samples, i, - MAD_NCHANNELS(&frame.header)); - num_samples *= MAD_NCHANNELS(&frame.header); - - auto cmd = decoder_data(*decoder, input_stream, output_buffer, - sizeof(output_buffer[0]) * num_samples, - bit_rate / 1000); - if (cmd != DecoderCommand::NONE) - return cmd; - } - - return DecoderCommand::NONE; -} - -inline DecoderCommand -MadDecoder::SyncAndSend() -{ - mad_synth_frame(&synth, &frame); - - if (!found_first_frame) { - unsigned int samples_per_frame = synth.pcm.length; - drop_start_frames = drop_start_samples / samples_per_frame; - drop_end_frames = drop_end_samples / samples_per_frame; - drop_start_samples = drop_start_samples % samples_per_frame; - drop_end_samples = drop_end_samples % samples_per_frame; - found_first_frame = true; - } - - if (drop_start_frames > 0) { - drop_start_frames--; - return DecoderCommand::NONE; - } else if ((drop_end_frames > 0) && - (current_frame == (max_frames + 1 - drop_end_frames))) { - /* stop decoding, effectively dropping all remaining - frames */ - return DecoderCommand::STOP; - } - - unsigned i = 0; - if (!decoded_first_frame) { - i = drop_start_samples; - decoded_first_frame = true; - } - - unsigned pcm_length = synth.pcm.length; - if (drop_end_samples && - (current_frame == max_frames - drop_end_frames)) { - if (drop_end_samples >= pcm_length) - pcm_length = 0; - else - pcm_length -= drop_end_samples; - } - - auto cmd = SendPCM(i, pcm_length); - if (cmd != DecoderCommand::NONE) - return cmd; - - if (drop_end_samples && - (current_frame == max_frames - drop_end_frames)) - /* stop decoding, effectively dropping - * all remaining samples */ - return DecoderCommand::STOP; - - return DecoderCommand::NONE; -} - -inline bool -MadDecoder::Read() -{ - enum mp3_action ret; - - UpdateTimerNextFrame(); - - switch (mute_frame) { - DecoderCommand cmd; - - case MUTEFRAME_SKIP: - mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_SEEK: - if (elapsed_time >= seek_where) - mute_frame = MUTEFRAME_NONE; - break; - case MUTEFRAME_NONE: - cmd = SyncAndSend(); - if (cmd == DecoderCommand::SEEK) { - unsigned long j; - - assert(input_stream.IsSeekable()); - - j = TimeToFrame(decoder_seek_where(*decoder)); - if (j < highest_frame) { - if (Seek(frame_offsets[j])) { - current_frame = j; - decoder_command_finished(*decoder); - } else - decoder_seek_error(*decoder); - } else { - seek_where = decoder_seek_where(*decoder); - mute_frame = MUTEFRAME_SEEK; - decoder_command_finished(*decoder); - } - } else if (cmd != DecoderCommand::NONE) - return false; - } - - while (true) { - bool skip = false; - - do { - Tag *tag = nullptr; - - ret = DecodeNextFrameHeader(&tag); - - if (tag != nullptr) { - decoder_tag(*decoder, input_stream, - std::move(*tag)); - delete tag; - } - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - else if (ret == DECODE_SKIP) - skip = true; - - if (mute_frame == MUTEFRAME_NONE) { - do { - ret = DecodeNextFrame(); - } while (ret == DECODE_CONT); - if (ret == DECODE_BREAK) - return false; - } - - if (!skip && ret == DECODE_OK) - break; - } - - return ret != DECODE_BREAK; -} - -static void -mp3_decode(Decoder &decoder, InputStream &input_stream) -{ - MadDecoder data(&decoder, input_stream); - - Tag *tag = nullptr; - if (!data.DecodeFirstFrame(&tag)) { - delete tag; - - if (decoder_get_command(decoder) == DecoderCommand::NONE) - LogError(mad_domain, - "Input does not appear to be a mp3 bit stream"); - return; - } - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - data.frame.header.samplerate, - SampleFormat::S24_P32, - MAD_NCHANNELS(&data.frame.header), - error)) { - LogError(error); - delete tag; - return; - } - - decoder_initialized(decoder, audio_format, - input_stream.IsSeekable(), - data.total_time); - - if (tag != nullptr) { - decoder_tag(decoder, input_stream, std::move(*tag)); - delete tag; - } - - while (data.Read()) {} -} - -static bool -mad_decoder_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - int total_time; - - total_time = mad_decoder_total_file_time(is); - if (total_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr }; -static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr }; - -const struct DecoderPlugin mad_decoder_plugin = { - "mad", - mp3_plugin_init, - nullptr, - mp3_decode, - nullptr, - nullptr, - mad_decoder_scan_stream, - nullptr, - mp3_suffixes, - mp3_mime_types, -}; diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/MadDecoderPlugin.hxx deleted file mode 100644 index 450323670..000000000 --- a/src/decoder/MadDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_MAD_HXX -#define MPD_DECODER_MAD_HXX - -extern const struct DecoderPlugin mad_decoder_plugin; - -#endif diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx deleted file mode 100644 index 34381aafa..000000000 --- a/src/decoder/MikmodDecoderPlugin.cxx +++ /dev/null @@ -1,248 +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 "MikmodDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "system/FatalError.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <mikmod.h> -#include <assert.h> - -static constexpr Domain mikmod_domain("mikmod"); - -/* this is largely copied from alsaplayer */ - -static constexpr size_t MIKMOD_FRAME_SIZE = 4096; - -static BOOL -mikmod_mpd_init(void) -{ - return VC_Init(); -} - -static void -mikmod_mpd_exit(void) -{ - VC_Exit(); -} - -static void -mikmod_mpd_update(void) -{ -} - -static BOOL -mikmod_mpd_is_present(void) -{ - return true; -} - -static char drv_name[] = PACKAGE_NAME; -static char drv_version[] = VERSION; - -#if (LIBMIKMOD_VERSION > 0x030106) -static char drv_alias[] = PACKAGE; -#endif - -static MDRIVER drv_mpd = { - nullptr, - drv_name, - drv_version, - 0, - 255, -#if (LIBMIKMOD_VERSION > 0x030106) - drv_alias, -#if (LIBMIKMOD_VERSION >= 0x030200) - nullptr, /* CmdLineHelp */ -#endif - nullptr, /* CommandLine */ -#endif - mikmod_mpd_is_present, - VC_SampleLoad, - VC_SampleUnload, - VC_SampleSpace, - VC_SampleLength, - mikmod_mpd_init, - mikmod_mpd_exit, - nullptr, - VC_SetNumVoices, - VC_PlayStart, - VC_PlayStop, - mikmod_mpd_update, - nullptr, - VC_VoiceSetVolume, - VC_VoiceGetVolume, - VC_VoiceSetFrequency, - VC_VoiceGetFrequency, - VC_VoiceSetPanning, - VC_VoiceGetPanning, - VC_VoicePlay, - VC_VoiceStop, - VC_VoiceStopped, - VC_VoiceGetPosition, - VC_VoiceRealVolume -}; - -static bool mikmod_loop; -static unsigned mikmod_sample_rate; - -static bool -mikmod_decoder_init(const config_param ¶m) -{ - static char params[] = ""; - - mikmod_loop = param.GetBlockValue("loop", false); - mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u); - if (!audio_valid_sample_rate(mikmod_sample_rate)) - FormatFatalError("Invalid sample rate in line %d: %u", - param.line, mikmod_sample_rate); - - md_device = 0; - md_reverb = 0; - - MikMod_RegisterDriver(&drv_mpd); - MikMod_RegisterAllLoaders(); - - md_pansep = 64; - md_mixfreq = mikmod_sample_rate; - md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | - DMODE_16BITS); - - if (MikMod_Init(params)) { - FormatError(mikmod_domain, - "Could not init MikMod: %s", - MikMod_strerror(MikMod_errno)); - return false; - } - - return true; -} - -static void -mikmod_decoder_finish(void) -{ - MikMod_Exit(); -} - -static void -mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs) -{ - /* deconstify the path because libmikmod wants a non-const - string pointer */ - char *const path2 = const_cast<char *>(path_fs); - - MODULE *handle; - int ret; - SBYTE buffer[MIKMOD_FRAME_SIZE]; - - handle = Player_Load(path2, 128, 0); - - if (handle == nullptr) { - FormatError(mikmod_domain, - "failed to open mod: %s", path_fs); - return; - } - - handle->loop = mikmod_loop; - - const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, false, 0); - - Player_Start(handle); - - DecoderCommand cmd = DecoderCommand::NONE; - while (cmd == DecoderCommand::NONE && Player_Active()) { - ret = VC_WriteBytes(buffer, sizeof(buffer)); - cmd = decoder_data(decoder, nullptr, buffer, ret, 0); - } - - Player_Stop(); - Player_Free(handle); -} - -static bool -mikmod_decoder_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - /* deconstify the path because libmikmod wants a non-const - string pointer */ - char *const path2 = const_cast<char *>(path_fs); - - MODULE *handle = Player_Load(path2, 128, 0); - - if (handle == nullptr) { - FormatDebug(mikmod_domain, - "Failed to open file: %s", path_fs); - return false; - } - - Player_Free(handle); - - char *title = Player_LoadTitle(path2); - if (title != nullptr) { - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, title); -#if (LIBMIKMOD_VERSION >= 0x030200) - MikMod_free(title); -#else - free(title); -#endif - } - - return true; -} - -static const char *const mikmod_decoder_suffixes[] = { - "amf", - "dsm", - "far", - "gdm", - "imf", - "it", - "med", - "mod", - "mtm", - "s3m", - "stm", - "stx", - "ult", - "uni", - "xm", - nullptr -}; - -const struct DecoderPlugin mikmod_decoder_plugin = { - "mikmod", - mikmod_decoder_init, - mikmod_decoder_finish, - nullptr, - mikmod_decoder_file_decode, - mikmod_decoder_scan_file, - nullptr, - nullptr, - mikmod_decoder_suffixes, - nullptr, -}; diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/MikmodDecoderPlugin.hxx deleted file mode 100644 index d25c5f6e7..000000000 --- a/src/decoder/MikmodDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_MIKMOD_HXX -#define MPD_DECODER_MIKMOD_HXX - -extern const struct DecoderPlugin mikmod_decoder_plugin; - -#endif diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx deleted file mode 100644 index e75f5479c..000000000 --- a/src/decoder/ModplugDecoderPlugin.cxx +++ /dev/null @@ -1,215 +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 "ModplugDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "tag/TagHandler.hxx" -#include "system/FatalError.hxx" -#include "util/WritableBuffer.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <libmodplug/modplug.h> - - -#include <assert.h> - -static constexpr Domain modplug_domain("modplug"); - -static constexpr size_t MODPLUG_FRAME_SIZE = 4096; -static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024; -static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; - -static int modplug_loop_count; - -static bool -modplug_decoder_init(const config_param ¶m) -{ - modplug_loop_count = param.GetBlockValue("loop_count", 0); - if (modplug_loop_count < -1) - FormatFatalError("Invalid loop count in line %d: %i", - param.line, modplug_loop_count); - - return true; -} - -static WritableBuffer<uint8_t> -mod_loadfile(Decoder *decoder, InputStream &is) -{ - const InputStream::offset_type size = is.GetSize(); - - if (size == 0) { - LogWarning(modplug_domain, "file is empty"); - return { nullptr, 0 }; - } - - if (size > MODPLUG_FILE_LIMIT) { - LogWarning(modplug_domain, "file too large"); - return { nullptr, 0 }; - } - - //known/unknown size, preallocate array, lets read in chunks - - const bool is_stream = size < 0; - - WritableBuffer<uint8_t> buffer; - buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size; - buffer.data = new uint8_t[buffer.size]; - - uint8_t *const end = buffer.end(); - uint8_t *p = buffer.begin(); - - while (true) { - size_t ret = decoder_read(decoder, is, p, end - p); - if (ret == 0) { - if (is.LockIsEOF()) - /* end of file */ - break; - - /* I/O error - skip this song */ - delete[] buffer.data; - buffer.data = nullptr; - return buffer; - } - - p += ret; - if (p == end) { - if (!is_stream) - break; - - LogWarning(modplug_domain, "stream too large"); - delete[] buffer.data; - buffer.data = nullptr; - return buffer; - } - } - - buffer.size = p - buffer.data; - return buffer; -} - -static ModPlugFile * -LoadModPlugFile(Decoder *decoder, InputStream &is) -{ - const auto buffer = mod_loadfile(decoder, is); - if (buffer.IsNull()) { - LogWarning(modplug_domain, "could not load stream"); - return nullptr; - } - - ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size); - delete[] buffer.data; - return f; -} - -static void -mod_decode(Decoder &decoder, InputStream &is) -{ - ModPlug_Settings settings; - int ret; - char audio_buffer[MODPLUG_FRAME_SIZE]; - - ModPlug_GetSettings(&settings); - /* alter setting */ - settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ - settings.mChannels = 2; - settings.mBits = 16; - settings.mFrequency = 44100; - settings.mLoopCount = modplug_loop_count; - /* insert more setting changes here */ - ModPlug_SetSettings(&settings); - - ModPlugFile *f = LoadModPlugFile(&decoder, is); - if (f == nullptr) { - LogWarning(modplug_domain, "could not decode stream"); - return; - } - - static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, - is.IsSeekable(), - ModPlug_GetLength(f) / 1000.0); - - DecoderCommand cmd; - do { - ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); - if (ret <= 0) - break; - - cmd = decoder_data(decoder, nullptr, - audio_buffer, ret, - 0); - - if (cmd == DecoderCommand::SEEK) { - float where = decoder_seek_where(decoder); - - ModPlug_Seek(f, (int)(where * 1000.0)); - - decoder_command_finished(decoder); - } - - } while (cmd != DecoderCommand::STOP); - - ModPlug_Unload(f); -} - -static bool -modplug_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - ModPlugFile *f = LoadModPlugFile(nullptr, is); - if (f == nullptr) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - ModPlug_GetLength(f) / 1000); - - const char *title = ModPlug_GetName(f); - if (title != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, title); - - ModPlug_Unload(f); - - return true; -} - -static const char *const mod_suffixes[] = { - "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it", - "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm", - "ult", "umx", "xm", - nullptr -}; - -const struct DecoderPlugin modplug_decoder_plugin = { - "modplug", - modplug_decoder_init, - nullptr, - mod_decode, - nullptr, - nullptr, - modplug_scan_stream, - nullptr, - mod_suffixes, - nullptr, -}; diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/ModplugDecoderPlugin.hxx deleted file mode 100644 index 4cd9f5b25..000000000 --- a/src/decoder/ModplugDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_MODPLUG_HXX -#define MPD_DECODER_MODPLUG_HXX - -extern const struct DecoderPlugin modplug_decoder_plugin; - -#endif diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx deleted file mode 100644 index dc258623c..000000000 --- a/src/decoder/MpcdecDecoderPlugin.cxx +++ /dev/null @@ -1,280 +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 "MpcdecDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <mpc/mpcdec.h> - -#include <assert.h> -#include <unistd.h> -#include <math.h> - -struct mpc_decoder_data { - InputStream &is; - Decoder *decoder; - - mpc_decoder_data(InputStream &_is, Decoder *_decoder) - :is(_is), decoder(_decoder) {} -}; - -static constexpr Domain mpcdec_domain("mpcdec"); - -static mpc_int32_t -mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return decoder_read(data->decoder, data->is, ptr, size); -} - -static mpc_bool_t -mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return data->is.LockSeek(offset, SEEK_SET, IgnoreError()); -} - -static mpc_int32_t -mpc_tell_cb(mpc_reader *reader) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return (long)data->is.GetOffset(); -} - -static mpc_bool_t -mpc_canseek_cb(mpc_reader *reader) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return data->is.IsSeekable(); -} - -static mpc_int32_t -mpc_getsize_cb(mpc_reader *reader) -{ - struct mpc_decoder_data *data = - (struct mpc_decoder_data *)reader->data; - - return data->is.GetSize(); -} - -/* this _looks_ performance-critical, don't de-inline -- eric */ -static inline int32_t -mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) -{ - /* only doing 16-bit audio for now */ - int32_t val; - - enum { - bits = 24, - }; - - const int clip_min = -1 << (bits - 1); - const int clip_max = (1 << (bits - 1)) - 1; - -#ifdef MPC_FIXED_POINT - const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; - - if (shift < 0) - val = sample >> -shift; - else - val = sample << shift; -#else - const int float_scale = 1 << (bits - 1); - - val = sample * float_scale; -#endif - - if (val < clip_min) - val = clip_min; - else if (val > clip_max) - val = clip_max; - - return val; -} - -static void -mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src, - unsigned num_samples) -{ - while (num_samples-- > 0) - *dest++ = mpc_to_mpd_sample(*src++); -} - -static void -mpcdec_decode(Decoder &mpd_decoder, InputStream &is) -{ - MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; - - mpc_decoder_data data(is, &mpd_decoder); - - mpc_reader reader; - reader.read = mpc_read_cb; - reader.seek = mpc_seek_cb; - reader.tell = mpc_tell_cb; - reader.get_size = mpc_getsize_cb; - reader.canseek = mpc_canseek_cb; - reader.data = &data; - - mpc_demux *demux = mpc_demux_init(&reader); - if (demux == nullptr) { - if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP) - LogWarning(mpcdec_domain, - "Not a valid musepack stream"); - return; - } - - mpc_streaminfo info; - mpc_demux_get_info(demux, &info); - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, info.sample_freq, - SampleFormat::S24_P32, - info.channels, error)) { - LogError(error); - mpc_demux_exit(demux); - return; - } - - ReplayGainInfo rgi; - rgi.Clear(); - rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); - rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; - rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); - rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; - - decoder_replay_gain(mpd_decoder, &rgi); - - decoder_initialized(mpd_decoder, audio_format, - is.IsSeekable(), - mpc_streaminfo_get_length(&info)); - - DecoderCommand cmd = DecoderCommand::NONE; - do { - if (cmd == DecoderCommand::SEEK) { - mpc_int64_t where = decoder_seek_where(mpd_decoder) * - audio_format.sample_rate; - bool success; - - success = mpc_demux_seek_sample(demux, where) - == MPC_STATUS_OK; - if (success) - decoder_command_finished(mpd_decoder); - else - decoder_seek_error(mpd_decoder); - } - - mpc_uint32_t vbr_update_bits = 0; - - mpc_frame_info frame; - frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; - mpc_status status = mpc_demux_decode(demux, &frame); - if (status != MPC_STATUS_OK) { - LogWarning(mpcdec_domain, - "Failed to decode sample"); - break; - } - - if (frame.bits == -1) - break; - - mpc_uint32_t ret = frame.samples; - ret *= info.channels; - - int32_t chunk[ARRAY_SIZE(sample_buffer)]; - mpc_to_mpd_buffer(chunk, sample_buffer, ret); - - long bit_rate = vbr_update_bits * audio_format.sample_rate - / 1152 / 1000; - - cmd = decoder_data(mpd_decoder, is, - chunk, ret * sizeof(chunk[0]), - bit_rate); - } while (cmd != DecoderCommand::STOP); - - mpc_demux_exit(demux); -} - -static float -mpcdec_get_file_duration(InputStream &is) -{ - mpc_decoder_data data(is, nullptr); - - mpc_reader reader; - reader.read = mpc_read_cb; - reader.seek = mpc_seek_cb; - reader.tell = mpc_tell_cb; - reader.get_size = mpc_getsize_cb; - reader.canseek = mpc_canseek_cb; - reader.data = &data; - - mpc_demux *demux = mpc_demux_init(&reader); - if (demux == nullptr) - return -1; - - mpc_streaminfo info; - mpc_demux_get_info(demux, &info); - mpc_demux_exit(demux); - - return mpc_streaminfo_get_length(&info); -} - -static bool -mpcdec_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - float total_time = mpcdec_get_file_duration(is); - - if (total_time < 0) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, total_time); - return true; -} - -static const char *const mpcdec_suffixes[] = { "mpc", nullptr }; - -const struct DecoderPlugin mpcdec_decoder_plugin = { - "mpcdec", - nullptr, - nullptr, - mpcdec_decode, - nullptr, - nullptr, - mpcdec_scan_stream, - nullptr, - mpcdec_suffixes, - nullptr, -}; diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/MpcdecDecoderPlugin.hxx deleted file mode 100644 index 23ecc801e..000000000 --- a/src/decoder/MpcdecDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_MPCDEC_HXX -#define MPD_DECODER_MPCDEC_HXX - -extern const struct DecoderPlugin mpcdec_decoder_plugin; - -#endif diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx deleted file mode 100644 index df23f7847..000000000 --- a/src/decoder/Mpg123DecoderPlugin.cxx +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "Mpg123DecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <mpg123.h> -#include <stdio.h> - -static constexpr Domain mpg123_domain("mpg123"); - -static bool -mpd_mpg123_init(gcc_unused const config_param ¶m) -{ - mpg123_init(); - - return true; -} - -static void -mpd_mpg123_finish(void) -{ - mpg123_exit(); -} - -/** - * Opens a file with an existing #mpg123_handle. - * - * @param handle a handle which was created before; on error, this - * function will not free it - * @param audio_format this parameter is filled after successful - * return - * @return true on success - */ -static bool -mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, - AudioFormat &audio_format) -{ - int error; - int channels, encoding; - long rate; - - /* mpg123_open() wants a writable string :-( */ - char *const path2 = const_cast<char *>(path_fs); - - error = mpg123_open(handle, path2); - if (error != MPG123_OK) { - FormatWarning(mpg123_domain, - "libmpg123 failed to open %s: %s", - path_fs, mpg123_plain_strerror(error)); - return false; - } - - /* obtain the audio format */ - - error = mpg123_getformat(handle, &rate, &channels, &encoding); - if (error != MPG123_OK) { - FormatWarning(mpg123_domain, - "mpg123_getformat() failed: %s", - mpg123_plain_strerror(error)); - return false; - } - - if (encoding != MPG123_ENC_SIGNED_16) { - /* other formats not yet implemented */ - FormatWarning(mpg123_domain, - "expected MPG123_ENC_SIGNED_16, got %d", - encoding); - return false; - } - - Error error2; - if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16, - channels, error2)) { - LogError(error2); - return false; - } - - return true; -} - -static void -mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs) -{ - mpg123_handle *handle; - int error; - off_t num_samples; - struct mpg123_frameinfo info; - - /* open the file */ - - handle = mpg123_new(nullptr, &error); - if (handle == nullptr) { - FormatError(mpg123_domain, - "mpg123_new() failed: %s", - mpg123_plain_strerror(error)); - return; - } - - AudioFormat audio_format; - if (!mpd_mpg123_open(handle, path_fs, audio_format)) { - mpg123_delete(handle); - return; - } - - num_samples = mpg123_length(handle); - - /* tell MPD core we're ready */ - - decoder_initialized(decoder, audio_format, true, - (float)num_samples / - (float)audio_format.sample_rate); - - if (mpg123_info(handle, &info) != MPG123_OK) { - info.vbr = MPG123_CBR; - info.bitrate = 0; - } - - switch (info.vbr) { - case MPG123_ABR: - info.bitrate = info.abr_rate; - break; - case MPG123_CBR: - break; - default: - info.bitrate = 0; - } - - /* the decoder main loop */ - - DecoderCommand cmd; - do { - unsigned char buffer[8192]; - size_t nbytes; - - /* decode */ - - error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); - if (error != MPG123_OK) { - if (error != MPG123_DONE) - FormatWarning(mpg123_domain, - "mpg123_read() failed: %s", - mpg123_plain_strerror(error)); - break; - } - - /* update bitrate for ABR/VBR */ - if (info.vbr != MPG123_CBR) { - /* FIXME: maybe skip, as too expensive? */ - /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ - if (mpg123_info (handle, &info) != MPG123_OK) - info.bitrate = 0; - } - - /* send to MPD */ - - cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate); - - if (cmd == DecoderCommand::SEEK) { - off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; - c = mpg123_seek(handle, c, SEEK_SET); - if (c < 0) - decoder_seek_error(decoder); - else { - decoder_command_finished(decoder); - decoder_timestamp(decoder, c/(double)audio_format.sample_rate); - } - - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); - - /* cleanup */ - - mpg123_delete(handle); -} - -static bool -mpd_mpg123_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - mpg123_handle *handle; - int error; - off_t num_samples; - - handle = mpg123_new(nullptr, &error); - if (handle == nullptr) { - FormatError(mpg123_domain, - "mpg123_new() failed: %s", - mpg123_plain_strerror(error)); - return false; - } - - AudioFormat audio_format; - if (!mpd_mpg123_open(handle, path_fs, audio_format)) { - mpg123_delete(handle); - return false; - } - - num_samples = mpg123_length(handle); - if (num_samples <= 0) { - mpg123_delete(handle); - return false; - } - - /* ID3 tag support not yet implemented */ - - mpg123_delete(handle); - - tag_handler_invoke_duration(handler, handler_ctx, - num_samples / audio_format.sample_rate); - return true; -} - -static const char *const mpg123_suffixes[] = { - "mp3", - nullptr -}; - -const struct DecoderPlugin mpg123_decoder_plugin = { - "mpg123", - mpd_mpg123_init, - mpd_mpg123_finish, - /* streaming not yet implemented */ - nullptr, - mpd_mpg123_file_decode, - mpd_mpg123_scan_file, - nullptr, - nullptr, - mpg123_suffixes, - nullptr, -}; diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/Mpg123DecoderPlugin.hxx deleted file mode 100644 index 10f7c37f5..000000000 --- a/src/decoder/Mpg123DecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_MPG123_HXX -#define MPD_DECODER_MPG123_HXX - -extern const struct DecoderPlugin mpg123_decoder_plugin; - -#endif diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx deleted file mode 100644 index 565dbafcf..000000000 --- a/src/decoder/OggCodec.cxx +++ /dev/null @@ -1,50 +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. - */ - -/* - * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#include "config.h" -#include "OggCodec.hxx" - -#include <string.h> - -enum ogg_codec -ogg_codec_detect(Decoder *decoder, InputStream &is) -{ - /* oggflac detection based on code in ogg123 and this post - * http://lists.xiph.org/pipermail/flac/2004-December/000393.html - * ogg123 trunk still doesn't have this patch as of June 2005 */ - unsigned char buf[41]; - size_t r = decoder_read(decoder, is, buf, sizeof(buf)); - if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) - return OGG_CODEC_UNKNOWN; - - if ((memcmp(buf + 29, "FLAC", 4) == 0 && - memcmp(buf + 37, "fLaC", 4) == 0) || - memcmp(buf + 28, "FLAC", 4) == 0 || - memcmp(buf + 28, "fLaC", 4) == 0) - return OGG_CODEC_FLAC; - - if (memcmp(buf + 28, "Opus", 4) == 0) - return OGG_CODEC_OPUS; - - return OGG_CODEC_VORBIS; -} diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx deleted file mode 100644 index 857871607..000000000 --- a/src/decoder/OggCodec.hxx +++ /dev/null @@ -1,39 +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. - */ - -/* - * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) - */ - -#ifndef MPD_OGG_CODEC_HXX -#define MPD_OGG_CODEC_HXX - -#include "DecoderAPI.hxx" - -enum ogg_codec { - OGG_CODEC_UNKNOWN, - OGG_CODEC_VORBIS, - OGG_CODEC_FLAC, - OGG_CODEC_OPUS, -}; - -enum ogg_codec -ogg_codec_detect(Decoder *decoder, InputStream &is); - -#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx deleted file mode 100644 index 65c7fa3ce..000000000 --- a/src/decoder/OggFind.cxx +++ /dev/null @@ -1,68 +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 "OggFind.hxx" -#include "OggSyncState.hxx" -#include "util/Error.hxx" - -#include <stdio.h> - -bool -OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) -{ - while (true) { - int r = ogg_stream_packetout(&os, &packet); - if (r == 0) { - if (!oy.ExpectPageIn(os)) - return false; - - continue; - } else if (r > 0 && packet.e_o_s) - return true; - } -} - -bool -OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence) -{ - oy.Reset(); - - /* reset the stream to clear any previous partial packet - data */ - ogg_stream_reset(&os); - - return is.LockSeek(offset, whence, IgnoreError()) && - oy.ExpectPageSeekIn(os); -} - -bool -OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, - InputStream &is) -{ - if (is.size > 0 && is.size - is.offset < 65536) - return OggFindEOS(oy, os, packet); - - if (!is.CheapSeeking()) - return false; - - return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) && - OggFindEOS(oy, os, packet); -} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx deleted file mode 100644 index ad51ccdf3..000000000 --- a/src/decoder/OggFind.hxx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OGG_FIND_HXX -#define MPD_OGG_FIND_HXX - -#include "check.h" -#include "InputStream.hxx" - -#include <ogg/ogg.h> - -struct InputStream; -class OggSyncState; - -/** - * Skip all pages/packets until an end-of-stream (EOS) packet for the - * specified stream is found. - * - * @return true if the EOS packet was found - */ -bool -OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); - -/** - * Seek the #InputStream and find the next Ogg page. - */ -bool -OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence); - -/** - * Try to find the end-of-stream (EOS) packet. Seek to the end of the - * file if necessary. - * - * @return true if the EOS packet was found - */ -bool -OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, - InputStream &is); - -#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx deleted file mode 100644 index 5235c1bd8..000000000 --- a/src/decoder/OggSyncState.hxx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OGG_SYNC_STATE_HXX -#define MPD_OGG_SYNC_STATE_HXX - -#include "check.h" -#include "OggUtil.hxx" - -#include <ogg/ogg.h> - -#include <stddef.h> - -/** - * Wrapper for an ogg_sync_state. - */ -class OggSyncState { - ogg_sync_state oy; - - InputStream &is; - Decoder *const decoder; - -public: - OggSyncState(InputStream &_is, Decoder *const _decoder=nullptr) - :is(_is), decoder(_decoder) { - ogg_sync_init(&oy); - } - - ~OggSyncState() { - ogg_sync_clear(&oy); - } - - void Reset() { - ogg_sync_reset(&oy); - } - - bool Feed(size_t size) { - return OggFeed(oy, decoder, is, size); - } - - bool ExpectPage(ogg_page &page) { - return OggExpectPage(oy, page, decoder, is); - } - - bool ExpectFirstPage(ogg_stream_state &os) { - return OggExpectFirstPage(oy, os, decoder, is); - } - - bool ExpectPageIn(ogg_stream_state &os) { - return OggExpectPageIn(oy, os, decoder, is); - } - - bool ExpectPageSeek(ogg_page &page) { - return OggExpectPageSeek(oy, page, decoder, is); - } - - bool ExpectPageSeekIn(ogg_stream_state &os) { - return OggExpectPageSeekIn(oy, os, decoder, is); - } -}; - -#endif diff --git a/src/decoder/OggUtil.cxx b/src/decoder/OggUtil.cxx deleted file mode 100644 index 8f181ce57..000000000 --- a/src/decoder/OggUtil.cxx +++ /dev/null @@ -1,118 +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 "OggUtil.hxx" -#include "DecoderAPI.hxx" - -bool -OggFeed(ogg_sync_state &oy, Decoder *decoder, - InputStream &input_stream, size_t size) -{ - char *buffer = ogg_sync_buffer(&oy, size); - if (buffer == nullptr) - return false; - - size_t nbytes = decoder_read(decoder, input_stream, - buffer, size); - if (nbytes == 0) - return false; - - ogg_sync_wrote(&oy, nbytes); - return true; -} - -bool -OggExpectPage(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &input_stream) -{ - while (true) { - int r = ogg_sync_pageout(&oy, &page); - if (r != 0) - return r > 0; - - if (!OggFeed(oy, decoder, input_stream, 1024)) - return false; - } -} - -bool -OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is) -{ - ogg_page page; - if (!OggExpectPage(oy, page, decoder, is)) - return false; - - ogg_stream_init(&os, ogg_page_serialno(&page)); - ogg_stream_pagein(&os, &page); - return true; -} - -bool -OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is) -{ - ogg_page page; - if (!OggExpectPage(oy, page, decoder, is)) - return false; - - ogg_stream_pagein(&os, &page); - return true; -} - -bool -OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &input_stream) -{ - size_t remaining_skipped = 32768; - - while (true) { - int r = ogg_sync_pageseek(&oy, &page); - if (r > 0) - return true; - - if (r < 0) { - /* skipped -r bytes */ - size_t nbytes = -r; - if (nbytes > remaining_skipped) - /* still no ogg page - we lost our - patience, abort */ - return false; - - remaining_skipped -= nbytes; - continue; - } - - if (!OggFeed(oy, decoder, input_stream, 1024)) - return false; - } -} - -bool -OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is) -{ - ogg_page page; - if (!OggExpectPageSeek(oy, page, decoder, is)) - return false; - - ogg_stream_pagein(&os, &page); - return true; -} diff --git a/src/decoder/OggUtil.hxx b/src/decoder/OggUtil.hxx deleted file mode 100644 index 41fc755ba..000000000 --- a/src/decoder/OggUtil.hxx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OGG_UTIL_HXX -#define MPD_OGG_UTIL_HXX - -#include "check.h" - -#include <ogg/ogg.h> - -#include <stddef.h> - -struct InputStream; -struct Decoder; - -/** - * Feed data from the #InputStream into the #ogg_sync_state. - * - * @return false on error or end-of-file - */ -bool -OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &is, - size_t size); - -/** - * Feed into the #ogg_sync_state until a page gets available. Garbage - * data at the beginning is considered a fatal error. - * - * @return true if a page is available - */ -bool -OggExpectPage(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &is); - -/** - * Combines OggExpectPage(), ogg_stream_init() and - * ogg_stream_pagein(). - * - * @return true if the stream was initialized and the first page was - * delivered to it - */ -bool -OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is); - -/** - * Combines OggExpectPage() and ogg_stream_pagein(). - * - * @return true if a page was delivered to the stream - */ -bool -OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is); - -/** - * Like OggExpectPage(), but allow skipping garbage (after seeking). - */ -bool -OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &is); - -/** - * Combines OggExpectPageSeek() and ogg_stream_pagein(). - * - * @return true if a page was delivered to the stream - */ -bool -OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, - Decoder *decoder, InputStream &is); - -#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx deleted file mode 100644 index 01ea3687a..000000000 --- a/src/decoder/OpusDecoderPlugin.cxx +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "OpusDecoderPlugin.h" -#include "OpusDomain.hxx" -#include "OpusHead.hxx" -#include "OpusTags.hxx" -#include "OggUtil.hxx" -#include "OggFind.hxx" -#include "OggSyncState.hxx" -#include "DecoderAPI.hxx" -#include "OggCodec.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagBuilder.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <opus.h> -#include <ogg/ogg.h> - -#include <glib.h> - -#include <string.h> -#include <stdio.h> - -static constexpr opus_int32 opus_sample_rate = 48000; - -gcc_pure -static bool -IsOpusHead(const ogg_packet &packet) -{ - return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; -} - -gcc_pure -static bool -IsOpusTags(const ogg_packet &packet) -{ - return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; -} - -static bool -mpd_opus_init(gcc_unused const config_param ¶m) -{ - LogDebug(opus_domain, opus_get_version_string()); - - return true; -} - -class MPDOpusDecoder { - Decoder &decoder; - InputStream &input_stream; - - ogg_stream_state os; - - OpusDecoder *opus_decoder; - opus_int16 *output_buffer; - unsigned output_size; - - bool os_initialized; - bool found_opus; - - int opus_serialno; - - ogg_int64_t eos_granulepos; - - size_t frame_size; - -public: - MPDOpusDecoder(Decoder &_decoder, - InputStream &_input_stream) - :decoder(_decoder), input_stream(_input_stream), - opus_decoder(nullptr), - output_buffer(nullptr), output_size(0), - os_initialized(false), found_opus(false) {} - ~MPDOpusDecoder(); - - bool ReadFirstPage(OggSyncState &oy); - bool ReadNextPage(OggSyncState &oy); - - DecoderCommand HandlePackets(); - DecoderCommand HandlePacket(const ogg_packet &packet); - DecoderCommand HandleBOS(const ogg_packet &packet); - DecoderCommand HandleTags(const ogg_packet &packet); - DecoderCommand HandleAudio(const ogg_packet &packet); - - bool Seek(OggSyncState &oy, double where); -}; - -MPDOpusDecoder::~MPDOpusDecoder() -{ - g_free(output_buffer); - - if (opus_decoder != nullptr) - opus_decoder_destroy(opus_decoder); - - if (os_initialized) - ogg_stream_clear(&os); -} - -inline bool -MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) -{ - assert(!os_initialized); - - if (!oy.ExpectFirstPage(os)) - return false; - - os_initialized = true; - return true; -} - -inline bool -MPDOpusDecoder::ReadNextPage(OggSyncState &oy) -{ - assert(os_initialized); - - ogg_page page; - if (!oy.ExpectPage(page)) - return false; - - const auto page_serialno = ogg_page_serialno(&page); - if (page_serialno != os.serialno) - ogg_stream_reset_serialno(&os, page_serialno); - - ogg_stream_pagein(&os, &page); - return true; -} - -inline DecoderCommand -MPDOpusDecoder::HandlePackets() -{ - ogg_packet packet; - while (ogg_stream_packetout(&os, &packet) == 1) { - auto cmd = HandlePacket(packet); - if (cmd != DecoderCommand::NONE) - return cmd; - } - - return DecoderCommand::NONE; -} - -inline DecoderCommand -MPDOpusDecoder::HandlePacket(const ogg_packet &packet) -{ - if (packet.e_o_s) - return DecoderCommand::STOP; - - if (packet.b_o_s) - return HandleBOS(packet); - else if (!found_opus) - return DecoderCommand::STOP; - - if (IsOpusTags(packet)) - return HandleTags(packet); - - return HandleAudio(packet); -} - -/** - * Load the end-of-stream packet and restore the previous file - * position. - */ -static bool -LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, - ogg_packet &packet) -{ - if (!is.CheapSeeking()) - /* we do this for local files only, because seeking - around remote files is expensive and not worth the - troubl */ - return -1; - - const auto old_offset = is.offset; - if (old_offset < 0) - return -1; - - /* create temporary Ogg objects for seeking and parsing the - EOS packet */ - OggSyncState oy(is, decoder); - ogg_stream_state os; - ogg_stream_init(&os, serialno); - - bool result = OggSeekFindEOS(oy, os, packet, is); - ogg_stream_clear(&os); - - /* restore the previous file position */ - is.Seek(old_offset, SEEK_SET, IgnoreError()); - - return result; -} - -/** - * Load the end-of-stream granulepos and restore the previous file - * position. - * - * @return -1 on error - */ -gcc_pure -static ogg_int64_t -LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) -{ - ogg_packet packet; - if (!LoadEOSPacket(is, decoder, serialno, packet)) - return -1; - - return packet.granulepos; -} - -inline DecoderCommand -MPDOpusDecoder::HandleBOS(const ogg_packet &packet) -{ - assert(packet.b_o_s); - - if (found_opus || !IsOpusHead(packet)) - return DecoderCommand::STOP; - - unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || - !audio_valid_channel_count(channels)) - return DecoderCommand::STOP; - - assert(opus_decoder == nullptr); - assert(output_buffer == nullptr); - - opus_serialno = os.serialno; - found_opus = true; - - /* TODO: parse attributes from the OpusHead (sample rate, - channels, ...) */ - - int opus_error; - opus_decoder = opus_decoder_create(opus_sample_rate, channels, - &opus_error); - if (opus_decoder == nullptr) { - FormatError(opus_domain, "libopus error: %s", - opus_strerror(opus_error)); - return DecoderCommand::STOP; - } - - eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, - opus_serialno); - const double duration = eos_granulepos >= 0 - ? double(eos_granulepos) / opus_sample_rate - : -1.0; - - const AudioFormat audio_format(opus_sample_rate, - SampleFormat::S16, channels); - decoder_initialized(decoder, audio_format, - eos_granulepos > 0, duration); - frame_size = audio_format.GetFrameSize(); - - /* allocate an output buffer for 16 bit PCM samples big enough - to hold a quarter second, larger than 120ms required by - libopus */ - output_size = audio_format.sample_rate / 4; - output_buffer = (opus_int16 *) - g_malloc(sizeof(*output_buffer) * output_size * - audio_format.channels); - - return decoder_get_command(decoder); -} - -inline DecoderCommand -MPDOpusDecoder::HandleTags(const ogg_packet &packet) -{ - ReplayGainInfo rgi; - rgi.Clear(); - - TagBuilder tag_builder; - - DecoderCommand cmd; - if (ScanOpusTags(packet.packet, packet.bytes, - &rgi, - &add_tag_handler, &tag_builder) && - !tag_builder.IsEmpty()) { - decoder_replay_gain(decoder, &rgi); - - Tag tag; - tag_builder.Commit(tag); - cmd = decoder_tag(decoder, input_stream, std::move(tag)); - } else - cmd = decoder_get_command(decoder); - - return cmd; -} - -inline DecoderCommand -MPDOpusDecoder::HandleAudio(const ogg_packet &packet) -{ - assert(opus_decoder != nullptr); - - int nframes = opus_decode(opus_decoder, - (const unsigned char*)packet.packet, - packet.bytes, - output_buffer, output_size, - 0); - if (nframes < 0) { - LogError(opus_domain, opus_strerror(nframes)); - return DecoderCommand::STOP; - } - - if (nframes > 0) { - const size_t nbytes = nframes * frame_size; - auto cmd = decoder_data(decoder, input_stream, - output_buffer, nbytes, - 0); - if (cmd != DecoderCommand::NONE) - return cmd; - - if (packet.granulepos > 0) - decoder_timestamp(decoder, - double(packet.granulepos) - / opus_sample_rate); - } - - return DecoderCommand::NONE; -} - -bool -MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) -{ - assert(eos_granulepos > 0); - assert(input_stream.seekable); - assert(input_stream.size > 0); - assert(input_stream.offset >= 0); - - const ogg_int64_t where_granulepos(where_s * opus_sample_rate); - - /* interpolate the file offset where we expect to find the - given granule position */ - /* TODO: implement binary search */ - InputStream::offset_type offset(where_granulepos * input_stream.size - / eos_granulepos); - - if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET)) - return false; - - decoder_timestamp(decoder, where_s); - return true; -} - -static void -mpd_opus_stream_decode(Decoder &decoder, - InputStream &input_stream) -{ - if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_OPUS) - return; - - /* rewind the stream, because ogg_codec_detect() has - moved it */ - input_stream.LockRewind(IgnoreError()); - - MPDOpusDecoder d(decoder, input_stream); - OggSyncState oy(input_stream, &decoder); - - if (!d.ReadFirstPage(oy)) - return; - - while (true) { - auto cmd = d.HandlePackets(); - if (cmd == DecoderCommand::SEEK) { - if (d.Seek(oy, decoder_seek_where(decoder))) - decoder_command_finished(decoder); - else - decoder_seek_error(decoder); - - continue; - } - - if (cmd != DecoderCommand::NONE) - break; - - if (!d.ReadNextPage(oy)) - break; - } -} - -static bool -mpd_opus_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - OggSyncState oy(is); - - ogg_stream_state os; - if (!oy.ExpectFirstPage(os)) - return false; - - /* read at most two more pages */ - unsigned remaining_pages = 2; - - bool result = false; - - ogg_packet packet; - while (true) { - int r = ogg_stream_packetout(&os, &packet); - if (r < 0) { - result = false; - break; - } - - if (r == 0) { - if (remaining_pages-- == 0) - break; - - if (!oy.ExpectPageIn(os)) { - result = false; - break; - } - - continue; - } - - if (packet.b_o_s) { - if (!IsOpusHead(packet)) - break; - - unsigned channels; - if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || - !audio_valid_channel_count(channels)) { - result = false; - break; - } - - result = true; - } else if (!result) - break; - else if (IsOpusTags(packet)) { - if (!ScanOpusTags(packet.packet, packet.bytes, - nullptr, - handler, handler_ctx)) - result = false; - - break; - } - } - - if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) - tag_handler_invoke_duration(handler, handler_ctx, - packet.granulepos / opus_sample_rate); - - ogg_stream_clear(&os); - - return result; -} - -static const char *const opus_suffixes[] = { - "opus", - "ogg", - "oga", - nullptr -}; - -static const char *const opus_mime_types[] = { - "audio/opus", - nullptr -}; - -const struct DecoderPlugin opus_decoder_plugin = { - "opus", - mpd_opus_init, - nullptr, - mpd_opus_stream_decode, - nullptr, - nullptr, - mpd_opus_scan_stream, - nullptr, - opus_suffixes, - opus_mime_types, -}; diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/OpusDecoderPlugin.h deleted file mode 100644 index 263ac6e2d..000000000 --- a/src/decoder/OpusDecoderPlugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 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_DECODER_OPUS_H -#define MPD_DECODER_OPUS_H - -extern const struct DecoderPlugin opus_decoder_plugin; - -#endif diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/OpusDomain.cxx deleted file mode 100644 index b00e2a553..000000000 --- a/src/decoder/OpusDomain.cxx +++ /dev/null @@ -1,24 +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 "OpusDomain.hxx" -#include "util/Domain.hxx" - -const Domain opus_domain("opus"); diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/OpusDomain.hxx deleted file mode 100644 index 2b56c427c..000000000 --- a/src/decoder/OpusDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OPUS_DOMAIN_HXX -#define MPD_OPUS_DOMAIN_HXX - -#include "check.h" - -extern const class Domain opus_domain; - -#endif diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx deleted file mode 100644 index 0417d3905..000000000 --- a/src/decoder/OpusHead.cxx +++ /dev/null @@ -1,44 +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 "OpusHead.hxx" - -#include <stdint.h> -#include <string.h> - -struct OpusHead { - char signature[8]; - uint8_t version, channels; - uint16_t pre_skip; - uint32_t sample_rate; - uint16_t output_gain; - uint8_t channel_mapping; -}; - -bool -ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) -{ - const OpusHead *h = (const OpusHead *)data; - if (size < 19 || (h->version & 0xf0) != 0) - return false; - - channels_r = h->channels; - return true; -} diff --git a/src/decoder/OpusHead.hxx b/src/decoder/OpusHead.hxx deleted file mode 100644 index fa6a2b666..000000000 --- a/src/decoder/OpusHead.hxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OPUS_HEAD_HXX -#define MPD_OPUS_HEAD_HXX - -#include "check.h" - -#include <stddef.h> - -bool -ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); - -#endif diff --git a/src/decoder/OpusReader.hxx b/src/decoder/OpusReader.hxx deleted file mode 100644 index 2bb39b748..000000000 --- a/src/decoder/OpusReader.hxx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OPUS_READER_HXX -#define MPD_OPUS_READER_HXX - -#include "check.h" - -#include <algorithm> - -#include <stdint.h> -#include <string.h> - -class OpusReader { - const uint8_t *p, *const end; - -public: - OpusReader(const void *_p, size_t size) - :p((const uint8_t *)_p), end(p + size) {} - - bool Skip(size_t length) { - p += length; - return p <= end; - } - - const void *Read(size_t length) { - const uint8_t *result = p; - return Skip(length) - ? result - : nullptr; - } - - bool Expect(const void *value, size_t length) { - const void *data = Read(length); - return data != nullptr && memcmp(value, data, length) == 0; - } - - bool ReadByte(uint8_t &value_r) { - if (p >= end) - return false; - - value_r = *p++; - return true; - } - - bool ReadShort(uint16_t &value_r) { - const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); - if (value == nullptr) - return false; - - value_r = value[0] | (value[1] << 8); - return true; - } - - bool ReadWord(uint32_t &value_r) { - const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); - if (value == nullptr) - return false; - - value_r = value[0] | (value[1] << 8) - | (value[2] << 16) | (value[3] << 24); - return true; - } - - bool SkipString() { - uint32_t length; - return ReadWord(length) && Skip(length); - } - - char *ReadString() { - uint32_t length; - if (!ReadWord(length)) - return nullptr; - - const char *src = (const char *)Read(length); - if (src == nullptr) - return nullptr; - - char *dest = new char[length + 1]; - *std::copy_n(src, length, dest) = 0; - return dest; - } -}; - -#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx deleted file mode 100644 index f7729e5ad..000000000 --- a/src/decoder/OpusTags.cxx +++ /dev/null @@ -1,102 +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 "OpusTags.hxx" -#include "OpusReader.hxx" -#include "XiphTags.hxx" -#include "tag/TagHandler.hxx" -#include "tag/Tag.hxx" -#include "ReplayGainInfo.hxx" - -#include <stdint.h> -#include <string.h> -#include <stdlib.h> - -gcc_pure -static TagType -ParseOpusTagName(const char *name) -{ - TagType type = tag_name_parse_i(name); - if (type != TAG_NUM_OF_ITEM_TYPES) - return type; - - return tag_table_lookup_i(xiph_tags, name); -} - -static void -ScanOneOpusTag(const char *name, const char *value, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx) -{ - if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { - /* R128_TRACK_GAIN is a Q7.8 fixed point number in - dB */ - - char *endptr; - long l = strtol(value, &endptr, 10); - if (endptr > value && *endptr == 0) - rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.; - } - - tag_handler_invoke_pair(handler, ctx, name, value); - - if (handler->tag != nullptr) { - TagType t = ParseOpusTagName(name); - if (t != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, ctx, t, value); - } -} - -bool -ScanOpusTags(const void *data, size_t size, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx) -{ - OpusReader r(data, size); - if (!r.Expect("OpusTags", 8)) - return false; - - if (handler->pair == nullptr && handler->tag == nullptr) - return true; - - if (!r.SkipString()) - return false; - - uint32_t n; - if (!r.ReadWord(n)) - return false; - - while (n-- > 0) { - char *p = r.ReadString(); - if (p == nullptr) - return false; - - char *eq = strchr(p, '='); - if (eq != nullptr && eq > p) { - *eq = 0; - - ScanOneOpusTag(p, eq + 1, rgi, handler, ctx); - } - - delete[] p; - } - - return true; -} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx deleted file mode 100644 index e1f1a1ff1..000000000 --- a/src/decoder/OpusTags.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OPUS_TAGS_HXX -#define MPD_OPUS_TAGS_HXX - -#include "check.h" - -#include <stddef.h> - -struct ReplayGainInfo; - -bool -ScanOpusTags(const void *data, size_t size, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx); - -#endif diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx deleted file mode 100644 index dbc38fb76..000000000 --- a/src/decoder/PcmDecoderPlugin.cxx +++ /dev/null @@ -1,114 +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 "decoder/PcmDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "util/Error.hxx" -#include "util/ByteReverse.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <unistd.h> -#include <string.h> -#include <stdio.h> /* for SEEK_SET */ - -static void -pcm_stream_decode(Decoder &decoder, InputStream &is) -{ - static constexpr AudioFormat audio_format = { - 44100, - SampleFormat::S16, - 2, - }; - - const char *const mime = is.GetMimeType(); - const bool reverse_endian = mime != nullptr && - strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; - - const double time_to_size = audio_format.GetTimeToSize(); - - float total_time = -1; - const auto size = is.GetSize(); - if (size >= 0) - total_time = size / time_to_size; - - decoder_initialized(decoder, audio_format, - is.IsSeekable(), total_time); - - DecoderCommand cmd; - do { - char buffer[4096]; - - size_t nbytes = decoder_read(decoder, is, - buffer, sizeof(buffer)); - - if (nbytes == 0 && is.LockIsEOF()) - break; - - if (reverse_endian) - /* make sure we deliver samples in host byte order */ - reverse_bytes_16((uint16_t *)buffer, - (uint16_t *)buffer, - (uint16_t *)(buffer + nbytes)); - - cmd = nbytes > 0 - ? decoder_data(decoder, is, - buffer, nbytes, 0) - : decoder_get_command(decoder); - if (cmd == DecoderCommand::SEEK) { - InputStream::offset_type offset(time_to_size * - decoder_seek_where(decoder)); - - Error error; - if (is.LockSeek(offset, SEEK_SET, error)) { - decoder_command_finished(decoder); - } else { - LogError(error); - decoder_seek_error(decoder); - } - - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); -} - -static const char *const pcm_mime_types[] = { - /* for streams obtained by the cdio_paranoia input plugin */ - "audio/x-mpd-cdda-pcm", - - /* same as above, but with reverse byte order */ - "audio/x-mpd-cdda-pcm-reverse", - - nullptr -}; - -const struct DecoderPlugin pcm_decoder_plugin = { - "pcm", - nullptr, - nullptr, - pcm_stream_decode, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - pcm_mime_types, -}; diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/PcmDecoderPlugin.hxx deleted file mode 100644 index 38e4a5020..000000000 --- a/src/decoder/PcmDecoderPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Not really a decoder; this plugin forwards its input data "as-is". - * - * It was written only to support the "cdio_paranoia" input plugin, - * which does not need a decoder. - */ - -#ifndef MPD_DECODER_PCM_HXX -#define MPD_DECODER_PCM_HXX - -extern const struct DecoderPlugin pcm_decoder_plugin; - -#endif diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/SidplayDecoderPlugin.cxx deleted file mode 100644 index 160337594..000000000 --- a/src/decoder/SidplayDecoderPlugin.cxx +++ /dev/null @@ -1,435 +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 "SidplayDecoderPlugin.hxx" -#include "../DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.hxx" -#include "Log.hxx" - -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <glib.h> - -#include <sidplay/sidplay2.h> -#include <sidplay/builders/resid.h> -#include <sidplay/utils/SidTuneMod.h> - -#define SUBTUNE_PREFIX "tune_" - -static constexpr Domain sidplay_domain("sidplay"); - -static GPatternSpec *path_with_subtune; -static const char *songlength_file; -static GKeyFile *songlength_database; - -static bool all_files_are_containers; -static unsigned default_songlength; - -static bool filter_setting; - -static GKeyFile * -sidplay_load_songlength_db(const char *path) -{ - GError *error = nullptr; - gchar *data; - gsize size; - - if (!g_file_get_contents(path, &data, &size, &error)) { - FormatError(sidplay_domain, - "unable to read songlengths file %s: %s", - path, error->message); - g_error_free(error); - return nullptr; - } - - /* replace any ; comment characters with # */ - for (gsize i = 0; i < size; i++) - if (data[i] == ';') - data[i] = '#'; - - GKeyFile *db = g_key_file_new(); - bool success = g_key_file_load_from_data(db, data, size, - G_KEY_FILE_NONE, &error); - g_free(data); - if (!success) { - FormatError(sidplay_domain, - "unable to parse songlengths file %s: %s", - path, error->message); - g_error_free(error); - g_key_file_free(db); - return nullptr; - } - - g_key_file_set_list_separator(db, ' '); - return db; -} - -static bool -sidplay_init(const config_param ¶m) -{ - /* read the songlengths database file */ - songlength_file = param.GetBlockValue("songlength_database"); - if (songlength_file != nullptr) - songlength_database = sidplay_load_songlength_db(songlength_file); - - default_songlength = param.GetBlockValue("default_songlength", 0u); - - all_files_are_containers = - param.GetBlockValue("all_files_are_containers", true); - - path_with_subtune=g_pattern_spec_new( - "*/" SUBTUNE_PREFIX "???.sid"); - - filter_setting = param.GetBlockValue("filter", true); - - return true; -} - -static void -sidplay_finish() -{ - g_pattern_spec_free(path_with_subtune); - - if(songlength_database) - g_key_file_free(songlength_database); -} - -/** - * returns the file path stripped of any /tune_xxx.sid subtune - * suffix - */ -static char * -get_container_name(const char *path_fs) -{ - char *path_container=g_strdup(path_fs); - - if(!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, nullptr)) - return path_container; - - char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if(ptr) *ptr='\0'; - - return path_container; -} - -/** - * returns tune number from file.sid/tune_xxx.sid style path or 1 if - * no subtune is appended - */ -static unsigned -get_song_num(const char *path_fs) -{ - if(g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - if(!sub) return 1; - - sub+=strlen("/" SUBTUNE_PREFIX); - int song_num=strtol(sub, nullptr, 10); - - if (errno == EINVAL) - return 1; - else - return song_num; - } else - return 1; -} - -/* get the song length in seconds */ -static int -get_song_length(const char *path_fs) -{ - if (songlength_database == nullptr) - return -1; - - gchar *sid_file=get_container_name(path_fs); - SidTuneMod tune(sid_file); - g_free(sid_file); - if(!tune) { - LogWarning(sidplay_domain, - "failed to load file for calculating md5 sum"); - return -1; - } - char md5sum[SIDTUNE_MD5_LENGTH+1]; - tune.createMD5(md5sum); - - const unsigned song_num = get_song_num(path_fs); - - gsize num_items; - gchar **values=g_key_file_get_string_list(songlength_database, - "Database", md5sum, &num_items, nullptr); - if(!values || song_num>num_items) { - g_strfreev(values); - return -1; - } - - int minutes=strtol(values[song_num-1], nullptr, 10); - if(errno==EINVAL) minutes=0; - - int seconds; - char *ptr=strchr(values[song_num-1], ':'); - if(ptr) { - seconds=strtol(ptr+1, nullptr, 10); - if(errno==EINVAL) seconds=0; - } else - seconds=0; - - g_strfreev(values); - - return (minutes*60)+seconds; -} - -static void -sidplay_file_decode(Decoder &decoder, const char *path_fs) -{ - int channels; - - /* load the tune */ - - char *path_container=get_container_name(path_fs); - SidTune tune(path_container, nullptr, true); - g_free(path_container); - if (!tune) { - LogWarning(sidplay_domain, "failed to load file"); - return; - } - - int song_num=get_song_num(path_fs); - tune.selectSong(song_num); - - int song_len=get_song_length(path_fs); - if(song_len==-1) song_len=default_songlength; - - /* initialize the player */ - - sidplay2 player; - int iret = player.load(&tune); - if (iret != 0) { - FormatWarning(sidplay_domain, - "sidplay2.load() failed: %s", player.error()); - return; - } - - /* initialize the builder */ - - ReSIDBuilder builder("ReSID"); - if (!builder) { - LogWarning(sidplay_domain, - "failed to initialize ReSIDBuilder"); - return; - } - - builder.create(player.info().maxsids); - if (!builder) { - LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); - return; - } - - builder.filter(filter_setting); - if (!builder) { - LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed"); - return; - } - - /* configure the player */ - - sid2_config_t config = player.config(); - - config.clockDefault = SID2_CLOCK_PAL; - config.clockForced = true; - config.clockSpeed = SID2_CLOCK_CORRECT; - config.frequency = 48000; - config.optimisation = SID2_DEFAULT_OPTIMISATION; - - config.precision = 16; - config.sidDefault = SID2_MOS6581; - config.sidEmulation = &builder; - config.sidModel = SID2_MODEL_CORRECT; - config.sidSamples = true; - config.sampleFormat = IsLittleEndian() - ? SID2_LITTLE_SIGNED - : SID2_BIG_SIGNED; - if (tune.isStereo()) { - config.playback = sid2_stereo; - channels = 2; - } else { - config.playback = sid2_mono; - channels = 1; - } - - iret = player.config(config); - if (iret != 0) { - FormatWarning(sidplay_domain, - "sidplay2.config() failed: %s", player.error()); - return; - } - - /* initialize the MPD decoder */ - - const AudioFormat audio_format(48000, SampleFormat::S16, channels); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, true, (float)song_len); - - /* .. and play */ - - const unsigned timebase = player.timebase(); - song_len *= timebase; - - DecoderCommand cmd; - do { - char buffer[4096]; - size_t nbytes; - - nbytes = player.play(buffer, sizeof(buffer)); - if (nbytes == 0) - break; - - decoder_timestamp(decoder, (double)player.time() / timebase); - - cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0); - - if (cmd == DecoderCommand::SEEK) { - unsigned data_time = player.time(); - unsigned target_time = (unsigned) - (decoder_seek_where(decoder) * timebase); - - /* can't rewind so return to zero and seek forward */ - if(target_time<data_time) { - player.stop(); - data_time=0; - } - - /* ignore data until target time is reached */ - while(data_time<target_time) { - nbytes=player.play(buffer, sizeof(buffer)); - if(nbytes==0) - break; - data_time = player.time(); - } - - decoder_command_finished(decoder); - } - - if (song_len > 0 && player.time() >= (unsigned)song_len) - break; - - } while (cmd != DecoderCommand::STOP); -} - -static bool -sidplay_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - int song_num=get_song_num(path_fs); - char *path_container=get_container_name(path_fs); - - SidTune tune(path_container, nullptr, true); - g_free(path_container); - if (!tune) - return false; - - const SidTuneInfo &info = tune.getInfo(); - - /* title */ - const char *title; - if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) - title=info.infoString[0]; - else - title=""; - - if(info.songs>1) { - char tag_title[1024]; - snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - title, song_num, info.songs); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, tag_title); - } else - tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); - - /* artist */ - if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, - info.infoString[1]); - - /* track */ - char track[16]; - sprintf(track, "%d", song_num); - tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); - - /* time */ - int song_len=get_song_length(path_fs); - if (song_len >= 0) - tag_handler_invoke_duration(handler, handler_ctx, song_len); - - return true; -} - -static char * -sidplay_container_scan(const char *path_fs, const unsigned int tnum) -{ - SidTune tune(path_fs, nullptr, true); - if (!tune) - return nullptr; - - const SidTuneInfo &info=tune.getInfo(); - - /* Don't treat sids containing a single tune - as containers */ - if(!all_files_are_containers && info.songs<2) - return nullptr; - - /* Construct container/tune path names, eg. - Delta.sid/tune_001.sid */ - if(tnum<=info.songs) { - char *subtune= g_strdup_printf( - SUBTUNE_PREFIX "%03u.sid", tnum); - return subtune; - } else - return nullptr; -} - -static const char *const sidplay_suffixes[] = { - "sid", - "mus", - "str", - "prg", - "P00", - nullptr -}; - -extern const struct DecoderPlugin sidplay_decoder_plugin; -const struct DecoderPlugin sidplay_decoder_plugin = { - "sidplay", - sidplay_init, - sidplay_finish, - nullptr, /* stream_decode() */ - sidplay_file_decode, - sidplay_scan_file, - nullptr, /* stream_tag() */ - sidplay_container_scan, - sidplay_suffixes, - nullptr, /* mime_types */ -}; diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/SidplayDecoderPlugin.hxx deleted file mode 100644 index 16544801f..000000000 --- a/src/decoder/SidplayDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_SIDPLAY_HXX -#define MPD_DECODER_SIDPLAY_HXX - -extern const struct DecoderPlugin sidplay_decoder_plugin; - -#endif diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx deleted file mode 100644 index bcdf6d7ca..000000000 --- a/src/decoder/SndfileDecoderPlugin.cxx +++ /dev/null @@ -1,273 +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 "SndfileDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <sndfile.h> - -static constexpr Domain sndfile_domain("sndfile"); - -struct SndfileInputStream { - Decoder *const decoder; - InputStream &is; - - size_t Read(void *buffer, size_t size) { - /* libsndfile chokes on partial reads; therefore - always force full reads */ - return decoder_read_full(decoder, is, buffer, size) - ? size - : 0; - } -}; - -static sf_count_t -sndfile_vio_get_filelen(void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - const InputStream &is = sis.is; - - return is.GetSize(); -} - -static sf_count_t -sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - InputStream &is = sis.is; - - Error error; - if (!is.LockSeek(offset, whence, error)) { - LogError(error, "Seek failed"); - return -1; - } - - return is.GetOffset(); -} - -static sf_count_t -sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - - return sis.Read(ptr, count); -} - -static sf_count_t -sndfile_vio_write(gcc_unused const void *ptr, - gcc_unused sf_count_t count, - gcc_unused void *user_data) -{ - /* no writing! */ - return -1; -} - -static sf_count_t -sndfile_vio_tell(void *user_data) -{ - SndfileInputStream &sis = *(SndfileInputStream *)user_data; - const InputStream &is = sis.is; - - return is.GetOffset(); -} - -/** - * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a - * libsndfile stream. - */ -static SF_VIRTUAL_IO vio = { - sndfile_vio_get_filelen, - sndfile_vio_seek, - sndfile_vio_read, - sndfile_vio_write, - sndfile_vio_tell, -}; - -/** - * Converts a frame number to a timestamp (in seconds). - */ -static float -frame_to_time(sf_count_t frame, const AudioFormat *audio_format) -{ - return (float)frame / (float)audio_format->sample_rate; -} - -/** - * Converts a timestamp (in seconds) to a frame number. - */ -static sf_count_t -time_to_frame(float t, const AudioFormat *audio_format) -{ - return (sf_count_t)(t * audio_format->sample_rate); -} - -static void -sndfile_stream_decode(Decoder &decoder, InputStream &is) -{ - SNDFILE *sf; - SF_INFO info; - size_t frame_size; - sf_count_t read_frames, num_frames; - int buffer[4096]; - - info.format = 0; - - SndfileInputStream sis{&decoder, is}; - sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); - if (sf == nullptr) { - LogWarning(sndfile_domain, "sf_open_virtual() failed"); - return; - } - - /* for now, always read 32 bit samples. Later, we could lower - MPD's CPU usage by reading 16 bit samples with - sf_readf_short() on low-quality source files. */ - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, info.samplerate, - SampleFormat::S32, - info.channels, error)) { - LogError(error); - return; - } - - decoder_initialized(decoder, audio_format, info.seekable, - frame_to_time(info.frames, &audio_format)); - - frame_size = audio_format.GetFrameSize(); - read_frames = sizeof(buffer) / frame_size; - - DecoderCommand cmd; - do { - num_frames = sf_readf_int(sf, buffer, read_frames); - if (num_frames <= 0) - break; - - cmd = decoder_data(decoder, is, - buffer, num_frames * frame_size, - 0); - if (cmd == DecoderCommand::SEEK) { - sf_count_t c = - time_to_frame(decoder_seek_where(decoder), - &audio_format); - c = sf_seek(sf, c, SEEK_SET); - if (c < 0) - decoder_seek_error(decoder); - else - decoder_command_finished(decoder); - cmd = DecoderCommand::NONE; - } - } while (cmd == DecoderCommand::NONE); - - sf_close(sf); -} - -static bool -sndfile_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - SNDFILE *sf; - SF_INFO info; - const char *p; - - info.format = 0; - - sf = sf_open(path_fs, SFM_READ, &info); - if (sf == nullptr) - return false; - - if (!audio_valid_sample_rate(info.samplerate)) { - sf_close(sf); - FormatWarning(sndfile_domain, - "Invalid sample rate in %s", path_fs); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - info.frames / info.samplerate); - - p = sf_get_string(sf, SF_STR_TITLE); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, p); - - p = sf_get_string(sf, SF_STR_ARTIST); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, p); - - p = sf_get_string(sf, SF_STR_DATE); - if (p != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, p); - - sf_close(sf); - - return true; -} - -static const char *const sndfile_suffixes[] = { - "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ - "au", "snd", /* Sun / DEC / NeXT */ - "paf", /* Paris Audio File */ - "iff", "svx", /* Commodore Amiga IFF / SVX */ - "sf", /* IRCAM */ - "voc", /* Creative */ - "w64", /* Soundforge */ - "pvf", /* Portable Voice Format */ - "xi", /* Fasttracker */ - "htk", /* HMM Tool Kit */ - "caf", /* Apple */ - "sd2", /* Sound Designer II */ - - /* libsndfile also supports FLAC and Ogg Vorbis, but only by - linking with libFLAC and libvorbis - we can do better, we - have native plugins for these libraries */ - - nullptr -}; - -static const char *const sndfile_mime_types[] = { - "audio/x-wav", - "audio/x-aiff", - - /* what are the MIME types of the other supported formats? */ - - nullptr -}; - -const struct DecoderPlugin sndfile_decoder_plugin = { - "sndfile", - nullptr, - nullptr, - sndfile_stream_decode, - nullptr, - sndfile_scan_file, - nullptr, - nullptr, - sndfile_suffixes, - sndfile_mime_types, -}; diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/SndfileDecoderPlugin.hxx deleted file mode 100644 index f8aa65680..000000000 --- a/src/decoder/SndfileDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_SNDFILE_HXX -#define MPD_DECODER_SNDFILE_HXX - -extern const struct DecoderPlugin sndfile_decoder_plugin; - -#endif diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx deleted file mode 100644 index d4f019b58..000000000 --- a/src/decoder/VorbisComments.cxx +++ /dev/null @@ -1,149 +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 "VorbisComments.hxx" -#include "XiphTags.hxx" -#include "tag/Tag.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagBuilder.hxx" -#include "ReplayGainInfo.hxx" -#include "util/ASCII.hxx" - -#include <glib.h> - -#include <assert.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> - -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (StringEqualsCaseASCII(comment, needle, len) && - comment[len] == '=') - return comment + len + 1; - - return nullptr; -} - -bool -vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) -{ - rgi.Clear(); - - const char *temp; - bool found = false; - - while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } - - comments++; - } - - return found; -} - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -vorbis_copy_comment(const char *comment, - const char *name, TagType tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != nullptr) { - tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); - return true; - } - - return false; -} - -static void -vorbis_scan_comment(const char *comment, - const struct tag_handler *handler, void *handler_ctx) -{ - if (handler->pair != nullptr) { - char *name = g_strdup((const char*)comment); - char *value = strchr(name, '='); - - if (value != nullptr && value > name) { - *value++ = 0; - tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); - } - - for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) - if (vorbis_copy_comment(comment, i->name, i->type, - handler, handler_ctx)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(comment, - tag_item_names[i], TagType(i), - handler, handler_ctx)) - return; -} - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx) -{ - while (*comments) - vorbis_scan_comment(*comments++, - handler, handler_ctx); - -} - -Tag * -vorbis_comments_to_tag(char **comments) -{ - TagBuilder tag_builder; - vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); - return tag_builder.IsEmpty() - ? nullptr - : tag_builder.Commit(); -} diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx deleted file mode 100644 index e5a48ef6b..000000000 --- a/src/decoder/VorbisComments.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VORBIS_COMMENTS_HXX -#define MPD_VORBIS_COMMENTS_HXX - -#include "check.h" - -struct ReplayGainInfo; -struct tag_handler; -struct Tag; - -bool -vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); - -void -vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx); - -Tag * -vorbis_comments_to_tag(char **comments); - -#endif diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx deleted file mode 100644 index 4d3e48528..000000000 --- a/src/decoder/VorbisDecoderPlugin.cxx +++ /dev/null @@ -1,352 +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 "VorbisDecoderPlugin.h" -#include "VorbisComments.hxx" -#include "VorbisDomain.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "OggCodec.hxx" -#include "util/Error.hxx" -#include "util/UriUtil.hxx" -#include "util/Macros.hxx" -#include "system/ByteOrder.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "Log.hxx" - -#ifndef HAVE_TREMOR -#define OV_EXCLUDE_STATIC_CALLBACKS -#include <vorbis/vorbisfile.h> -#else -#include <tremor/ivorbisfile.h> -/* Macros to make Tremor's API look like libogg. Tremor always - returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. -*/ -#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ - ov_read(VF, BUFFER, LENGTH, BITSTREAM) -#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) -#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) -#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) -#endif /* HAVE_TREMOR */ - -#include <assert.h> -#include <errno.h> - -struct vorbis_input_stream { - Decoder *decoder; - - InputStream *input_stream; - bool seekable; -}; - -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) -{ - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - size_t ret = decoder_read(vis->decoder, *vis->input_stream, - ptr, size * nmemb); - - errno = 0; - - return ret / size; -} - -static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) -{ - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - - Error error; - return vis->seekable && - (vis->decoder == nullptr || - decoder_get_command(*vis->decoder) != DecoderCommand::STOP) && - vis->input_stream->LockSeek(offset, whence, error) - ? 0 : -1; -} - -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(gcc_unused void *data) -{ - return 0; -} - -static long ogg_tell_cb(void *data) -{ - struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data; - - return (long)vis->input_stream->offset; -} - -static const ov_callbacks vorbis_is_callbacks = { - ogg_read_cb, - ogg_seek_cb, - ogg_close_cb, - ogg_tell_cb, -}; - -static const char * -vorbis_strerror(int code) -{ - switch (code) { - case OV_EREAD: - return "read error"; - - case OV_ENOTVORBIS: - return "not vorbis stream"; - - case OV_EVERSION: - return "vorbis version mismatch"; - - case OV_EBADHEADER: - return "invalid vorbis header"; - - case OV_EFAULT: - return "internal logic error"; - - default: - return "unknown error"; - } -} - -static bool -vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, - Decoder *decoder, InputStream &input_stream) -{ - vis->decoder = decoder; - vis->input_stream = &input_stream; - vis->seekable = input_stream.CheapSeeking(); - - int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); - if (ret < 0) { - if (decoder == nullptr || - decoder_get_command(*decoder) == DecoderCommand::NONE) - FormatWarning(vorbis_domain, - "Failed to open Ogg Vorbis stream: %s", - vorbis_strerror(ret)); - return false; - } - - return true; -} - -static void -vorbis_send_comments(Decoder &decoder, InputStream &is, - char **comments) -{ - Tag *tag = vorbis_comments_to_tag(comments); - if (!tag) - return; - - decoder_tag(decoder, is, std::move(*tag)); - delete tag; -} - -#ifndef HAVE_TREMOR -static void -vorbis_interleave(float *dest, const float *const*src, - unsigned nframes, unsigned channels) -{ - for (const float *const*src_end = src + channels; - src != src_end; ++src, ++dest) { - float *d = dest; - for (const float *s = *src, *s_end = s + nframes; - s != s_end; ++s, d += channels) - *d = *s; - } -} -#endif - -/* public */ -static void -vorbis_stream_decode(Decoder &decoder, - InputStream &input_stream) -{ - if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS) - return; - - /* rewind the stream, because ogg_codec_detect() has - moved it */ - input_stream.LockRewind(IgnoreError()); - - struct vorbis_input_stream vis; - OggVorbis_File vf; - if (!vorbis_is_open(&vis, &vf, &decoder, input_stream)) - return; - - const vorbis_info *vi = ov_info(&vf, -1); - if (vi == nullptr) { - LogWarning(vorbis_domain, "ov_info() has failed"); - return; - } - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, vi->rate, -#ifdef HAVE_TREMOR - SampleFormat::S16, -#else - SampleFormat::FLOAT, -#endif - vi->channels, error)) { - LogError(error); - return; - } - - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - - decoder_initialized(decoder, audio_format, vis.seekable, total_time); - -#ifdef HAVE_TREMOR - char buffer[4096]; -#else - float buffer[2048]; - const int frames_per_buffer = - ARRAY_SIZE(buffer) / audio_format.channels; - const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; -#endif - - int prev_section = -1; - unsigned kbit_rate = 0; - - DecoderCommand cmd = decoder_get_command(decoder); - do { - if (cmd == DecoderCommand::SEEK) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - - int current_section; - -#ifdef HAVE_TREMOR - long nbytes = ov_read(&vf, buffer, sizeof(buffer), - IsBigEndian(), 2, 1, - ¤t_section); -#else - float **per_channel; - long nframes = ov_read_float(&vf, &per_channel, - frames_per_buffer, - ¤t_section); - long nbytes = nframes; - if (nframes > 0) { - vorbis_interleave(buffer, - (const float*const*)per_channel, - nframes, audio_format.channels); - nbytes *= frame_size; - } -#endif - - if (nbytes == OV_HOLE) /* bad packet */ - nbytes = 0; - else if (nbytes <= 0) - /* break on EOF or other error */ - break; - - if (current_section != prev_section) { - vi = ov_info(&vf, -1); - if (vi == nullptr) { - LogWarning(vorbis_domain, - "ov_info() has failed"); - break; - } - - if (vi->rate != (long)audio_format.sample_rate || - vi->channels != (int)audio_format.channels) { - /* we don't support audio format - change yet */ - LogWarning(vorbis_domain, - "audio format change, stopping here"); - break; - } - - char **comments = ov_comment(&vf, -1)->user_comments; - vorbis_send_comments(decoder, input_stream, comments); - - ReplayGainInfo rgi; - if (vorbis_comments_to_replay_gain(rgi, comments)) - decoder_replay_gain(decoder, &rgi); - - prev_section = current_section; - } - - long test = ov_bitrate_instant(&vf); - if (test > 0) - kbit_rate = test / 1000; - - cmd = decoder_data(decoder, input_stream, - buffer, nbytes, - kbit_rate); - } while (cmd != DecoderCommand::STOP); - - ov_clear(&vf); -} - -static bool -vorbis_scan_stream(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct vorbis_input_stream vis; - OggVorbis_File vf; - - if (!vorbis_is_open(&vis, &vf, nullptr, is)) - return false; - - tag_handler_invoke_duration(handler, handler_ctx, - (int)(ov_time_total(&vf, -1) + 0.5)); - - vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, - handler, handler_ctx); - - ov_clear(&vf); - return true; -} - -static const char *const vorbis_suffixes[] = { - "ogg", "oga", nullptr -}; - -static const char *const vorbis_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/vorbis", - "audio/vorbis+ogg", - "audio/x-ogg", - "audio/x-vorbis", - "audio/x-vorbis+ogg", - nullptr -}; - -const struct DecoderPlugin vorbis_decoder_plugin = { - "vorbis", - nullptr, - nullptr, - vorbis_stream_decode, - nullptr, - nullptr, - vorbis_scan_stream, - nullptr, - vorbis_suffixes, - vorbis_mime_types -}; diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/VorbisDecoderPlugin.h deleted file mode 100644 index 54953d83a..000000000 --- a/src/decoder/VorbisDecoderPlugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2012 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_DECODER_VORBIS_H -#define MPD_DECODER_VORBIS_H - -extern const struct DecoderPlugin vorbis_decoder_plugin; - -#endif diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/VorbisDomain.cxx deleted file mode 100644 index 32ff4d6b7..000000000 --- a/src/decoder/VorbisDomain.cxx +++ /dev/null @@ -1,24 +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 "VorbisDomain.hxx" -#include "util/Domain.hxx" - -const Domain vorbis_domain("vorbis"); diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx deleted file mode 100644 index a35edd041..000000000 --- a/src/decoder/VorbisDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VORBIS_DOMAIN_HXX -#define MPD_VORBIS_DOMAIN_HXX - -#include "check.h" - -extern const class Domain vorbis_domain; - -#endif diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx deleted file mode 100644 index 98555c5e8..000000000 --- a/src/decoder/WavpackDecoderPlugin.cxx +++ /dev/null @@ -1,571 +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 "WavpackDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "InputStream.hxx" -#include "CheckAudioFormat.hxx" -#include "tag/TagHandler.hxx" -#include "tag/ApeTag.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "Log.hxx" - -#include <wavpack/wavpack.h> -#include <glib.h> - -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> - -#define ERRORLEN 80 - -static constexpr Domain wavpack_domain("wavpack"); - -/** A pointer type for format converter function. */ -typedef void (*format_samples_t)( - int bytes_per_sample, - void *buffer, uint32_t count -); - -/* - * This function has been borrowed from the tiny player found on - * wavpack.com. Modifications were required because mpd only handles - * max 24-bit samples. - */ -static void -format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) -{ - int32_t *src = (int32_t *)buffer; - - switch (bytes_per_sample) { - case 1: { - int8_t *dst = (int8_t *)buffer; - /* - * The asserts like the following one are because we do the - * formatting of samples within a single buffer. The size - * of the output samples never can be greater than the size - * of the input ones. Otherwise we would have an overflow. - */ - static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); - - /* pass through and align 8-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - case 2: { - uint16_t *dst = (uint16_t *)buffer; - static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); - - /* pass through and align 16-bit samples */ - while (count--) { - *dst++ = *src++; - } - break; - } - - case 3: - case 4: - /* do nothing */ - break; - } -} - -/* - * This function converts floating point sample data to 24-bit integer. - */ -static void -format_samples_float(gcc_unused int bytes_per_sample, void *buffer, - uint32_t count) -{ - float *p = (float *)buffer; - - while (count--) { - *p /= (1 << 23); - ++p; - } -} - -/** - * Choose a MPD sample format from libwavpacks' number of bits. - */ -static SampleFormat -wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) -{ - if (is_float) - return SampleFormat::FLOAT; - - switch (bytes_per_sample) { - case 1: - return SampleFormat::S8; - - case 2: - return SampleFormat::S16; - - case 3: - return SampleFormat::S24_P32; - - case 4: - return SampleFormat::S32; - - default: - return SampleFormat::UNDEFINED; - } -} - -/* - * This does the main decoding thing. - * Requires an already opened WavpackContext. - */ -static void -wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek) -{ - bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; - SampleFormat sample_format = - wavpack_bits_to_sample_format(is_float, - WavpackGetBytesPerSample(wpc)); - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - WavpackGetSampleRate(wpc), - sample_format, - WavpackGetNumChannels(wpc), error)) { - LogError(error); - return; - } - - const format_samples_t format_samples = is_float - ? format_samples_float - : format_samples_int; - - const float total_time = float(WavpackGetNumSamples(wpc)) - / audio_format.sample_rate; - - const int bytes_per_sample = WavpackGetBytesPerSample(wpc); - const int output_sample_size = audio_format.GetFrameSize(); - - /* wavpack gives us all kind of samples in a 32-bit space */ - int32_t chunk[1024]; - const uint32_t samples_requested = ARRAY_SIZE(chunk) / - audio_format.channels; - - decoder_initialized(decoder, audio_format, can_seek, total_time); - - DecoderCommand cmd = decoder_get_command(decoder); - while (cmd != DecoderCommand::STOP) { - if (cmd == DecoderCommand::SEEK) { - if (can_seek) { - unsigned where = decoder_seek_where(decoder) * - audio_format.sample_rate; - - if (WavpackSeekSample(wpc, where)) { - decoder_command_finished(decoder); - } else { - decoder_seek_error(decoder); - } - } else { - decoder_seek_error(decoder); - } - } - - uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, - samples_requested); - if (samples_got == 0) - break; - - int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + - 0.5); - format_samples(bytes_per_sample, chunk, - samples_got * audio_format.channels); - - cmd = decoder_data(decoder, nullptr, chunk, - samples_got * output_sample_size, - bitrate); - } -} - -/** - * Locate and parse a floating point tag. Returns true if it was - * found. - */ -static bool -wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) -{ - char buffer[64]; - if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0) - return false; - - *value_r = atof(buffer); - return true; -} - -static bool -wavpack_replaygain(ReplayGainInfo &rgi, - WavpackContext *wpc) -{ - rgi.Clear(); - - bool found = false; - found |= wavpack_tag_float(wpc, "replaygain_track_gain", - &rgi.tuples[REPLAY_GAIN_TRACK].gain); - found |= wavpack_tag_float(wpc, "replaygain_track_peak", - &rgi.tuples[REPLAY_GAIN_TRACK].peak); - found |= wavpack_tag_float(wpc, "replaygain_album_gain", - &rgi.tuples[REPLAY_GAIN_ALBUM].gain); - found |= wavpack_tag_float(wpc, "replaygain_album_peak", - &rgi.tuples[REPLAY_GAIN_ALBUM].peak); - - return found; -} - -static void -wavpack_scan_tag_item(WavpackContext *wpc, const char *name, - TagType type, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[1024]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_tag(handler, handler_ctx, type, buffer); - -} - -static void -wavpack_scan_pair(WavpackContext *wpc, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - char buffer[8192]; - int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); - if (len <= 0 || (unsigned)len >= sizeof(buffer)) - return; - - tag_handler_invoke_pair(handler, handler_ctx, name, buffer); -} - -/* - * Reads metainfo from the specified file. - */ -static bool -wavpack_scan_file(const char *fname, - const struct tag_handler *handler, void *handler_ctx) -{ - char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == nullptr) { - FormatError(wavpack_domain, - "failed to open WavPack file \"%s\": %s", - fname, error); - return false; - } - - tag_handler_invoke_duration(handler, handler_ctx, - WavpackGetNumSamples(wpc) / - WavpackGetSampleRate(wpc)); - - /* the WavPack format implies APEv2 tags, which means we can - reuse the mapping from tag_ape.c */ - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { - const char *name = tag_item_names[i]; - if (name != nullptr) - wavpack_scan_tag_item(wpc, name, (TagType)i, - handler, handler_ctx); - } - - for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i) - wavpack_scan_tag_item(wpc, i->name, i->type, - handler, handler_ctx); - - if (handler->pair != nullptr) { - char name[64]; - - for (int i = 0, n = WavpackGetNumTagItems(wpc); - i < n; ++i) { - int len = WavpackGetTagItemIndexed(wpc, i, name, - sizeof(name)); - if (len <= 0 || (unsigned)len >= sizeof(name)) - continue; - - wavpack_scan_pair(wpc, name, handler, handler_ctx); - } - } - - WavpackCloseFile(wpc); - - return true; -} - -/* - * mpd input_stream <=> WavpackStreamReader wrapper callbacks - */ - -/* This struct is needed for per-stream last_byte storage. */ -struct wavpack_input { - Decoder *decoder; - InputStream *is; - /* Needed for push_back_byte() */ - int last_byte; -}; - -/** - * Little wrapper for struct wavpack_input to cast from void *. - */ -static struct wavpack_input * -wpin(void *id) -{ - assert(id); - return (struct wavpack_input *)id; -} - -static int32_t -wavpack_input_read_bytes(void *id, void *data, int32_t bcount) -{ - uint8_t *buf = (uint8_t *)data; - int32_t i = 0; - - if (wpin(id)->last_byte != EOF) { - *buf++ = wpin(id)->last_byte; - wpin(id)->last_byte = EOF; - --bcount; - ++i; - } - - /* wavpack fails if we return a partial read, so we just wait - until the buffer is full */ - while (bcount > 0) { - size_t nbytes = decoder_read( - wpin(id)->decoder, *wpin(id)->is, buf, bcount - ); - if (nbytes == 0) { - /* EOF, error or a decoder command */ - break; - } - - i += nbytes; - bcount -= nbytes; - buf += nbytes; - } - - return i; -} - -static uint32_t -wavpack_input_get_pos(void *id) -{ - return wpin(id)->is->offset; -} - -static int -wavpack_input_set_pos_abs(void *id, uint32_t pos) -{ - return wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1; -} - -static int -wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) -{ - return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 0 : -1; -} - -static int -wavpack_input_push_back_byte(void *id, int c) -{ - if (wpin(id)->last_byte == EOF) { - wpin(id)->last_byte = c; - return c; - } else { - return EOF; - } -} - -static uint32_t -wavpack_input_get_length(void *id) -{ - if (wpin(id)->is->size < 0) - return 0; - - return wpin(id)->is->size; -} - -static int -wavpack_input_can_seek(void *id) -{ - return wpin(id)->is->seekable; -} - -static WavpackStreamReader mpd_is_reader = { - wavpack_input_read_bytes, - wavpack_input_get_pos, - wavpack_input_set_pos_abs, - wavpack_input_set_pos_rel, - wavpack_input_push_back_byte, - wavpack_input_get_length, - wavpack_input_can_seek, - nullptr /* no need to write edited tags */ -}; - -static void -wavpack_input_init(struct wavpack_input *isp, Decoder &decoder, - InputStream &is) -{ - isp->decoder = &decoder; - isp->is = &is; - isp->last_byte = EOF; -} - -static InputStream * -wavpack_open_wvc(Decoder &decoder, const char *uri, - Mutex &mutex, Cond &cond, - struct wavpack_input *wpi) -{ - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - if (uri == nullptr) - return nullptr; - - char *wvc_url = g_strconcat(uri, "c", nullptr); - - InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond, - IgnoreError()); - g_free(wvc_url); - - if (is_wvc == nullptr) - return nullptr; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - char first_byte; - size_t nbytes = decoder_read(decoder, *is_wvc, - &first_byte, sizeof(first_byte)); - if (nbytes == 0) { - is_wvc->Close(); - return nullptr; - } - - /* push it back */ - wavpack_input_init(wpi, decoder, *is_wvc); - wpi->last_byte = first_byte; - return is_wvc; -} - -/* - * Decodes a stream. - */ -static void -wavpack_streamdecode(Decoder &decoder, InputStream &is) -{ - int open_flags = OPEN_NORMALIZE; - bool can_seek = is.seekable; - - wavpack_input isp_wvc; - InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(), - is.mutex, is.cond, - &isp_wvc); - if (is_wvc != nullptr) { - open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; - } - - if (!can_seek) { - open_flags |= OPEN_STREAMING; - } - - wavpack_input isp; - wavpack_input_init(&isp, decoder, is); - - char error[ERRORLEN]; - WavpackContext *wpc = - WavpackOpenFileInputEx(&mpd_is_reader, &isp, - open_flags & OPEN_WVC - ? &isp_wvc : nullptr, - error, open_flags, 23); - - if (wpc == nullptr) { - FormatError(wavpack_domain, - "failed to open WavPack stream: %s", error); - return; - } - - wavpack_decode(decoder, wpc, can_seek); - - WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) { - is_wvc->Close(); - } -} - -/* - * Decodes a file. - */ -static void -wavpack_filedecode(Decoder &decoder, const char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, - 23); - if (wpc == nullptr) { - FormatWarning(wavpack_domain, - "failed to open WavPack file \"%s\": %s", - fname, error); - return; - } - - ReplayGainInfo rgi; - if (wavpack_replaygain(rgi, wpc)) - decoder_replay_gain(decoder, &rgi); - - wavpack_decode(decoder, wpc, true); - - WavpackCloseFile(wpc); -} - -static char const *const wavpack_suffixes[] = { - "wv", - nullptr -}; - -static char const *const wavpack_mime_types[] = { - "audio/x-wavpack", - nullptr -}; - -const struct DecoderPlugin wavpack_decoder_plugin = { - "wavpack", - nullptr, - nullptr, - wavpack_streamdecode, - wavpack_filedecode, - wavpack_scan_file, - nullptr, - nullptr, - wavpack_suffixes, - wavpack_mime_types -}; diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/WavpackDecoderPlugin.hxx deleted file mode 100644 index 3a2d94532..000000000 --- a/src/decoder/WavpackDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_WAVPACK_HXX -#define MPD_DECODER_WAVPACK_HXX - -extern const struct DecoderPlugin wavpack_decoder_plugin; - -#endif diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx deleted file mode 100644 index 3da3f1387..000000000 --- a/src/decoder/WildmidiDecoderPlugin.cxx +++ /dev/null @@ -1,158 +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 "WildmidiDecoderPlugin.hxx" -#include "DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -extern "C" { -#include <wildmidi_lib.h> -} - -static constexpr Domain wildmidi_domain("wildmidi"); - -static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000; - -static bool -wildmidi_init(const config_param ¶m) -{ - Error error; - const AllocatedPath path = - param.GetBlockPath("config_file", - "/etc/timidity/timidity.cfg", - error); - if (path.IsNull()) - FatalError(error); - - if (!FileExists(path)) { - const auto utf8 = path.ToUTF8(); - FormatDebug(wildmidi_domain, - "configuration file does not exist: %s", - utf8.c_str()); - return false; - } - - return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0; -} - -static void -wildmidi_finish(void) -{ - WildMidi_Shutdown(); -} - -static void -wildmidi_file_decode(Decoder &decoder, const char *path_fs) -{ - static constexpr AudioFormat audio_format = { - WILDMIDI_SAMPLE_RATE, - SampleFormat::S16, - 2, - }; - midi *wm; - const struct _WM_Info *info; - - wm = WildMidi_Open(path_fs); - if (wm == nullptr) - return; - - info = WildMidi_GetInfo(wm); - if (info == nullptr) { - WildMidi_Close(wm); - return; - } - - decoder_initialized(decoder, audio_format, true, - info->approx_total_samples / WILDMIDI_SAMPLE_RATE); - - DecoderCommand cmd; - do { - char buffer[4096]; - int len; - - info = WildMidi_GetInfo(wm); - if (info == nullptr) - break; - - len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); - if (len <= 0) - break; - - cmd = decoder_data(decoder, nullptr, buffer, len, 0); - - if (cmd == DecoderCommand::SEEK) { - unsigned long seek_where = WILDMIDI_SAMPLE_RATE * - decoder_seek_where(decoder); - - WildMidi_FastSeek(wm, &seek_where); - decoder_command_finished(decoder); - cmd = DecoderCommand::NONE; - } - - } while (cmd == DecoderCommand::NONE); - - WildMidi_Close(wm); -} - -static bool -wildmidi_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - midi *wm = WildMidi_Open(path_fs); - if (wm == nullptr) - return false; - - const struct _WM_Info *info = WildMidi_GetInfo(wm); - if (info == nullptr) { - WildMidi_Close(wm); - return false; - } - - int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; - tag_handler_invoke_duration(handler, handler_ctx, duration); - - WildMidi_Close(wm); - - return true; -} - -static const char *const wildmidi_suffixes[] = { - "mid", - nullptr -}; - -const struct DecoderPlugin wildmidi_decoder_plugin = { - "wildmidi", - wildmidi_init, - wildmidi_finish, - nullptr, - wildmidi_file_decode, - wildmidi_scan_file, - nullptr, - nullptr, - wildmidi_suffixes, - nullptr, -}; diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/WildmidiDecoderPlugin.hxx deleted file mode 100644 index a6289612e..000000000 --- a/src/decoder/WildmidiDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_DECODER_WILDMIDI_HXX -#define MPD_DECODER_WILDMIDI_HXX - -extern const struct DecoderPlugin wildmidi_decoder_plugin; - -#endif diff --git a/src/decoder/XiphTags.cxx b/src/decoder/XiphTags.cxx deleted file mode 100644 index b9958a19a..000000000 --- a/src/decoder/XiphTags.cxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "XiphTags.hxx" - -const struct tag_table xiph_tags[] = { - { "tracknumber", TAG_TRACK }, - { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, - { nullptr, TAG_NUM_OF_ITEM_TYPES } -}; diff --git a/src/decoder/XiphTags.hxx b/src/decoder/XiphTags.hxx deleted file mode 100644 index 606dfef10..000000000 --- a/src/decoder/XiphTags.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_XIPH_TAGS_HXX -#define MPD_XIPH_TAGS_HXX - -#include "check.h" -#include "tag/TagTable.hxx" - -extern const struct tag_table xiph_tags[]; - -#endif diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..32a2432f4 --- /dev/null +++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx @@ -0,0 +1,142 @@ +/* + * 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 "AdPlugDecoderPlugin.h" +#include "tag/TagHandler.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <adplug/adplug.h> +#include <adplug/emuopl.h> + +#include <assert.h> + +static unsigned sample_rate; + +static bool +adplug_init(const config_param ¶m) +{ + Error error; + + sample_rate = param.GetBlockValue("sample_rate", 48000u); + if (!audio_check_sample_rate(sample_rate, error)) { + LogError(error); + return false; + } + + return true; +} + +static void +adplug_file_decode(Decoder &decoder, Path path_fs) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl); + if (player == nullptr) + return; + + const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, false, + player->songlength() / 1000.); + + int16_t buffer[2048]; + const unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2; + DecoderCommand cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, nullptr, + buffer, sizeof(buffer), + 0); + } while (cmd == DecoderCommand::NONE); + + delete player; +} + +static void +adplug_scan_tag(TagType type, const std::string &value, + const struct tag_handler *handler, void *handler_ctx) +{ + if (!value.empty()) + tag_handler_invoke_tag(handler, handler_ctx, + type, value.c_str()); +} + +static bool +adplug_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + CEmuopl opl(sample_rate, true, true); + opl.init(); + + CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl); + if (player == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + player->songlength() / 1000); + + if (handler->tag != nullptr) { + adplug_scan_tag(TAG_TITLE, player->gettitle(), + handler, handler_ctx); + adplug_scan_tag(TAG_ARTIST, player->getauthor(), + handler, handler_ctx); + adplug_scan_tag(TAG_COMMENT, player->getdesc(), + handler, handler_ctx); + } + + delete player; + return true; +} + +static const char *const adplug_suffixes[] = { + "amd", + "d00", + "hsc", + "laa", + "rad", + "raw", + "sa2", + nullptr +}; + +const struct DecoderPlugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..539dbbf0a --- /dev/null +++ b/src/decoder/plugins/AdPlugDecoderPlugin.h @@ -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_DECODER_ADPLUG_H +#define MPD_DECODER_ADPLUG_H + +extern const struct DecoderPlugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx new file mode 100644 index 000000000..93a6e03a2 --- /dev/null +++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx @@ -0,0 +1,300 @@ +/* + * 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 "AudiofileDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <audiofile.h> +#include <af_vfs.h> + +#include <assert.h> +#include <stdio.h> + +static constexpr Domain audiofile_domain("audiofile"); + +static void +audiofile_error_func(long, const char *msg) +{ + LogWarning(audiofile_domain, msg); +} + +static bool +audiofile_init(const config_param &) +{ + afSetErrorHandler(audiofile_error_func); + return true; +} + +struct AudioFileInputStream { + Decoder *const decoder; + InputStream &is; + + size_t Read(void *buffer, size_t size) { + /* libaudiofile does not like partial reads at all, + and will abort playback; therefore always force full + reads */ + return decoder_read_full(decoder, is, buffer, size) + ? size + : 0; + } +}; + +gcc_pure +static double +audiofile_get_duration(AFfilehandle fh) +{ + double frame_count = afGetFrameCount(fh, AF_DEFAULT_TRACK); + double rate = afGetRate(fh, AF_DEFAULT_TRACK); + + return frame_count / rate; +} + +static ssize_t +audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + + return afis.Read(data, length); +} + +static AFfileoffset +audiofile_file_length(AFvirtualfile *vfile) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + InputStream &is = afis.is; + + return is.GetSize(); +} + +static AFfileoffset +audiofile_file_tell(AFvirtualfile *vfile) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + InputStream &is = afis.is; + + return is.GetOffset(); +} + +static void +audiofile_file_destroy(AFvirtualfile *vfile) +{ + assert(vfile->closure != nullptr); + + vfile->closure = nullptr; +} + +static AFfileoffset +audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset _offset, + int is_relative) +{ + AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure; + InputStream &is = afis.is; + + InputStream::offset_type offset = _offset; + if (is_relative) + offset += is.GetOffset(); + + Error error; + if (is.LockSeek(offset, error)) { + return is.GetOffset(); + } else { + LogError(error, "Seek failed"); + return -1; + } +} + +static AFvirtualfile * +setup_virtual_fops(AudioFileInputStream &afis) +{ + AFvirtualfile *vf = new AFvirtualfile(); + vf->closure = &afis; + vf->write = nullptr; + vf->read = audiofile_file_read; + vf->length = audiofile_file_length; + vf->destroy = audiofile_file_destroy; + vf->seek = audiofile_file_seek; + vf->tell = audiofile_file_tell; + return vf; +} + +static SampleFormat +audiofile_bits_to_sample_format(int bits) +{ + switch (bits) { + case 8: + return SampleFormat::S8; + + case 16: + return SampleFormat::S16; + + case 24: + return SampleFormat::S24_P32; + + case 32: + return SampleFormat::S32; + } + + return SampleFormat::UNDEFINED; +} + +static SampleFormat +audiofile_setup_sample_format(AFfilehandle af_fp) +{ + int fs, bits; + + afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) { + FormatDebug(audiofile_domain, + "input file has %d bit samples, converting to 16", + bits); + bits = 16; + } + + afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, + AF_SAMPFMT_TWOSCOMP, bits); + afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + + return audiofile_bits_to_sample_format(bits); +} + +static void +audiofile_stream_decode(Decoder &decoder, InputStream &is) +{ + if (!is.IsSeekable()) { + LogWarning(audiofile_domain, "not seekable"); + return; + } + + AudioFileInputStream afis{&decoder, is}; + AFvirtualfile *const vf = setup_virtual_fops(afis); + + const AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr); + if (fh == AF_NULL_FILEHANDLE) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + afGetRate(fh, AF_DEFAULT_TRACK), + audiofile_setup_sample_format(fh), + afGetVirtualChannels(fh, AF_DEFAULT_TRACK), + error)) { + LogError(error); + afCloseFile(fh); + return; + } + + const double total_time = audiofile_get_duration(fh); + + const uint16_t kbit_rate = (uint16_t) + (is.GetSize() * 8.0 / total_time / 1000.0 + 0.5); + + const unsigned frame_size = (unsigned) + afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true); + + decoder_initialized(decoder, audio_format, true, total_time); + + DecoderCommand cmd; + do { + /* pick 1020 since its divisible for 8,16,24, and + 32-bit audio */ + char chunk[1020]; + const int nframes = + afReadFrames(fh, AF_DEFAULT_TRACK, chunk, + sizeof(chunk) / frame_size); + if (nframes <= 0) + break; + + cmd = decoder_data(decoder, nullptr, + chunk, nframes * frame_size, + kbit_rate); + + if (cmd == DecoderCommand::SEEK) { + AFframecount frame = decoder_seek_where(decoder) * + audio_format.sample_rate; + afSeekFrame(fh, AF_DEFAULT_TRACK, frame); + + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + afCloseFile(fh); +} + +gcc_pure +static int +audiofile_get_duration(InputStream &is) +{ + if (!is.IsSeekable()) + return -1; + + AudioFileInputStream afis{nullptr, is}; + AFvirtualfile *vf = setup_virtual_fops(afis); + AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr); + if (fh == AF_NULL_FILEHANDLE) + return -1; + + int duration = int(audiofile_get_duration(fh)); + afCloseFile(fh); + return duration; +} + +static bool +audiofile_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + int total_time = audiofile_get_duration(is); + if (total_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; +} + +static const char *const audiofile_suffixes[] = { + "wav", "au", "aiff", "aif", nullptr +}; + +static const char *const audiofile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + nullptr +}; + +const struct DecoderPlugin audiofile_decoder_plugin = { + "audiofile", + audiofile_init, + nullptr, + audiofile_stream_decode, + nullptr, + nullptr, + audiofile_scan_stream, + nullptr, + audiofile_suffixes, + audiofile_mime_types, +}; diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx new file mode 100644 index 000000000..61129076d --- /dev/null +++ b/src/decoder/plugins/AudiofileDecoderPlugin.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_DECODER_AUDIOFILE_HXX +#define MPD_DECODER_AUDIOFILE_HXX + +extern const struct DecoderPlugin audiofile_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx new file mode 100644 index 000000000..0f10b20e9 --- /dev/null +++ b/src/decoder/plugins/DsdLib.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* \file + * + * This file contains functions used by the DSF and DSDIFF decoders. + * + */ + +#include "config.h" +#include "DsdLib.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "tag/TagId3.hxx" +#include "util/Error.hxx" + +#include <string.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +bool +DsdId::Equals(const char *s) const +{ + assert(s != nullptr); + assert(strlen(s) == sizeof(value)); + + return memcmp(value, s, sizeof(value)) == 0; +} + +/** + * Skip the #input_stream to the specified offset. + */ +bool +dsdlib_skip_to(Decoder *decoder, InputStream &is, + uint64_t offset) +{ + if (is.IsSeekable()) + return is.Seek(offset, IgnoreError()); + + if (uint64_t(is.GetOffset()) > offset) + return false; + + return dsdlib_skip(decoder, is, offset - is.GetOffset()); +} + +/** + * Skip some bytes from the #input_stream. + */ +bool +dsdlib_skip(Decoder *decoder, InputStream &is, + uint64_t delta) +{ + if (delta == 0) + return true; + + if (is.IsSeekable()) + return is.Seek(is.GetOffset() + delta, IgnoreError()); + + if (delta > 1024 * 1024) + /* don't skip more than one megabyte; it would be too + expensive */ + return false; + + return decoder_skip(decoder, is, delta); +} + +#ifdef HAVE_ID3TAG +void +dsdlib_tag_id3(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, int64_t tagoffset) +{ + assert(tagoffset >= 0); + + if (tagoffset == 0) + return; + + if (!dsdlib_skip_to(nullptr, is, tagoffset)) + return; + + struct id3_tag *id3_tag = nullptr; + id3_length_t count; + + /* Prevent broken files causing problems */ + const auto size = is.GetSize(); + const auto offset = is.GetOffset(); + if (offset >= size) + return; + + count = size - offset; + + /* Check and limit id3 tag size to prevent a stack overflow */ + if (count == 0 || count > 4096) + return; + + id3_byte_t dsdid3[count]; + id3_byte_t *dsdid3data; + dsdid3data = dsdid3; + + if (!decoder_read_full(nullptr, is, dsdid3data, count)) + return; + + id3_tag = id3_tag_parse(dsdid3data, count); + if (id3_tag == nullptr) + return; + + scan_id3_tag(id3_tag, handler, handler_ctx); + + id3_tag_delete(id3_tag); + + return; +} +#endif diff --git a/src/decoder/plugins/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx new file mode 100644 index 000000000..5250922ac --- /dev/null +++ b/src/decoder/plugins/DsdLib.hxx @@ -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. + */ + +#ifndef MPD_DECODER_DSDLIB_HXX +#define MPD_DECODER_DSDLIB_HXX + +#include "system/ByteOrder.hxx" +#include "Compiler.h" + +#include <stddef.h> +#include <stdint.h> + +struct Decoder; +class InputStream; + +struct DsdId { + char value[4]; + + gcc_pure + bool Equals(const char *s) const; +}; + +class DsdUint64 { + uint32_t lo; + uint32_t hi; + +public: + constexpr uint64_t Read() const { + return (uint64_t(FromLE32(hi)) << 32) | + uint64_t(FromLE32(lo)); + } +}; + +class DffDsdUint64 { + uint32_t hi; + uint32_t lo; + +public: + constexpr uint64_t Read() const { + return (uint64_t(FromBE32(hi)) << 32) | + uint64_t(FromBE32(lo)); + } +}; + +bool +dsdlib_skip_to(Decoder *decoder, InputStream &is, + uint64_t offset); + +bool +dsdlib_skip(Decoder *decoder, InputStream &is, + uint64_t delta); + +/** + * Add tags from ID3 tag. All tags commonly found in the ID3 tags of + * DSF and DSDIFF files are imported + */ +void +dsdlib_tag_id3(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, int64_t tagoffset); + +#endif diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx new file mode 100644 index 000000000..c6880c4f9 --- /dev/null +++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* \file + * + * This plugin decodes DSDIFF data (SACD) embedded in DFF files. + * The DFF code was modeled after the specification found here: + * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf + * + * All functions common to both DSD decoders have been moved to dsdlib + */ + +#include "config.h" +#include "DsdiffDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "system/ByteOrder.hxx" +#include "tag/TagHandler.hxx" +#include "DsdLib.hxx" +#include "Log.hxx" + +struct DsdiffHeader { + DsdId id; + DffDsdUint64 size; + DsdId format; +}; + +struct DsdiffChunkHeader { + DsdId id; + DffDsdUint64 size; + + /** + * Read the "size" attribute from the specified header, converting it + * to the host byte order if needed. + */ + constexpr + uint64_t GetSize() const { + return size.Read(); + } +}; + +/** struct for DSDIFF native Artist and Title tags */ +struct dsdiff_native_tag { + uint32_t size; +}; + +struct DsdiffMetaData { + unsigned sample_rate, channels; + bool bitreverse; + uint64_t chunk_size; +}; + +static bool lsbitfirst; + +static bool +dsdiff_init(const config_param ¶m) +{ + lsbitfirst = param.GetBlockValue("lsbitfirst", false); + return true; +} + +static bool +dsdiff_read_id(Decoder *decoder, InputStream &is, + DsdId *id) +{ + return decoder_read_full(decoder, is, id, sizeof(*id)); +} + +static bool +dsdiff_read_chunk_header(Decoder *decoder, InputStream &is, + DsdiffChunkHeader *header) +{ + return decoder_read_full(decoder, is, header, sizeof(*header)); +} + +static bool +dsdiff_read_payload(Decoder *decoder, InputStream &is, + const DsdiffChunkHeader *header, + void *data, size_t length) +{ + uint64_t size = header->GetSize(); + if (size != (uint64_t)length) + return false; + + return decoder_read_full(decoder, is, data, length); +} + +/** + * Read and parse a "SND" chunk inside "PROP". + */ +static bool +dsdiff_read_prop_snd(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + InputStream::offset_type end_offset) +{ + DsdiffChunkHeader header; + while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) { + if (!dsdiff_read_chunk_header(decoder, is, &header)) + return false; + + InputStream::offset_type chunk_end_offset = is.GetOffset() + + header.GetSize(); + if (chunk_end_offset > end_offset) + return false; + + if (header.id.Equals("FS ")) { + uint32_t sample_rate; + if (!dsdiff_read_payload(decoder, is, &header, + &sample_rate, + sizeof(sample_rate))) + return false; + + metadata->sample_rate = FromBE32(sample_rate); + } else if (header.id.Equals("CHNL")) { + uint16_t channels; + if (header.GetSize() < sizeof(channels) || + !decoder_read_full(decoder, is, + &channels, sizeof(channels)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + metadata->channels = FromBE16(channels); + } else if (header.id.Equals("CMPR")) { + DsdId type; + if (header.GetSize() < sizeof(type) || + !decoder_read_full(decoder, is, + &type, sizeof(type)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + if (!type.Equals("DSD ")) + /* only uncompressed DSD audio data + is implemented */ + return false; + } else { + /* ignore unknown chunk */ + + if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } + + return is.GetOffset() == end_offset; +} + +/** + * Read and parse a "PROP" chunk. + */ +static bool +dsdiff_read_prop(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + const DsdiffChunkHeader *prop_header) +{ + uint64_t prop_size = prop_header->GetSize(); + InputStream::offset_type end_offset = is.GetOffset() + prop_size; + + DsdId prop_id; + if (prop_size < sizeof(prop_id) || + !dsdiff_read_id(decoder, is, &prop_id)) + return false; + + if (prop_id.Equals("SND ")) + return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); + else + /* ignore unknown PROP chunk */ + return dsdlib_skip_to(decoder, is, end_offset); +} + +static void +dsdiff_handle_native_tag(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, InputStream::offset_type tagoffset, + TagType type) +{ + if (!dsdlib_skip_to(nullptr, is, tagoffset)) + return; + + struct dsdiff_native_tag metatag; + + if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag))) + return; + + uint32_t length = FromBE32(metatag.size); + + /* Check and limit size of the tag to prevent a stack overflow */ + if (length == 0 || length > 60) + return; + + char string[length]; + char *label; + label = string; + + if (!decoder_read_full(nullptr, is, label, (size_t)length)) + return; + + string[length] = '\0'; + tag_handler_invoke_tag(handler, handler_ctx, type, label); + return; +} + +/** + * Read and parse additional metadata chunks for tagging purposes. By default + * dsdiff files only support equivalents for artist and title but some of the + * extract tools add an id3 tag to provide more tags. If such id3 is found + * this will be used for tagging otherwise the native tags (if any) will be + * used + */ + +static bool +dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header, + const struct tag_handler *handler, + void *handler_ctx) +{ + + /* skip from DSD data to next chunk header */ + if (!dsdlib_skip(decoder, is, metadata->chunk_size)) + return false; + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + + /** offset for artist tag */ + InputStream::offset_type artist_offset = 0; + /** offset for title tag */ + InputStream::offset_type title_offset = 0; + +#ifdef HAVE_ID3TAG + InputStream::offset_type id3_offset = 0; +#endif + + /* Now process all the remaining chunk headers in the stream + and record their position and size */ + + do { + uint64_t chunk_size = chunk_header->GetSize(); + + /* DIIN chunk, is directly followed by other chunks */ + if (chunk_header->id.Equals("DIIN")) + chunk_size = 0; + + /* DIAR chunk - DSDIFF native tag for Artist */ + if (chunk_header->id.Equals("DIAR")) { + chunk_size = chunk_header->GetSize(); + artist_offset = is.GetOffset(); + } + + /* DITI chunk - DSDIFF native tag for Title */ + if (chunk_header->id.Equals("DITI")) { + chunk_size = chunk_header->GetSize(); + title_offset = is.GetOffset(); + } +#ifdef HAVE_ID3TAG + /* 'ID3 ' chunk, offspec. Used by sacdextract */ + if (chunk_header->id.Equals("ID3 ")) { + chunk_size = chunk_header->GetSize(); + id3_offset = is.GetOffset(); + } +#endif + + if (!dsdlib_skip(decoder, is, chunk_size)) + break; + } while (dsdiff_read_chunk_header(decoder, is, chunk_header)); + + /* done processing chunk headers, process tags if any */ + +#ifdef HAVE_ID3TAG + if (id3_offset != 0) { + /* a ID3 tag has preference over the other tags, do not process + other tags if we have one */ + dsdlib_tag_id3(is, handler, handler_ctx, id3_offset); + return true; + } +#endif + + if (artist_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + artist_offset, TAG_ARTIST); + + if (title_offset != 0) + dsdiff_handle_native_tag(is, handler, handler_ctx, + title_offset, TAG_TITLE); + return true; +} + +/** + * Read and parse all metadata chunks at the beginning. Stop when the + * first "DSD" chunk is seen, and return its header in the + * "chunk_header" parameter. + */ +static bool +dsdiff_read_metadata(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header) +{ + DsdiffHeader header; + if (!decoder_read_full(decoder, is, &header, sizeof(header)) || + !header.id.Equals("FRM8") || + !header.format.Equals("DSD ")) + return false; + + while (true) { + if (!dsdiff_read_chunk_header(decoder, is, + chunk_header)) + return false; + + if (chunk_header->id.Equals("PROP")) { + if (!dsdiff_read_prop(decoder, is, metadata, + chunk_header)) + return false; + } else if (chunk_header->id.Equals("DSD ")) { + const uint64_t chunk_size = chunk_header->GetSize(); + metadata->chunk_size = chunk_size; + return true; + } else { + /* ignore unknown chunk */ + const uint64_t chunk_size = chunk_header->GetSize(); + InputStream::offset_type chunk_end_offset = + is.GetOffset() + chunk_size; + + if (!dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +/** + * Decode one "DSD" chunk. + */ +static bool +dsdiff_decode_chunk(Decoder &decoder, InputStream &is, + unsigned channels, + uint64_t chunk_size) +{ + uint8_t buffer[8192]; + + const size_t sample_size = sizeof(buffer[0]); + const size_t frame_size = channels * sample_size; + const unsigned buffer_frames = sizeof(buffer) / frame_size; + const unsigned buffer_samples = buffer_frames * frame_size; + const size_t buffer_size = buffer_samples * sample_size; + + while (chunk_size > 0) { + /* see how much aligned data from the remaining chunk + fits into the local buffer */ + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + unsigned now_frames = + (unsigned)chunk_size / frame_size; + now_size = now_frames * frame_size; + } + + if (!decoder_read_full(&decoder, is, buffer, now_size)) + return false; + + const size_t nbytes = now_size; + chunk_size -= nbytes; + + if (lsbitfirst) + bit_reverse_buffer(buffer, buffer + nbytes); + + const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); + switch (cmd) { + case DecoderCommand::NONE: + break; + + case DecoderCommand::START: + case DecoderCommand::STOP: + return false; + + case DecoderCommand::SEEK: + + /* Not implemented yet */ + decoder_seek_error(decoder); + break; + } + } + return dsdlib_skip(&decoder, is, chunk_size); +} + +static void +dsdiff_stream_decode(Decoder &decoder, InputStream &is) +{ + DsdiffMetaData metadata; + + DsdiffChunkHeader chunk_header; + /* check if it is is a proper DFF file */ + if (!dsdiff_read_metadata(&decoder, is, &metadata, &chunk_header)) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, error)) { + LogError(error); + return; + } + + /* calculate song time from DSD chunk size and sample frequency */ + uint64_t chunk_size = metadata.chunk_size; + float songtime = ((chunk_size / metadata.channels) * 8) / + (float) metadata.sample_rate; + + /* success: file was recognized */ + decoder_initialized(decoder, audio_format, false, songtime); + + /* every iteration of the following loop decodes one "DSD" + chunk from a DFF file */ + + while (true) { + chunk_size = chunk_header.GetSize(); + + if (chunk_header.id.Equals("DSD ")) { + if (!dsdiff_decode_chunk(decoder, is, + metadata.channels, + chunk_size)) + break; + } else { + /* ignore other chunks */ + if (!dsdlib_skip(&decoder, is, chunk_size)) + break; + } + + /* read next chunk header; the first one was read by + dsdiff_read_metadata() */ + if (!dsdiff_read_chunk_header(&decoder, + is, &chunk_header)) + break; + } +} + +static bool +dsdiff_scan_stream(InputStream &is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + DsdiffMetaData metadata; + DsdiffChunkHeader chunk_header; + + /* First check for DFF metadata */ + if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header)) + return false; + + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, IgnoreError())) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* calculate song time and add as tag */ + unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / + metadata.sample_rate; + tag_handler_invoke_duration(handler, handler_ctx, songtime); + + /* Read additional metadata and created tags if available */ + dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header, + handler, handler_ctx); + + return true; +} + +static const char *const dsdiff_suffixes[] = { + "dff", + nullptr +}; + +static const char *const dsdiff_mime_types[] = { + "application/x-dff", + nullptr +}; + +const struct DecoderPlugin dsdiff_decoder_plugin = { + "dsdiff", + dsdiff_init, + nullptr, + dsdiff_stream_decode, + nullptr, + nullptr, + dsdiff_scan_stream, + nullptr, + dsdiff_suffixes, + dsdiff_mime_types, +}; diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx new file mode 100644 index 000000000..7aa36752b --- /dev/null +++ b/src/decoder/plugins/DsdiffDecoderPlugin.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_DECODER_DSDIFF_H +#define MPD_DECODER_DSDIFF_H + +extern const struct DecoderPlugin dsdiff_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx new file mode 100644 index 000000000..6a7267f61 --- /dev/null +++ b/src/decoder/plugins/DsfDecoderPlugin.cxx @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* \file + * + * This plugin decodes DSDIFF data (SACD) embedded in DSF files. + * + * The DSF code was created using the specification found here: + * http://dsd-guide.com/sonys-dsf-file-format-spec + * + * All functions common to both DSD decoders have been moved to dsdlib + */ + +#include "config.h" +#include "DsfDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "system/ByteOrder.hxx" +#include "DsdLib.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +struct DsfMetaData { + unsigned sample_rate, channels; + bool bitreverse; + uint64_t chunk_size; +#ifdef HAVE_ID3TAG + InputStream::offset_type id3_offset; + uint64_t id3_size; +#endif +}; + +struct DsfHeader { + /** DSF header id: "DSD " */ + DsdId id; + /** DSD chunk size, including id = 28 */ + DsdUint64 size; + /** total file size */ + DsdUint64 fsize; + /** pointer to id3v2 metadata, should be at the end of the file */ + DsdUint64 pmeta; +}; + +/** DSF file fmt chunk */ +struct DsfFmtChunk { + /** id: "fmt " */ + DsdId id; + /** fmt chunk size, including id, normally 52 */ + DsdUint64 size; + /** version of this format = 1 */ + uint32_t version; + /** 0: DSD raw */ + uint32_t formatid; + /** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */ + uint32_t channeltype; + /** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */ + uint32_t channelnum; + /** sample frequency: 2822400, 5644800 */ + uint32_t sample_freq; + /** bits per sample 1 or 8 */ + uint32_t bitssample; + /** Sample count per channel in bytes */ + DsdUint64 scnt; + /** block size per channel = 4096 */ + uint32_t block_size; + /** reserved, should be all zero */ + uint32_t reserved; +}; + +struct DsfDataChunk { + DsdId id; + /** "data" chunk size, includes header (id+size) */ + DsdUint64 size; +}; + +/** + * Read and parse all needed metadata chunks for DSF files. + */ +static bool +dsf_read_metadata(Decoder *decoder, InputStream &is, + DsfMetaData *metadata) +{ + DsfHeader dsf_header; + if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) || + !dsf_header.id.Equals("DSD ")) + return false; + + const uint64_t chunk_size = dsf_header.size.Read(); + if (sizeof(dsf_header) != chunk_size) + return false; + +#ifdef HAVE_ID3TAG + const uint64_t metadata_offset = dsf_header.pmeta.Read(); +#endif + + /* read the 'fmt ' chunk of the DSF file */ + DsfFmtChunk dsf_fmt_chunk; + if (!decoder_read_full(decoder, is, + &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) || + !dsf_fmt_chunk.id.Equals("fmt ")) + return false; + + const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read(); + if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) + return false; + + uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); + + /* for now, only support version 1 of the standard, DSD raw stereo + files with a sample freq of 2822400 or 5644800 Hz */ + + if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 + || dsf_fmt_chunk.channeltype != 2 + || dsf_fmt_chunk.channelnum != 2 + || (samplefreq != 2822400 && samplefreq != 5644800)) + return false; + + uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size); + /* according to the spec block size should always be 4096 */ + if (chblksize != 4096) + return false; + + /* read the 'data' chunk of the DSF file */ + DsfDataChunk data_chunk; + if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) || + !data_chunk.id.Equals("data")) + return false; + + /* data size of DSF files are padded to multiple of 4096, + we use the actual data size as chunk size */ + + uint64_t data_size = data_chunk.size.Read(); + if (data_size < sizeof(data_chunk)) + return false; + + data_size -= sizeof(data_chunk); + + /* data_size cannot be bigger or equal to total file size */ + const uint64_t size = (uint64_t)is.GetSize(); + if (data_size >= size) + return false; + + /* use the sample count from the DSF header as the upper + bound, because some DSF files contain junk at the end of + the "data" chunk */ + const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); + const uint64_t playable_size = samplecnt * 2 / 8; + if (data_size > playable_size) + data_size = playable_size; + + metadata->chunk_size = data_size; + metadata->channels = (unsigned) dsf_fmt_chunk.channelnum; + metadata->sample_rate = samplefreq; +#ifdef HAVE_ID3TAG + /* metada_offset cannot be bigger then or equal to total file size */ + if (metadata_offset >= size) + metadata->id3_offset = 0; + else + metadata->id3_offset = (InputStream::offset_type)metadata_offset; +#endif + /* check bits per sample format, determine if bitreverse is needed */ + metadata->bitreverse = dsf_fmt_chunk.bitssample == 1; + return true; +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +/** + * DSF data is build up of alternating 4096 blocks of DSD samples for left and + * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1 + * block of 4096 DSD right samples to 8k of samples in normal PCM left/right + * order. + */ +static void +dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes) +{ + for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { + scratch[i] = *(dest+j); + j++; + } + + for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { + scratch[i] = *(dest+4096+j); + j++; + } + + for (unsigned i = 0; i < (unsigned)nrbytes; i++) { + *dest = scratch[i]; + dest++; + } +} + +/** + * Decode one complete DSF 'data' chunk i.e. a complete song + */ +static bool +dsf_decode_chunk(Decoder &decoder, InputStream &is, + unsigned channels, + uint64_t chunk_size, + bool bitreverse) +{ + uint8_t buffer[8192]; + + /* scratch buffer for DSF samples to convert to the needed + normal left/right regime of samples */ + uint8_t dsf_scratch_buffer[8192]; + + const size_t sample_size = sizeof(buffer[0]); + const size_t frame_size = channels * sample_size; + const unsigned buffer_frames = sizeof(buffer) / frame_size; + const unsigned buffer_samples = buffer_frames * frame_size; + const size_t buffer_size = buffer_samples * sample_size; + + while (chunk_size > 0) { + /* see how much aligned data from the remaining chunk + fits into the local buffer */ + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + unsigned now_frames = + (unsigned)chunk_size / frame_size; + now_size = now_frames * frame_size; + } + + if (!decoder_read_full(&decoder, is, buffer, now_size)) + return false; + + const size_t nbytes = now_size; + chunk_size -= nbytes; + + if (bitreverse) + bit_reverse_buffer(buffer, buffer + nbytes); + + dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes); + + const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0); + switch (cmd) { + case DecoderCommand::NONE: + break; + + case DecoderCommand::START: + case DecoderCommand::STOP: + return false; + + case DecoderCommand::SEEK: + + /* not implemented yet */ + decoder_seek_error(decoder); + break; + } + } + return dsdlib_skip(&decoder, is, chunk_size); +} + +static void +dsf_stream_decode(Decoder &decoder, InputStream &is) +{ + /* check if it is a proper DSF file */ + DsfMetaData metadata; + if (!dsf_read_metadata(&decoder, is, &metadata)) + return; + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, error)) { + LogError(error); + return; + } + /* Calculate song time from DSD chunk size and sample frequency */ + uint64_t chunk_size = metadata.chunk_size; + float songtime = ((chunk_size / metadata.channels) * 8) / + (float) metadata.sample_rate; + + /* success: file was recognized */ + decoder_initialized(decoder, audio_format, false, songtime); + + if (!dsf_decode_chunk(decoder, is, metadata.channels, + chunk_size, + metadata.bitreverse)) + return; +} + +static bool +dsf_scan_stream(InputStream &is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + /* check DSF metadata */ + DsfMetaData metadata; + if (!dsf_read_metadata(nullptr, is, &metadata)) + return false; + + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8, + SampleFormat::DSD, + metadata.channels, IgnoreError())) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* calculate song time and add as tag */ + unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) / + metadata.sample_rate; + tag_handler_invoke_duration(handler, handler_ctx, songtime); + +#ifdef HAVE_ID3TAG + /* Add available tags from the ID3 tag */ + dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); +#endif + return true; +} + +static const char *const dsf_suffixes[] = { + "dsf", + nullptr +}; + +static const char *const dsf_mime_types[] = { + "application/x-dsf", + nullptr +}; + +const struct DecoderPlugin dsf_decoder_plugin = { + "dsf", + nullptr, + nullptr, + dsf_stream_decode, + nullptr, + nullptr, + dsf_scan_stream, + nullptr, + dsf_suffixes, + dsf_mime_types, +}; diff --git a/src/decoder/plugins/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx new file mode 100644 index 000000000..02bea0b5c --- /dev/null +++ b/src/decoder/plugins/DsfDecoderPlugin.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_DECODER_DSF_H +#define MPD_DECODER_DSF_H + +extern const struct DecoderPlugin dsf_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx new file mode 100644 index 000000000..6a415bb53 --- /dev/null +++ b/src/decoder/plugins/FaadDecoderPlugin.cxx @@ -0,0 +1,474 @@ +/* + * 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 "FaadDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "../DecoderBuffer.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <neaacdec.h> + +#include <assert.h> +#include <string.h> +#include <unistd.h> + +static const unsigned adts_sample_rates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +static constexpr Domain faad_decoder_domain("faad_decoder"); + +/** + * Check whether the buffer head is an AAC frame, and return the frame + * length. Returns 0 if it is not a frame. + */ +static size_t +adts_check_frame(const unsigned char *data) +{ + /* check syncword */ + if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0))) + return 0; + + return (((unsigned int)data[3] & 0x3) << 11) | + (((unsigned int)data[4]) << 3) | + (data[5] >> 5); +} + +/** + * Find the next AAC frame in the buffer. Returns 0 if no frame is + * found or if not enough data is available. + */ +static size_t +adts_find_frame(DecoderBuffer *buffer) +{ + while (true) { + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_need(buffer, 8)); + if (data.IsNull()) + /* failed */ + return 0; + + /* find the 0xff marker */ + const uint8_t *p = (const uint8_t *) + memchr(data.data, 0xff, data.size); + if (p == nullptr) { + /* no marker - discard the buffer */ + decoder_buffer_clear(buffer); + continue; + } + + if (p > data.data) { + /* discard data before 0xff */ + decoder_buffer_consume(buffer, p - data.data); + continue; + } + + /* is it a frame? */ + const size_t frame_length = adts_check_frame(data.data); + if (frame_length == 0) { + /* it's just some random 0xff byte; discard it + and continue searching */ + decoder_buffer_consume(buffer, 1); + continue; + } + + if (decoder_buffer_need(buffer, frame_length).IsNull()) { + /* not enough data; discard this frame to + prevent a possible buffer overflow */ + decoder_buffer_clear(buffer); + continue; + } + + /* found a full frame! */ + return frame_length; + } +} + +static float +adts_song_duration(DecoderBuffer *buffer) +{ + const InputStream &is = decoder_buffer_get_stream(buffer); + const bool estimate = !is.CheapSeeking(); + const auto file_size = is.GetSize(); + if (estimate && file_size <= 0) + return -1; + + unsigned sample_rate = 0; + + /* Read all frames to ensure correct time and bitrate */ + unsigned frames = 0; + for (;; frames++) { + const unsigned frame_length = adts_find_frame(buffer); + if (frame_length == 0) + break; + + if (frames == 0) { + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + assert(!data.IsEmpty()); + assert(frame_length <= data.size); + + sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; + if (sample_rate == 0) + break; + } + + decoder_buffer_consume(buffer, frame_length); + + if (estimate && frames == 128) { + /* if this is a remote file, don't slurp the + whole file just for checking the song + duration; instead, stop after some time and + extrapolate the song duration from what we + have until now */ + + const auto offset = is.GetOffset() + - decoder_buffer_available(buffer); + if (offset <= 0) + return -1; + + frames = (frames * file_size) / offset; + break; + } + } + + if (sample_rate == 0) + return -1; + + float frames_per_second = (float)sample_rate / 1024.0; + assert(frames_per_second > 0); + + return (float)frames / frames_per_second; +} + +static float +faad_song_duration(DecoderBuffer *buffer, InputStream &is) +{ + const auto size = is.GetSize(); + const size_t fileread = size >= 0 ? size : 0; + + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_need(buffer, 5)); + if (data.IsNull()) + return -1; + + size_t tagsize = 0; + if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) { + /* skip the ID3 tag */ + + tagsize = (data.data[6] << 21) | (data.data[7] << 14) | + (data.data[8] << 7) | (data.data[9] << 0); + + tagsize += 10; + + if (!decoder_buffer_skip(buffer, tagsize)) + return -1; + + data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_need(buffer, 5)); + if (data.IsNull()) + return -1; + } + + if (data.size >= 8 && adts_check_frame(data.data) > 0) { + /* obtain the duration from the ADTS header */ + + if (!is.IsSeekable()) + return -1; + + float song_length = adts_song_duration(buffer); + + is.LockSeek(tagsize, IgnoreError()); + + decoder_buffer_clear(buffer); + + return song_length; + } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) { + /* obtain the duration from the ADIF header */ + size_t skip_size = (data.data[4] & 0x80) ? 9 : 0; + + if (8 + skip_size > data.size) + /* not enough data yet; skip parsing this + header */ + return -1; + + unsigned bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) | + (data.data[5 + skip_size] << 11) | + (data.data[6 + skip_size] << 3) | + (data.data[7 + skip_size] & 0xE0); + + if (fileread != 0 && bit_rate != 0) + return fileread * 8.0 / bit_rate; + else + return fileread; + } else + return -1; +} + +static NeAACDecHandle +faad_decoder_new() +{ + const NeAACDecHandle decoder = NeAACDecOpen(); + + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + config->downMatrix = 1; + config->dontUpSampleImplicitSBR = 0; + NeAACDecSetConfiguration(decoder, config); + + return decoder; +} + +/** + * Wrapper for NeAACDecInit() which works around some API + * inconsistencies in libfaad. + */ +static bool +faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, + AudioFormat &audio_format, Error &error) +{ + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) { + error.Set(faad_decoder_domain, "Empty file"); + return false; + } + + uint8_t channels; + uint32_t sample_rate; +#ifdef HAVE_FAAD_LONG + /* neaacdec.h declares all arguments as "unsigned long", but + internally expects uint32_t pointers. To avoid gcc + warnings, use this workaround. */ + unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate; +#else + uint32_t *sample_rate_p = &sample_rate; +#endif + long nbytes = NeAACDecInit(decoder, + /* deconst hack, libfaad requires this */ + const_cast<unsigned char *>(data.data), + data.size, + sample_rate_p, &channels); + if (nbytes < 0) { + error.Set(faad_decoder_domain, "Not an AAC stream"); + return false; + } + + decoder_buffer_consume(buffer, nbytes); + + return audio_format_init_checked(audio_format, sample_rate, + SampleFormat::S16, channels, error); +} + +/** + * Wrapper for NeAACDecDecode() which works around some API + * inconsistencies in libfaad. + */ +static const void * +faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, + NeAACDecFrameInfo *frame_info) +{ + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) + return nullptr; + + return NeAACDecDecode(decoder, frame_info, + /* deconst hack, libfaad requires this */ + const_cast<uint8_t *>(data.data), + data.size); +} + +/** + * Get a song file's total playing time in seconds, as a float. + * Returns 0 if the duration is unknown, and a negative value if the + * file is invalid. + */ +static float +faad_get_file_time_float(InputStream &is) +{ + DecoderBuffer *buffer = + decoder_buffer_new(nullptr, is, + FAAD_MIN_STREAMSIZE * MAX_CHANNELS); + float length = faad_song_duration(buffer, is); + + if (length < 0) { + NeAACDecHandle decoder = faad_decoder_new(); + + decoder_buffer_fill(buffer); + + AudioFormat audio_format; + if (faad_decoder_init(decoder, buffer, audio_format, + IgnoreError())) + length = 0; + + NeAACDecClose(decoder); + } + + decoder_buffer_free(buffer); + + return length; +} + +/** + * Get a song file's total playing time in seconds, as an int. + * Returns 0 if the duration is unknown, and a negative value if the + * file is invalid. + */ +static int +faad_get_file_time(InputStream &is) +{ + float length = faad_get_file_time_float(is); + if (length < 0) + return -1; + + return int(length + 0.5); +} + +static void +faad_stream_decode(Decoder &mpd_decoder, InputStream &is, + DecoderBuffer *buffer, const NeAACDecHandle decoder) +{ + const float total_time = faad_song_duration(buffer, is); + + if (adts_find_frame(buffer) == 0) + return; + + /* initialize it */ + + Error error; + AudioFormat audio_format; + if (!faad_decoder_init(decoder, buffer, audio_format, error)) { + LogError(error); + return; + } + + /* initialize the MPD core */ + + decoder_initialized(mpd_decoder, audio_format, false, total_time); + + /* the decoder loop */ + + DecoderCommand cmd; + unsigned bit_rate = 0; + do { + /* find the next frame */ + + const size_t frame_size = adts_find_frame(buffer); + if (frame_size == 0) + /* end of file */ + break; + + /* decode it */ + + NeAACDecFrameInfo frame_info; + const void *const decoded = + faad_decoder_decode(decoder, buffer, &frame_info); + + if (frame_info.error > 0) { + FormatWarning(faad_decoder_domain, + "error decoding AAC stream: %s", + NeAACDecGetErrorMessage(frame_info.error)); + break; + } + + if (frame_info.channels != audio_format.channels) { + FormatDefault(faad_decoder_domain, + "channel count changed from %u to %u", + audio_format.channels, frame_info.channels); + break; + } + + if (frame_info.samplerate != audio_format.sample_rate) { + FormatDefault(faad_decoder_domain, + "sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); + break; + } + + decoder_buffer_consume(buffer, frame_info.bytesconsumed); + + /* update bit rate and position */ + + if (frame_info.samples > 0) { + bit_rate = frame_info.bytesconsumed * 8.0 * + frame_info.channels * audio_format.sample_rate / + frame_info.samples / 1000 + 0.5; + } + + /* send PCM samples to MPD */ + + cmd = decoder_data(mpd_decoder, is, decoded, + (size_t)frame_info.samples * 2, + bit_rate); + } while (cmd != DecoderCommand::STOP); +} + +static void +faad_stream_decode(Decoder &mpd_decoder, InputStream &is) +{ + DecoderBuffer *buffer = + decoder_buffer_new(&mpd_decoder, is, + FAAD_MIN_STREAMSIZE * MAX_CHANNELS); + + /* create the libfaad decoder */ + + const NeAACDecHandle decoder = faad_decoder_new(); + + faad_stream_decode(mpd_decoder, is, buffer, decoder); + + /* cleanup */ + + NeAACDecClose(decoder); + decoder_buffer_free(buffer); +} + +static bool +faad_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + int file_time = faad_get_file_time(is); + if (file_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, file_time); + return true; +} + +static const char *const faad_suffixes[] = { "aac", nullptr }; +static const char *const faad_mime_types[] = { + "audio/aac", "audio/aacp", nullptr +}; + +const DecoderPlugin faad_decoder_plugin = { + "faad", + nullptr, + nullptr, + faad_stream_decode, + nullptr, + nullptr, + faad_scan_stream, + nullptr, + faad_suffixes, + faad_mime_types, +}; diff --git a/src/decoder/plugins/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx new file mode 100644 index 000000000..968433e9b --- /dev/null +++ b/src/decoder/plugins/FaadDecoderPlugin.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_FAAD_DECODER_PLUGIN_HXX +#define MPD_FAAD_DECODER_PLUGIN_HXX + +extern const struct DecoderPlugin faad_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx new file mode 100644 index 000000000..3c0747514 --- /dev/null +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -0,0 +1,740 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "FfmpegMetaData.hxx" +#include "tag/TagHandler.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "LogV.hxx" + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavformat/avio.h> +#include <libavutil/avutil.h> +#include <libavutil/log.h> +#include <libavutil/mathematics.h> + +#if LIBAVUTIL_VERSION_MAJOR >= 53 +#include <libavutil/frame.h> +#endif +} + +#include <assert.h> +#include <string.h> + +static constexpr Domain ffmpeg_domain("ffmpeg"); + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +static LogLevel +import_ffmpeg_level(int level) +{ + if (level <= AV_LOG_FATAL) + return LogLevel::ERROR; + + if (level <= AV_LOG_WARNING) + return LogLevel::WARNING; + + if (level <= AV_LOG_INFO) + return LogLevel::INFO; + + return LogLevel::DEBUG; +} + +static void +mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, + const char *fmt, va_list vl) +{ + const AVClass * cls = nullptr; + + if (ptr != nullptr) + cls = *(const AVClass *const*)ptr; + + if (cls != nullptr) { + char domain[64]; + snprintf(domain, sizeof(domain), "%s/%s", + ffmpeg_domain.GetName(), cls->item_name(ptr)); + const Domain d(domain); + LogFormatV(d, import_ffmpeg_level(level), fmt, vl); + } +} + +struct AvioStream { + Decoder *const decoder; + InputStream &input; + + AVIOContext *io; + + unsigned char buffer[8192]; + + AvioStream(Decoder *_decoder, InputStream &_input) + :decoder(_decoder), input(_input), io(nullptr) {} + + ~AvioStream() { + if (io != nullptr) + av_free(io); + } + + bool Open(); +}; + +static int +mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) +{ + AvioStream *stream = (AvioStream *)opaque; + + return decoder_read(stream->decoder, stream->input, + (void *)buf, size); +} + +static int64_t +mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) +{ + AvioStream *stream = (AvioStream *)opaque; + + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + pos += stream->input.GetOffset(); + break; + + case SEEK_END: + if (!stream->input.KnownSize()) + return -1; + + pos += stream->input.GetSize(); + break; + + case AVSEEK_SIZE: + return stream->input.GetSize(); + + default: + return -1; + } + + if (!stream->input.LockSeek(pos, IgnoreError())) + return -1; + + return stream->input.GetOffset(); +} + +bool +AvioStream::Open() +{ + io = avio_alloc_context(buffer, sizeof(buffer), + false, this, + mpd_ffmpeg_stream_read, nullptr, + input.IsSeekable() + ? mpd_ffmpeg_stream_seek : nullptr); + return io != nullptr; +} + +/** + * API compatibility wrapper for av_open_input_stream() and + * avformat_open_input(). + */ +static int +mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, + AVIOContext *pb, + const char *filename, + AVInputFormat *fmt) +{ + AVFormatContext *context = avformat_alloc_context(); + if (context == nullptr) + return AVERROR(ENOMEM); + + context->pb = pb; + *ic_ptr = context; + return avformat_open_input(ic_ptr, filename, fmt, nullptr); +} + +static bool +ffmpeg_init(gcc_unused const config_param ¶m) +{ + av_log_set_callback(mpd_ffmpeg_log_callback); + + av_register_all(); + return true; +} + +static int +ffmpeg_find_audio_stream(const AVFormatContext *format_context) +{ + for (unsigned i = 0; i < format_context->nb_streams; ++i) + if (format_context->streams[i]->codec->codec_type == + AVMEDIA_TYPE_AUDIO) + return i; + + return -1; +} + +gcc_const +static double +time_from_ffmpeg(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) + / (double)1024; +} + +gcc_const +static int64_t +time_to_ffmpeg(double t, const AVRational time_base) +{ + return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024}, + time_base); +} + +/** + * Replace #AV_NOPTS_VALUE with the given fallback. + */ +static constexpr int64_t +timestamp_fallback(int64_t t, int64_t fallback) +{ + return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) + ? t + : fallback; +} + +/** + * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with + * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply + * assume that the stream's start time is zero, which appears to be + * the best way out of that situation. + */ +static int64_t +start_time_fallback(const AVStream &stream) +{ + return timestamp_fallback(stream.start_time, 0); +} + +static void +copy_interleave_frame2(uint8_t *dest, uint8_t **src, + unsigned nframes, unsigned nchannels, + unsigned sample_size) +{ + for (unsigned frame = 0; frame < nframes; ++frame) { + for (unsigned channel = 0; channel < nchannels; ++channel) { + memcpy(dest, src[channel] + frame * sample_size, + sample_size); + dest += sample_size; + } + } +} + +/** + * Copy PCM data from a AVFrame to an interleaved buffer. + */ +static int +copy_interleave_frame(const AVCodecContext *codec_context, + const AVFrame *frame, + uint8_t **output_buffer, + uint8_t **global_buffer, int *global_buffer_size) +{ + int plane_size; + const int data_size = + av_samples_get_buffer_size(&plane_size, + codec_context->channels, + frame->nb_samples, + codec_context->sample_fmt, 1); + if (data_size <= 0) + return data_size; + + if (av_sample_fmt_is_planar(codec_context->sample_fmt) && + codec_context->channels > 1) { + if(*global_buffer_size < data_size) { + av_freep(global_buffer); + + *global_buffer = (uint8_t*)av_malloc(data_size); + + if (!*global_buffer) + /* Not enough memory - shouldn't happen */ + return AVERROR(ENOMEM); + *global_buffer_size = data_size; + } + *output_buffer = *global_buffer; + copy_interleave_frame2(*output_buffer, frame->extended_data, + frame->nb_samples, + codec_context->channels, + av_get_bytes_per_sample(codec_context->sample_fmt)); + } else { + *output_buffer = frame->extended_data[0]; + } + + return data_size; +} + +static DecoderCommand +ffmpeg_send_packet(Decoder &decoder, InputStream &is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVStream *stream, + AVFrame *frame, + uint8_t **buffer, int *buffer_size) +{ + if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + time_from_ffmpeg(packet->pts - start_time_fallback(*stream), + stream->time_base)); + + AVPacket packet2 = *packet; + + uint8_t *output_buffer; + + DecoderCommand cmd = DecoderCommand::NONE; + while (packet2.size > 0 && cmd == DecoderCommand::NONE) { + int audio_size = 0; + int got_frame = 0; + int len = avcodec_decode_audio4(codec_context, + frame, &got_frame, + &packet2); + if (len >= 0 && got_frame) { + audio_size = copy_interleave_frame(codec_context, + frame, + &output_buffer, + buffer, buffer_size); + if (audio_size < 0) + len = audio_size; + } + + if (len < 0) { + /* if error, we skip the frame */ + LogDefault(ffmpeg_domain, + "decoding failed, frame skipped"); + break; + } + + packet2.data += len; + packet2.size -= len; + + if (audio_size <= 0) + continue; + + cmd = decoder_data(decoder, is, + output_buffer, audio_size, + codec_context->bit_rate / 1000); + } + return cmd; +} + +gcc_const +static SampleFormat +ffmpeg_sample_format(enum AVSampleFormat sample_fmt) +{ + switch (sample_fmt) { + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + return SampleFormat::S16; + + case AV_SAMPLE_FMT_S32: + case AV_SAMPLE_FMT_S32P: + return SampleFormat::S32; + + case AV_SAMPLE_FMT_FLTP: + return SampleFormat::FLOAT; + + default: + break; + } + + char buffer[64]; + const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer), + sample_fmt); + if (name != nullptr) + FormatError(ffmpeg_domain, + "Unsupported libavcodec SampleFormat value: %s (%d)", + name, sample_fmt); + else + FormatError(ffmpeg_domain, + "Unsupported libavcodec SampleFormat value: %d", + sample_fmt); + return SampleFormat::UNDEFINED; +} + +static AVInputFormat * +ffmpeg_probe(Decoder *decoder, InputStream &is) +{ + enum { + BUFFER_SIZE = 16384, + PADDING = 16, + }; + + unsigned char buffer[BUFFER_SIZE]; + size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); + if (nbytes <= PADDING || !is.LockRewind(IgnoreError())) + return nullptr; + + /* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes + beyond the declared buffer limit, which makes valgrind + angry; this workaround removes some padding from the buffer + size */ + nbytes -= PADDING; + + AVProbeData avpd; + avpd.buf = buffer; + avpd.buf_size = nbytes; + avpd.filename = is.GetURI(); + + return av_probe_input_format(&avpd, true); +} + +static void +ffmpeg_decode(Decoder &decoder, InputStream &input) +{ + AVInputFormat *input_format = ffmpeg_probe(&decoder, input); + if (input_format == nullptr) + return; + + FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", + input_format->name, input_format->long_name); + + AvioStream stream(&decoder, input); + if (!stream.Open()) { + LogError(ffmpeg_domain, "Failed to open stream"); + return; + } + + //ffmpeg works with ours "fileops" helper + AVFormatContext *format_context = nullptr; + if (mpd_ffmpeg_open_input(&format_context, stream.io, + input.GetURI(), + input_format) != 0) { + LogError(ffmpeg_domain, "Open failed"); + return; + } + + const int find_result = + avformat_find_stream_info(format_context, nullptr); + if (find_result < 0) { + LogError(ffmpeg_domain, "Couldn't find stream info"); + avformat_close_input(&format_context); + return; + } + + int audio_stream = ffmpeg_find_audio_stream(format_context); + if (audio_stream == -1) { + LogError(ffmpeg_domain, "No audio stream inside"); + avformat_close_input(&format_context); + return; + } + + AVStream *av_stream = format_context->streams[audio_stream]; + + AVCodecContext *codec_context = av_stream->codec; + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) + const AVCodecDescriptor *codec_descriptor = + avcodec_descriptor_get(codec_context->codec_id); + if (codec_descriptor != nullptr) + FormatDebug(ffmpeg_domain, "codec '%s'", + codec_descriptor->name); +#else + if (codec_context->codec_name[0] != 0) + FormatDebug(ffmpeg_domain, "codec '%s'", + codec_context->codec_name); +#endif + + AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); + + if (!codec) { + LogError(ffmpeg_domain, "Unsupported audio codec"); + avformat_close_input(&format_context); + return; + } + + const SampleFormat sample_format = + ffmpeg_sample_format(codec_context->sample_fmt); + if (sample_format == SampleFormat::UNDEFINED) { + // (error message already done by ffmpeg_sample_format()) + avformat_close_input(&format_context); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + codec_context->sample_rate, + sample_format, + codec_context->channels, error)) { + LogError(error); + avformat_close_input(&format_context); + return; + } + + /* the audio format must be read from AVCodecContext by now, + because avcodec_open() has been demonstrated to fill bogus + values into AVCodecContext.channels - a change that will be + reverted later by avcodec_decode_audio3() */ + + const int open_result = avcodec_open2(codec_context, codec, nullptr); + if (open_result < 0) { + LogError(ffmpeg_domain, "Could not open codec"); + avformat_close_input(&format_context); + return; + } + + int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE + ? format_context->duration / AV_TIME_BASE + : 0; + + decoder_initialized(decoder, audio_format, + input.IsSeekable(), total_time); + +#if LIBAVUTIL_VERSION_MAJOR >= 53 + AVFrame *frame = av_frame_alloc(); +#else + AVFrame *frame = avcodec_alloc_frame(); +#endif + if (!frame) { + LogError(ffmpeg_domain, "Could not allocate frame"); + avformat_close_input(&format_context); + return; + } + + uint8_t *interleaved_buffer = nullptr; + int interleaved_buffer_size = 0; + + DecoderCommand cmd; + do { + AVPacket packet; + if (av_read_frame(format_context, &packet) < 0) + /* end of file */ + break; + + if (packet.stream_index == audio_stream) + cmd = ffmpeg_send_packet(decoder, input, + &packet, codec_context, + av_stream, + frame, + &interleaved_buffer, &interleaved_buffer_size); + else + cmd = decoder_get_command(decoder); + + av_free_packet(&packet); + + if (cmd == DecoderCommand::SEEK) { + int64_t where = + time_to_ffmpeg(decoder_seek_where(decoder), + av_stream->time_base) + + start_time_fallback(*av_stream); + + if (av_seek_frame(format_context, audio_stream, where, + AVSEEK_FLAG_ANY) < 0) + decoder_seek_error(decoder); + else { + avcodec_flush_buffers(codec_context); + decoder_command_finished(decoder); + } + } + } while (cmd != DecoderCommand::STOP); + +#if LIBAVUTIL_VERSION_MAJOR >= 53 + av_frame_free(&frame); +#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) + avcodec_free_frame(&frame); +#else + av_freep(&frame); +#endif + av_freep(&interleaved_buffer); + + avcodec_close(codec_context); + avformat_close_input(&format_context); +} + +//no tag reading in ffmpeg, check if playable +static bool +ffmpeg_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + AVInputFormat *input_format = ffmpeg_probe(nullptr, is); + if (input_format == nullptr) + return false; + + AvioStream stream(nullptr, is); + if (!stream.Open()) + return false; + + AVFormatContext *f = nullptr; + if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(), + input_format) != 0) + return false; + + const int find_result = + avformat_find_stream_info(f, nullptr); + if (find_result < 0) { + avformat_close_input(&f); + return false; + } + + if (f->duration != (int64_t)AV_NOPTS_VALUE) + tag_handler_invoke_duration(handler, handler_ctx, + f->duration / AV_TIME_BASE); + + ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); + int idx = ffmpeg_find_audio_stream(f); + if (idx >= 0) + ffmpeg_scan_dictionary(f->streams[idx]->metadata, + handler, handler_ctx); + + avformat_close_input(&f); + return true; +} + +/** + * A list of extensions found for the formats supported by ffmpeg. + * This list is current as of 02-23-09; To find out if there are more + * supported formats, check the ffmpeg changelog since this date for + * more formats. + */ +static const char *const ffmpeg_suffixes[] = { + "16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif", + "aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf", + "atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak", + "cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa", + "eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726", + "gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", + "m4a", "m4b", "m4v", + "mad", + "mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+", + "mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu", + "mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv", + "ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra", + "ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd", + "sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts", + "tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc", + "vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv", + "wve", + nullptr +}; + +static const char *const ffmpeg_mime_types[] = { + "application/flv", + "application/m4a", + "application/mp4", + "application/octet-stream", + "application/ogg", + "application/x-ms-wmz", + "application/x-ms-wmd", + "application/x-ogg", + "application/x-shockwave-flash", + "application/x-shorten", + "audio/8svx", + "audio/16sv", + "audio/aac", + "audio/ac3", + "audio/aiff" + "audio/amr", + "audio/basic", + "audio/flac", + "audio/m4a", + "audio/mp4", + "audio/mpeg", + "audio/musepack", + "audio/ogg", + "audio/qcelp", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-8svx", + "audio/x-16sv", + "audio/x-aac", + "audio/x-ac3", + "audio/x-aiff" + "audio/x-alaw", + "audio/x-au", + "audio/x-dca", + "audio/x-eac3", + "audio/x-flac", + "audio/x-gsm", + "audio/x-mace", + "audio/x-matroska", + "audio/x-monkeys-audio", + "audio/x-mpeg", + "audio/x-ms-wma", + "audio/x-ms-wax", + "audio/x-musepack", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + "audio/x-pn-realaudio", + "audio/x-pn-multirate-realaudio", + "audio/x-speex", + "audio/x-tta" + "audio/x-voc", + "audio/x-wav", + "audio/x-wma", + "audio/x-wv", + "video/anim", + "video/quicktime", + "video/msvideo", + "video/ogg", + "video/theora", + "video/webm", + "video/x-dv", + "video/x-flv", + "video/x-matroska", + "video/x-mjpeg", + "video/x-mpeg", + "video/x-ms-asf", + "video/x-msvideo", + "video/x-ms-wmv", + "video/x-ms-wvx", + "video/x-ms-wm", + "video/x-ms-wmx", + "video/x-nut", + "video/x-pva", + "video/x-theora", + "video/x-vid", + "video/x-wmv", + "video/x-xvid", + + /* special value for the "ffmpeg" input plugin: all streams by + the "ffmpeg" input plugin shall be decoded by this + plugin */ + "audio/x-mpd-ffmpeg", + + nullptr +}; + +const struct DecoderPlugin ffmpeg_decoder_plugin = { + "ffmpeg", + ffmpeg_init, + nullptr, + ffmpeg_decode, + nullptr, + nullptr, + ffmpeg_scan_stream, + nullptr, + ffmpeg_suffixes, + ffmpeg_mime_types +}; diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx new file mode 100644 index 000000000..0a3e78e4b --- /dev/null +++ b/src/decoder/plugins/FfmpegDecoderPlugin.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_DECODER_FFMPEG_HXX +#define MPD_DECODER_FFMPEG_HXX + +extern const struct DecoderPlugin ffmpeg_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx new file mode 100644 index 000000000..a39466945 --- /dev/null +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegMetaData.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagHandler.hxx" + +static const struct tag_table ffmpeg_tags[] = { + { "year", TAG_DATE }, + { "author-sort", TAG_ARTIST_SORT }, + { "album_artist", TAG_ALBUM_ARTIST }, + { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, + + /* sentinel */ + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +ffmpeg_copy_metadata(TagType type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *mt = nullptr; + + while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + type, mt->value); +} + +static void +ffmpeg_scan_pairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *i = nullptr; + + while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) + tag_handler_invoke_pair(handler, handler_ctx, + i->key, i->value); +} + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], + handler, handler_ctx); + + for (const struct tag_table *i = ffmpeg_tags; + i->name != nullptr; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + + if (handler->pair != nullptr) + ffmpeg_scan_pairs(dict, handler, handler_ctx); +} diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx new file mode 100644 index 000000000..5eb41db68 --- /dev/null +++ b/src/decoder/plugins/FfmpegMetaData.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_METADATA_HXX +#define MPD_FFMPEG_METADATA_HXX + +extern "C" { +#include <libavutil/dict.h> +} + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +struct tag_handler; + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/plugins/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx new file mode 100644 index 000000000..f06a52cf8 --- /dev/null +++ b/src/decoder/plugins/FlacCommon.cxx @@ -0,0 +1,190 @@ +/* + * 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. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#include "config.h" +#include "FlacCommon.hxx" +#include "FlacMetadata.hxx" +#include "FlacPcm.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +flac_data::flac_data(Decoder &_decoder, + InputStream &_input_stream) + :FlacInput(_input_stream, &_decoder), + initialized(false), unsupported(false), + total_frames(0), first_frame(0), next_frame(0), position(0), + decoder(_decoder), input_stream(_input_stream) +{ +} + +static SampleFormat +flac_sample_format(unsigned bits_per_sample) +{ + switch (bits_per_sample) { + case 8: + return SampleFormat::S8; + + case 16: + return SampleFormat::S16; + + case 24: + return SampleFormat::S24_P32; + + case 32: + return SampleFormat::S32; + + default: + return SampleFormat::UNDEFINED; + } +} + +static void +flac_got_stream_info(struct flac_data *data, + const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + if (data->initialized || data->unsupported) + return; + + Error error; + if (!audio_format_init_checked(data->audio_format, + stream_info->sample_rate, + flac_sample_format(stream_info->bits_per_sample), + stream_info->channels, error)) { + LogError(error); + data->unsupported = true; + return; + } + + data->frame_size = data->audio_format.GetFrameSize(); + + if (data->total_frames == 0) + data->total_frames = stream_info->total_samples; + + data->initialized = true; +} + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + struct flac_data *data) +{ + if (data->unsupported) + return; + + ReplayGainInfo rgi; + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + flac_got_stream_info(data, &block->data.stream_info); + break; + + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + if (flac_parse_replay_gain(rgi, block)) + decoder_replay_gain(data->decoder, &rgi); + + decoder_mixramp(data->decoder, flac_parse_mixramp(block)); + + data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment); + break; + + default: + break; + } +} + +/** + * This function attempts to call decoder_initialized() in case there + * was no STREAMINFO block. This is allowed for nonseekable streams, + * where the server sends us only a part of the file, without + * providing the STREAMINFO block from the beginning of the file + * (e.g. when seeking with SqueezeBox Server). + */ +static bool +flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) +{ + if (data->unsupported) + return false; + + Error error; + if (!audio_format_init_checked(data->audio_format, + header->sample_rate, + flac_sample_format(header->bits_per_sample), + header->channels, error)) { + LogError(error); + data->unsupported = true; + return false; + } + + data->frame_size = data->audio_format.GetFrameSize(); + + decoder_initialized(data->decoder, data->audio_format, + data->input_stream.IsSeekable(), + (float)data->total_frames / + (float)data->audio_format.sample_rate); + + data->initialized = true; + + return true; +} + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes) +{ + void *buffer; + unsigned bit_rate; + + if (!data->initialized && !flac_got_first_frame(data, &frame->header)) + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + size_t buffer_size = frame->header.blocksize * data->frame_size; + buffer = data->buffer.Get(buffer_size); + + flac_convert(buffer, frame->header.channels, + data->audio_format.format, buf, + 0, frame->header.blocksize); + + if (nbytes > 0) + bit_rate = nbytes * 8 * frame->header.sample_rate / + (1000 * frame->header.blocksize); + else + bit_rate = 0; + + auto cmd = decoder_data(data->decoder, data->input_stream, + buffer, buffer_size, + bit_rate); + data->next_frame += frame->header.blocksize; + switch (cmd) { + case DecoderCommand::NONE: + case DecoderCommand::START: + break; + + case DecoderCommand::STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + case DecoderCommand::SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} diff --git a/src/decoder/plugins/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx new file mode 100644 index 000000000..34ce0a3fc --- /dev/null +++ b/src/decoder/plugins/FlacCommon.hxx @@ -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. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#ifndef MPD_FLAC_COMMON_HXX +#define MPD_FLAC_COMMON_HXX + +#include "FlacInput.hxx" +#include "../DecoderAPI.hxx" +#include "pcm/PcmBuffer.hxx" + +#include <FLAC/stream_decoder.h> + +struct flac_data : public FlacInput { + PcmBuffer buffer; + + /** + * The size of one frame in the output buffer. + */ + unsigned frame_size; + + /** + * Has decoder_initialized() been called yet? + */ + bool initialized; + + /** + * Does the FLAC file contain an unsupported audio format? + */ + bool unsupported; + + /** + * The validated audio format of the FLAC file. This + * attribute is defined if "initialized" is true. + */ + AudioFormat audio_format; + + /** + * The total number of frames in this song. The decoder + * plugin may initialize this attribute to override the value + * provided by libFLAC (e.g. for sub songs from a CUE sheet). + */ + FLAC__uint64 total_frames; + + /** + * The number of the first frame in this song. This is only + * non-zero if playing sub songs from a CUE sheet. + */ + FLAC__uint64 first_frame; + + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; + + FLAC__uint64 position; + + Decoder &decoder; + InputStream &input_stream; + + Tag tag; + + flac_data(Decoder &decoder, InputStream &input_stream); +}; + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + struct flac_data *data); + +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx new file mode 100644 index 000000000..39a0fce73 --- /dev/null +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "FlacDecoderPlugin.h" +#include "FlacDomain.hxx" +#include "FlacCommon.hxx" +#include "FlacMetadata.hxx" +#include "OggCodec.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + + case FLAC__STREAM_DECODER_OGG_ERROR: + case FLAC__STREAM_DECODER_SEEK_ERROR: + case FLAC__STREAM_DECODER_ABORTED: + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + case FLAC__STREAM_DECODER_UNINITIALIZED: + break; + } + + LogError(flac_domain, FLAC__StreamDecoderStateString[state]); +} + +static void flacMetadata(gcc_unused const FLAC__StreamDecoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (struct flac_data *) vdata); +} + +static FLAC__StreamDecoderWriteStatus +flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, + const FLAC__int32 *const buf[], void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + FLAC__uint64 nbytes = 0; + + if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { + if (data->position > 0 && nbytes > data->position) { + nbytes -= data->position; + data->position += nbytes; + } else { + data->position = nbytes; + nbytes = 0; + } + } else + nbytes = 0; + + return flac_common_write(data, frame, buf, nbytes); +} + +static bool +flac_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.Read(path_fs.c_str())) { + FormatDebug(flac_domain, + "Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +flac_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.Read(is)) { + FormatDebug(flac_domain, + "Failed to read FLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == nullptr) { + LogError(flac_domain, + "FLAC__stream_decoder_new() failed"); + return nullptr; + } + + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + LogDebug(flac_domain, + "FLAC__stream_decoder_set_metadata_respond() has failed"); + + return sd; +} + +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + FLAC__uint64 duration) +{ + data->total_frames = duration; + + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + LogWarning(flac_domain, "problem reading metadata"); + return false; + } + + if (data->initialized) { + /* done */ + decoder_initialized(data->decoder, data->audio_format, + data->input_stream.IsSeekable(), + (float)data->total_frames / + (float)data->audio_format.sample_rate); + return true; + } + + if (data->input_stream.IsSeekable()) + /* allow the workaround below only for nonseekable + streams*/ + return false; + + /* no stream_info packet found; try to initialize the decoder + from the first frame header */ + FLAC__stream_decoder_process_single(sd); + return data->initialized; +} + +static void +flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, + FLAC__uint64 t_start, FLAC__uint64 t_end) +{ + Decoder &decoder = data->decoder; + + data->first_frame = t_start; + + while (true) { + DecoderCommand cmd; + if (!data->tag.IsEmpty()) { + cmd = decoder_tag(data->decoder, data->input_stream, + std::move(data->tag)); + data->tag.Clear(); + } else + cmd = decoder_get_command(decoder); + + if (cmd == DecoderCommand::SEEK) { + FLAC__uint64 seek_sample = t_start + + decoder_seek_where(decoder) * + data->audio_format.sample_rate; + if (seek_sample >= t_start && + (t_end == 0 || seek_sample <= t_end) && + FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { + data->next_frame = seek_sample; + data->position = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else if (cmd == DecoderCommand::STOP || + FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) + break; + + if (t_end != 0 && data->next_frame >= t_end) + /* end of this sub track */ + break; + + if (!FLAC__stream_decoder_process_single(flac_dec) && + decoder_get_command(decoder) == DecoderCommand::NONE) { + /* a failure that was not triggered by a + decoder command */ + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + break; + } + } +} + +static FLAC__StreamDecoderInitStatus +stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_ogg_stream(flac_dec, + FlacInput::Read, + FlacInput::Seek, + FlacInput::Tell, + FlacInput::Length, + FlacInput::Eof, + flac_write_cb, + flacMetadata, + FlacInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return FLAC__stream_decoder_init_stream(flac_dec, + FlacInput::Read, + FlacInput::Seek, + FlacInput::Tell, + FlacInput::Length, + FlacInput::Eof, + flac_write_cb, + flacMetadata, + FlacInput::Error, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) +{ + return is_ogg + ? stream_init_oggflac(flac_dec, data) + : stream_init_flac(flac_dec, data); +} + +static void +flac_decode_internal(Decoder &decoder, + InputStream &input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + + flac_dec = flac_decoder_new(); + if (flac_dec == nullptr) + return; + + struct flac_data data(decoder, input_stream); + + FLAC__StreamDecoderInitStatus status = + stream_init(flac_dec, &data, is_ogg); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + FLAC__stream_decoder_delete(flac_dec); + LogWarning(flac_domain, + FLAC__StreamDecoderInitStatusString[status]); + return; + } + + if (!flac_decoder_initialize(&data, flac_dec, 0)) { + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); +} + +static void +flac_decode(Decoder &decoder, InputStream &input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +static bool +oggflac_init(gcc_unused const config_param ¶m) +{ + return !!FLAC_API_SUPPORTS_OGG_FLAC; +} + +static bool +oggflac_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.ReadOgg(path_fs.c_str())) { + FormatDebug(flac_domain, + "Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static bool +oggflac_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + FlacMetadataChain chain; + if (!chain.ReadOgg(is)) { + FormatDebug(flac_domain, + "Failed to read OggFLAC tags: %s", + chain.GetStatusString()); + return false; + } + + chain.Scan(handler, handler_ctx); + return true; +} + +static void +oggflac_decode(Decoder &decoder, InputStream &input_stream) +{ + if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_FLAC) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream.LockRewind(IgnoreError()); + + flac_decode_internal(decoder, input_stream, true); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-flac+ogg", + "audio/x-ogg", + nullptr +}; + +const struct DecoderPlugin oggflac_decoder_plugin = { + "oggflac", + oggflac_init, + nullptr, + oggflac_decode, + nullptr, + oggflac_scan_file, + oggflac_scan_stream, + nullptr, + oggflac_suffixes, + oggflac_mime_types, +}; + +static const char *const flac_suffixes[] = { "flac", nullptr }; +static const char *const flac_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + nullptr +}; + +const struct DecoderPlugin flac_decoder_plugin = { + "flac", + nullptr, + nullptr, + flac_decode, + nullptr, + flac_scan_file, + flac_scan_stream, + nullptr, + flac_suffixes, + flac_mime_types, +}; diff --git a/src/decoder/plugins/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h new file mode 100644 index 000000000..fcdecf869 --- /dev/null +++ b/src/decoder/plugins/FlacDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * 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_DECODER_FLAC_H +#define MPD_DECODER_FLAC_H + +extern const struct DecoderPlugin flac_decoder_plugin; +extern const struct DecoderPlugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx new file mode 100644 index 000000000..fc5cc5498 --- /dev/null +++ b/src/decoder/plugins/FlacDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FlacDomain.hxx" +#include "util/Domain.hxx" + +const Domain flac_domain("flac"); diff --git a/src/decoder/plugins/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx new file mode 100644 index 000000000..a06c6c6b4 --- /dev/null +++ b/src/decoder/plugins/FlacDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_DOMAIN_HXX +#define MPD_FLAC_DOMAIN_HXX + +#include "check.h" + +extern const class Domain flac_domain; + +#endif diff --git a/src/decoder/plugins/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx new file mode 100644 index 000000000..d37cea532 --- /dev/null +++ b/src/decoder/plugins/FlacIOHandle.cxx @@ -0,0 +1,134 @@ +/* + * 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 "FlacIOHandle.hxx" +#include "util/Error.hxx" +#include "Compiler.h" + +#include <errno.h> +#include <stdio.h> + +static size_t +FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + uint8_t *const p0 = (uint8_t *)ptr, *p = p0, + *const end = p0 + size * nmemb; + + /* libFLAC is very picky about short reads, and expects the IO + callback to fill the whole buffer (undocumented!) */ + + Error error; + while (p < end) { + size_t nbytes = is->LockRead(p, end - p, error); + if (nbytes == 0) { + if (!error.IsDefined()) + /* end of file */ + break; + + if (error.IsDomain(errno_domain)) + errno = error.GetCode(); + else + /* just some random non-zero + errno value */ + errno = EINVAL; + return 0; + } + + p += nbytes; + } + + /* libFLAC expects a clean errno after returning from the IO + callbacks (undocumented!) */ + errno = 0; + return (p - p0) / size; +} + +static int +FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence) +{ + InputStream *is = (InputStream *)handle; + + InputStream::offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is->GetOffset(); + break; + + case SEEK_END: + if (!is->KnownSize()) + return -1; + + offset += is->GetSize(); + break; + + default: + return -1; + } + + return is->LockSeek(offset, IgnoreError()) ? 0 : -1; +} + +static FLAC__int64 +FlacIOTell(FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + return is->GetOffset(); +} + +static int +FlacIOEof(FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + return is->LockIsEOF(); +} + +static int +FlacIOClose(gcc_unused FLAC__IOHandle handle) +{ + /* no-op because the libFLAC caller is repsonsible for closing + the #InputStream */ + + return 0; +} + +const FLAC__IOCallbacks flac_io_callbacks = { + FlacIORead, + nullptr, + nullptr, + nullptr, + FlacIOEof, + FlacIOClose, +}; + +const FLAC__IOCallbacks flac_io_callbacks_seekable = { + FlacIORead, + nullptr, + FlacIOSeek, + FlacIOTell, + FlacIOEof, + FlacIOClose, +}; diff --git a/src/decoder/plugins/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx new file mode 100644 index 000000000..90acc66af --- /dev/null +++ b/src/decoder/plugins/FlacIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * 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_FLAC_IO_HANDLE_HXX +#define MPD_FLAC_IO_HANDLE_HXX + +#include "Compiler.h" +#include "input/InputStream.hxx" + +#include <FLAC/callback.h> + +extern const FLAC__IOCallbacks flac_io_callbacks; +extern const FLAC__IOCallbacks flac_io_callbacks_seekable; + +static inline FLAC__IOHandle +ToFlacIOHandle(InputStream &is) +{ + return (FLAC__IOHandle)&is; +} + +static inline const FLAC__IOCallbacks & +GetFlacIOCallbacks(const InputStream &is) +{ + return is.IsSeekable() + ? flac_io_callbacks_seekable + : flac_io_callbacks; +} + +#endif diff --git a/src/decoder/plugins/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx new file mode 100644 index 000000000..5b4c3104d --- /dev/null +++ b/src/decoder/plugins/FlacInput.cxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FlacInput.hxx" +#include "FlacDomain.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Compiler.h" + +FLAC__StreamDecoderReadStatus +FlacInput::Read(FLAC__byte buffer[], size_t *bytes) +{ + size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes); + *bytes = r; + + if (r == 0) { + if (input_stream.LockIsEOF() || + (decoder != nullptr && + decoder_get_command(*decoder) != DecoderCommand::NONE)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +FLAC__StreamDecoderSeekStatus +FlacInput::Seek(FLAC__uint64 absolute_byte_offset) +{ + if (!input_stream.IsSeekable()) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + ::Error error; + if (!input_stream.LockSeek(absolute_byte_offset, error)) { + LogError(error); + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + } + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus +FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) +{ + if (!input_stream.IsSeekable()) + return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; + + *absolute_byte_offset = (FLAC__uint64)input_stream.GetOffset(); + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus +FlacInput::Length(FLAC__uint64 *stream_length) +{ + if (!input_stream.KnownSize()) + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *stream_length = (FLAC__uint64)input_stream.GetSize(); + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +FLAC__bool +FlacInput::Eof() +{ + return (decoder != nullptr && + decoder_get_command(*decoder) != DecoderCommand::NONE && + decoder_get_command(*decoder) != DecoderCommand::SEEK) || + input_stream.LockIsEOF(); +} + +void +FlacInput::Error(FLAC__StreamDecoderErrorStatus status) +{ + if (decoder == nullptr || + decoder_get_command(*decoder) != DecoderCommand::STOP) + LogWarning(flac_domain, + FLAC__StreamDecoderErrorStatusString[status]); +} + +FLAC__StreamDecoderReadStatus +FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, + void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Read(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus +FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Seek(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus +FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Tell(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus +FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Length(stream_length); +} + +FLAC__bool +FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder, + void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + return i->Eof(); +} + +void +FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + FlacInput *i = (FlacInput *)client_data; + + i->Error(status); +} + diff --git a/src/decoder/plugins/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx new file mode 100644 index 000000000..427abccb4 --- /dev/null +++ b/src/decoder/plugins/FlacInput.hxx @@ -0,0 +1,75 @@ +/* + * 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_FLAC_INPUT_HXX +#define MPD_FLAC_INPUT_HXX + +#include <FLAC/stream_decoder.h> + +struct Decoder; +class InputStream; + +/** + * This class wraps an #InputStream in libFLAC stream decoder + * callbacks. + */ +class FlacInput { + Decoder *const decoder; + + InputStream &input_stream; + +public: + FlacInput(InputStream &_input_stream, + Decoder *_decoder=nullptr) + :decoder(_decoder), input_stream(_input_stream) {} + +protected: + FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); + FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); + FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); + FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); + FLAC__bool Eof(); + void Error(FLAC__StreamDecoderErrorStatus status); + +public: + static FLAC__StreamDecoderReadStatus + Read(const FLAC__StreamDecoder *flac_decoder, + FLAC__byte buffer[], size_t *bytes, void *client_data); + + static FLAC__StreamDecoderSeekStatus + Seek(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderTellStatus + Tell(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data); + + static FLAC__StreamDecoderLengthStatus + Length(const FLAC__StreamDecoder *flac_decoder, + FLAC__uint64 *stream_length, void *client_data); + + static FLAC__bool + Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); + + static void + Error(const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data); +}; + +#endif diff --git a/src/decoder/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx new file mode 100644 index 000000000..b921e8481 --- /dev/null +++ b/src/decoder/plugins/FlacMetadata.cxx @@ -0,0 +1,223 @@ +/* + * 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 "FlacMetadata.hxx" +#include "XiphTags.hxx" +#include "MixRampInfo.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "ReplayGainInfo.hxx" +#include "util/ASCII.hxx" +#include "util/SplitString.hxx" + +#include <string.h> + +static const char * +vorbis_comment_value(const FLAC__StreamMetadata *block, + const char *name) +{ + int offset = + FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + name); + if (offset < 0) + return nullptr; + + size_t name_length = strlen(name); + + const FLAC__StreamMetadata_VorbisComment_Entry &vc = + block->data.vorbis_comment.comments[offset]; + const char *comment = (const char *)vc.entry; + + /* 1 is for '=' */ + return comment + name_length + 1; +} + +static bool +flac_find_float_comment(const FLAC__StreamMetadata *block, + const char *cmnt, float *fl) +{ + const char *value = vorbis_comment_value(block, cmnt); + if (value == nullptr) + return false; + + *fl = (float)atof(value); + return true; +} + +bool +flac_parse_replay_gain(ReplayGainInfo &rgi, + const FLAC__StreamMetadata *block) +{ + rgi.Clear(); + + bool found = false; + if (flac_find_float_comment(block, "replaygain_album_gain", + &rgi.tuples[REPLAY_GAIN_ALBUM].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_album_peak", + &rgi.tuples[REPLAY_GAIN_ALBUM].peak)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_gain", + &rgi.tuples[REPLAY_GAIN_TRACK].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_peak", + &rgi.tuples[REPLAY_GAIN_TRACK].peak)) + found = true; + + return found; +} + +gcc_pure +static std::string +flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt) +{ + const char *value = vorbis_comment_value(block, cmnt); + if (value == nullptr) + return std::string(); + + return std::string(value); +} + +MixRampInfo +flac_parse_mixramp(const FLAC__StreamMetadata *block) +{ + MixRampInfo mix_ramp; + mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start")); + mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end")); + return mix_ramp; +} + +/** + * Checks if the specified name matches the entry's name, and if yes, + * returns the comment value; + */ +static const char * +flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name) +{ + size_t name_length = strlen(name); + const char *comment = (const char*)entry->entry; + + if (!StringEqualsCaseASCII(comment, name, name_length)) + return nullptr; + + if (comment[name_length] == '=') { + return comment + name_length + 1; + } + + return nullptr; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, TagType tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value = flac_comment_value(entry, name); + if (value != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static void +flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != nullptr) { + const char *comment = (const char *)entry->entry; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) + tag_handler_invoke_pair(handler, handler_ctx, + split.GetFirst(), + split.GetSecond()); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (flac_copy_comment(entry, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (flac_copy_comment(entry, + tag_item_names[i], (TagType)i, + handler, handler_ctx)) + return; +} + +static void +flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < comment->num_comments; ++i) + flac_scan_comment(&comment->comments[i], + handler, handler_ctx); +} + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx) +{ + switch (block->type) { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flac_scan_comments(&block->data.vorbis_comment, + handler, handler_ctx); + break; + + case FLAC__METADATA_TYPE_STREAMINFO: + if (block->data.stream_info.sample_rate > 0) + tag_handler_invoke_duration(handler, handler_ctx, + flac_duration(&block->data.stream_info)); + break; + + default: + break; + } +} + +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment) +{ + TagBuilder tag_builder; + flac_scan_comments(comment, &add_tag_handler, &tag_builder); + return tag_builder.Commit(); +} + +void +FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx) +{ + FLACMetadataIterator iterator(*this); + + do { + FLAC__StreamMetadata *block = iterator.GetBlock(); + if (block == nullptr) + break; + + flac_scan_metadata(block, handler, handler_ctx); + } while (iterator.Next()); +} diff --git a/src/decoder/plugins/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx new file mode 100644 index 000000000..e0449b2a2 --- /dev/null +++ b/src/decoder/plugins/FlacMetadata.hxx @@ -0,0 +1,140 @@ +/* + * 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_FLAC_METADATA_H +#define MPD_FLAC_METADATA_H + +#include "Compiler.h" +#include "FlacIOHandle.hxx" + +#include <FLAC/metadata.h> + +#include <assert.h> + +struct tag_handler; +class MixRampInfo; + +class FlacMetadataChain { + FLAC__Metadata_Chain *chain; + +public: + FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {} + + ~FlacMetadataChain() { + ::FLAC__metadata_chain_delete(chain); + } + + explicit operator FLAC__Metadata_Chain *() { + return chain; + } + + bool Read(const char *path) { + return ::FLAC__metadata_chain_read(chain, path); + } + + bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_with_callbacks(chain, + handle, + callbacks); + } + + bool Read(InputStream &is) { + return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); + } + + bool ReadOgg(const char *path) { + return ::FLAC__metadata_chain_read_ogg(chain, path); + } + + bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) { + return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain, + handle, + callbacks); + } + + bool ReadOgg(InputStream &is) { + return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is)); + } + + gcc_pure + FLAC__Metadata_ChainStatus GetStatus() const { + return ::FLAC__metadata_chain_status(chain); + } + + gcc_pure + const char *GetStatusString() const { + return FLAC__Metadata_ChainStatusString[GetStatus()]; + } + + void Scan(const tag_handler *handler, void *handler_ctx); +}; + +class FLACMetadataIterator { + FLAC__Metadata_Iterator *iterator; + +public: + FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {} + + FLACMetadataIterator(FlacMetadataChain &chain) + :iterator(::FLAC__metadata_iterator_new()) { + ::FLAC__metadata_iterator_init(iterator, + (FLAC__Metadata_Chain *)chain); + } + + ~FLACMetadataIterator() { + ::FLAC__metadata_iterator_delete(iterator); + } + + bool Next() { + return ::FLAC__metadata_iterator_next(iterator); + } + + gcc_pure + FLAC__StreamMetadata *GetBlock() { + return ::FLAC__metadata_iterator_get_block(iterator); + } +}; + +struct Tag; +struct ReplayGainInfo; + +static inline unsigned +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + assert(stream_info->sample_rate > 0); + + return (stream_info->total_samples + stream_info->sample_rate - 1) / + stream_info->sample_rate; +} + +bool +flac_parse_replay_gain(ReplayGainInfo &rgi, + const FLAC__StreamMetadata *block); + +MixRampInfo +flac_parse_mixramp(const FLAC__StreamMetadata *block); + +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment); + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/plugins/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx new file mode 100644 index 000000000..311500f26 --- /dev/null +++ b/src/decoder/plugins/FlacPcm.cxx @@ -0,0 +1,110 @@ +/* + * 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 "FlacPcm.hxx" + +#include <assert.h> + +static void flac_convert_stereo16(int16_t *dest, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + for (; position < end; ++position) { + *dest++ = buf[0][position]; + *dest++ = buf[1][position]; + } +} + +static void +flac_convert_16(int16_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +/** + * Note: this function also handles 24 bit files! + */ +static void +flac_convert_32(int32_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void +flac_convert_8(int8_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +void +flac_convert(void *dest, + unsigned int num_channels, SampleFormat sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) +{ + switch (sample_format) { + case SampleFormat::S16: + if (num_channels == 2) + flac_convert_stereo16((int16_t*)dest, buf, + position, end); + else + flac_convert_16((int16_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::S8: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + + case SampleFormat::FLOAT: + case SampleFormat::DSD: + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + } +} diff --git a/src/decoder/plugins/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx new file mode 100644 index 000000000..30c318725 --- /dev/null +++ b/src/decoder/plugins/FlacPcm.hxx @@ -0,0 +1,33 @@ +/* + * 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_FLAC_PCM_HXX +#define MPD_FLAC_PCM_HXX + +#include "AudioFormat.hxx" + +#include <FLAC/ordinals.h> + +void +flac_convert(void *dest, + unsigned int num_channels, SampleFormat sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end); + +#endif diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx new file mode 100644 index 000000000..bdf30baea --- /dev/null +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx @@ -0,0 +1,225 @@ +/* + * 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 "FluidsynthDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <fluidsynth.h> + +static constexpr Domain fluidsynth_domain("fluidsynth"); + +static unsigned sample_rate; +static const char *soundfont_path; + +/** + * Convert a fluidsynth log level to a GLib log level. + */ +static LogLevel +fluidsynth_level_to_mpd(enum fluid_log_level level) +{ + switch (level) { + case FLUID_PANIC: + case FLUID_ERR: + return LogLevel::ERROR; + + case FLUID_WARN: + return LogLevel::WARNING; + + case FLUID_INFO: + return LogLevel::INFO; + + case FLUID_DBG: + case LAST_LOG_LEVEL: + return LogLevel::DEBUG; + } + + /* invalid fluidsynth log level */ + return LogLevel::INFO; +} + +/** + * The fluidsynth logging callback. It forwards messages to the GLib + * logging library. + */ +static void +fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data) +{ + Log(fluidsynth_domain, + fluidsynth_level_to_mpd(fluid_log_level(level)), + message); +} + +static bool +fluidsynth_init(const config_param ¶m) +{ + Error error; + + sample_rate = param.GetBlockValue("sample_rate", 48000u); + if (!audio_check_sample_rate(sample_rate, error)) { + LogError(error); + return false; + } + + soundfont_path = param.GetBlockValue("soundfont", + "/usr/share/sounds/sf2/FluidR3_GM.sf2"); + + fluid_set_log_function(LAST_LOG_LEVEL, + fluidsynth_mpd_log_function, nullptr); + + return true; +} + +static void +fluidsynth_file_decode(Decoder &decoder, Path path_fs) +{ + char setting_sample_rate[] = "synth.sample-rate"; + /* + char setting_verbose[] = "synth.verbose"; + char setting_yes[] = "yes"; + */ + fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; + int ret; + + /* set up fluid settings */ + + settings = new_fluid_settings(); + if (settings == nullptr) + return; + + fluid_settings_setnum(settings, setting_sample_rate, sample_rate); + + /* + fluid_settings_setstr(settings, setting_verbose, setting_yes); + */ + + /* create the fluid synth */ + + synth = new_fluid_synth(settings); + if (synth == nullptr) { + delete_fluid_settings(settings); + return; + } + + ret = fluid_synth_sfload(synth, soundfont_path, true); + if (ret < 0) { + LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed"); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* create the fluid player */ + + player = new_fluid_player(synth); + if (player == nullptr) { + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + ret = fluid_player_add(player, path_fs.c_str()); + if (ret != 0) { + LogWarning(fluidsynth_domain, "fluid_player_add() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* start the player */ + + ret = fluid_player_play(player); + if (ret != 0) { + LogWarning(fluidsynth_domain, "fluid_player_play() failed"); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return; + } + + /* initialization complete - announce the audio format to the + MPD core */ + + const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2); + decoder_initialized(decoder, audio_format, false, -1); + + DecoderCommand cmd; + while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { + int16_t buffer[2048]; + const unsigned max_frames = ARRAY_SIZE(buffer) / 2; + + /* read samples from fluidsynth and send them to the + MPD core */ + + ret = fluid_synth_write_s16(synth, max_frames, + buffer, 0, 2, + buffer, 1, 2); + if (ret != 0) + break; + + cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer), + 0); + if (cmd != DecoderCommand::NONE) + break; + } + + /* clean up */ + + fluid_player_stop(player); + fluid_player_join(player); + + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); +} + +static bool +fluidsynth_scan_file(Path path_fs, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + return fluid_is_midifile(path_fs.c_str()); +} + +static const char *const fluidsynth_suffixes[] = { + "mid", + nullptr +}; + +const struct DecoderPlugin fluidsynth_decoder_plugin = { + "fluidsynth", + fluidsynth_init, + nullptr, + nullptr, + fluidsynth_file_decode, + fluidsynth_scan_file, + nullptr, + nullptr, + fluidsynth_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx new file mode 100644 index 000000000..cd8ec2d62 --- /dev/null +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.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_DECODER_FLUIDSYNTH_HXX +#define MPD_DECODER_FLUIDSYNTH_HXX + +extern const struct DecoderPlugin fluidsynth_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx new file mode 100644 index 000000000..469da2540 --- /dev/null +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -0,0 +1,298 @@ +/* + * 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 "GmeDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Alloc.hxx" +#include "util/FormatString.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <gme/gme.h> + +#define SUBTUNE_PREFIX "tune_" + +static constexpr Domain gme_domain("gme"); + +static constexpr unsigned GME_SAMPLE_RATE = 44100; +static constexpr unsigned GME_CHANNELS = 2; +static constexpr unsigned GME_BUFFER_FRAMES = 2048; +static constexpr unsigned GME_BUFFER_SAMPLES = + GME_BUFFER_FRAMES * GME_CHANNELS; + +/** + * returns the file path stripped of any /tune_xxx.* subtune + * suffix + */ +static char * +get_container_name(Path path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + char *path_container = xstrdup(path_fs.c_str()); + + char pat[64]; + snprintf(pat, sizeof(pat), "%s%s", + "*/" SUBTUNE_PREFIX "???.", + subtune_suffix); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + if (!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, nullptr)) { + g_pattern_spec_free(path_with_subtune); + return path_container; + } + + char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if (ptr != nullptr) + *ptr='\0'; + + g_pattern_spec_free(path_with_subtune); + return path_container; +} + +/** + * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune + * is appended. + */ +static int +get_song_num(Path path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + + char pat[64]; + snprintf(pat, sizeof(pat), "%s%s", + "*/" SUBTUNE_PREFIX "???.", + subtune_suffix); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + + if (g_pattern_match(path_with_subtune, + path_fs.length(), path_fs.data(), nullptr)) { + char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX); + g_pattern_spec_free(path_with_subtune); + if (!sub) + return 0; + + sub += strlen("/" SUBTUNE_PREFIX); + int song_num = strtol(sub, nullptr, 10); + + return song_num - 1; + } else { + g_pattern_spec_free(path_with_subtune); + return 0; + } +} + +static char * +gme_container_scan(Path path_fs, const unsigned int tnum) +{ + Music_Emu *emu; + const char *gme_err = gme_open_file(path_fs.c_str(), &emu, + GME_SAMPLE_RATE); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return nullptr; + } + + const unsigned num_songs = gme_track_count(emu); + gme_delete(emu); + /* if it only contains a single tune, don't treat as container */ + if (num_songs < 2) + return nullptr; + + const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + if (tnum <= num_songs){ + return FormatNew(SUBTUNE_PREFIX "%03u.%s", + tnum, subtune_suffix); + } else + return nullptr; +} + +static void +gme_file_decode(Decoder &decoder, Path path_fs) +{ + char *path_container = get_container_name(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + free(path_container); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return; + } + + gme_info_t *ti; + const int song_num = get_song_num(path_fs); + gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + gme_delete(emu); + return; + } + + const float song_len = ti->length > 0 + ? ti->length / 1000.0 + : -1.0; + + /* initialize the MPD decoder */ + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE, + SampleFormat::S16, GME_CHANNELS, + error)) { + LogError(error); + gme_free_info(ti); + gme_delete(emu); + return; + } + + decoder_initialized(decoder, audio_format, true, song_len); + + gme_err = gme_start_track(emu, song_num); + if (gme_err != nullptr) + LogWarning(gme_domain, gme_err); + + if (ti->length > 0) + gme_set_fade(emu, ti->length); + + /* play */ + DecoderCommand cmd; + do { + short buf[GME_BUFFER_SAMPLES]; + gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return; + } + + cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0); + if (cmd == DecoderCommand::SEEK) { + float where = decoder_seek_where(decoder); + gme_err = gme_seek(emu, int(where * 1000)); + if (gme_err != nullptr) + LogWarning(gme_domain, gme_err); + decoder_command_finished(decoder); + } + + if (gme_track_ended(emu)) + break; + } while (cmd != DecoderCommand::STOP); + + gme_free_info(ti); + gme_delete(emu); +} + +static bool +gme_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + char *path_container = get_container_name(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + free(path_container); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return false; + } + + const int song_num = get_song_num(path_fs); + + gme_info_t *ti; + gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + gme_delete(emu); + return false; + } + + assert(ti != nullptr); + + if (ti->length > 0) + tag_handler_invoke_duration(handler, handler_ctx, + ti->length / 100); + + if (ti->song != nullptr) { + if (gme_track_count(emu) > 1) { + /* start numbering subtunes from 1 */ + char tag_title[1024]; + snprintf(tag_title, sizeof(tag_title), + "%s (%d/%d)", + ti->song, song_num + 1, + gme_track_count(emu)); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + } else + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, ti->song); + } + + if (ti->author != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ARTIST, ti->author); + + if (ti->game != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ALBUM, ti->game); + + if (ti->comment != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_COMMENT, ti->comment); + + if (ti->copyright != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_DATE, ti->copyright); + + gme_free_info(ti); + gme_delete(emu); + + return true; +} + +static const char *const gme_suffixes[] = { + "ay", "gbs", "gym", "hes", "kss", "nsf", + "nsfe", "sap", "spc", "vgm", "vgz", + nullptr +}; + +extern const struct DecoderPlugin gme_decoder_plugin; +const struct DecoderPlugin gme_decoder_plugin = { + "gme", + nullptr, + nullptr, + nullptr, + gme_file_decode, + gme_scan_file, + nullptr, + gme_container_scan, + gme_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx new file mode 100644 index 000000000..f4885b6e4 --- /dev/null +++ b/src/decoder/plugins/GmeDecoderPlugin.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_DECODER_GME_HXX +#define MPD_DECODER_GME_HXX + +extern const struct DecoderPlugin gme_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx new file mode 100644 index 000000000..886aa1795 --- /dev/null +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -0,0 +1,1155 @@ +/* + * 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 "MadDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "config/ConfigGlobal.hxx" +#include "tag/TagId3.hxx" +#include "tag/TagRva2.hxx" +#include "tag/TagHandler.hxx" +#include "CheckAudioFormat.hxx" +#include "util/StringUtil.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mad.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#define FRAMES_CUSHION 2000 + +#define READ_BUFFER_SIZE 40960 + +enum mp3_action { + DECODE_SKIP = -3, + DECODE_BREAK = -2, + DECODE_CONT = -1, + DECODE_OK = 0 +}; + +enum muteframe { + MUTEFRAME_NONE, + MUTEFRAME_SKIP, + MUTEFRAME_SEEK +}; + +/* the number of samples of silence the decoder inserts at start */ +#define DECODERDELAY 529 + +#define DEFAULT_GAPLESS_MP3_PLAYBACK true + +static constexpr Domain mad_domain("mad"); + +static bool gapless_playback; + +static inline int32_t +mad_fixed_to_24_sample(mad_fixed_t sample) +{ + enum { + bits = 24, + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + /* round */ + sample = sample + (1L << (MAD_F_FRACBITS - bits)); + + /* clip */ + if (gcc_unlikely(sample > MAX)) + sample = MAX; + else if (gcc_unlikely(sample < MIN)) + sample = MIN; + + /* quantize */ + return sample >> (MAD_F_FRACBITS + 1 - bits); +} + +static void +mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, + unsigned int start, unsigned int end, + unsigned int num_channels) +{ + unsigned int i, c; + + for (i = start; i < end; ++i) { + for (c = 0; c < num_channels; ++c) + *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); + } +} + +static bool +mp3_plugin_init(gcc_unused const config_param ¶m) +{ + gapless_playback = config_get_bool(CONF_GAPLESS_MP3_PLAYBACK, + DEFAULT_GAPLESS_MP3_PLAYBACK); + return true; +} + +#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 + +struct MadDecoder { + struct mad_stream stream; + struct mad_frame frame; + struct mad_synth synth; + mad_timer_t timer; + unsigned char input_buffer[READ_BUFFER_SIZE]; + int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + float total_time; + float elapsed_time; + float seek_where; + enum muteframe mute_frame; + long *frame_offsets; + mad_timer_t *times; + unsigned long highest_frame; + unsigned long max_frames; + unsigned long current_frame; + unsigned int drop_start_frames; + unsigned int drop_end_frames; + unsigned int drop_start_samples; + unsigned int drop_end_samples; + bool found_replay_gain; + bool found_xing; + bool found_first_frame; + bool decoded_first_frame; + unsigned long bit_rate; + Decoder *const decoder; + InputStream &input_stream; + enum mad_layer layer; + + MadDecoder(Decoder *decoder, InputStream &input_stream); + ~MadDecoder(); + + bool Seek(long offset); + bool FillBuffer(); + void ParseId3(size_t tagsize, Tag **mpd_tag); + enum mp3_action DecodeNextFrameHeader(Tag **tag); + enum mp3_action DecodeNextFrame(); + + gcc_pure + InputStream::offset_type ThisFrameOffset() const; + + gcc_pure + InputStream::offset_type RestIncludingThisFrame() const; + + /** + * Attempt to calulcate the length of the song from filesize + */ + void FileSizeToSongLength(); + + bool DecodeFirstFrame(Tag **tag); + + gcc_pure + long TimeToFrame(double t) const; + + void UpdateTimerNextFrame(); + + /** + * Sends the synthesized current frame via decoder_data(). + */ + DecoderCommand SendPCM(unsigned i, unsigned pcm_length); + + /** + * Synthesize the current frame and send it via + * decoder_data(). + */ + DecoderCommand SyncAndSend(); + + bool Read(); +}; + +MadDecoder::MadDecoder(Decoder *_decoder, + InputStream &_input_stream) + :mute_frame(MUTEFRAME_NONE), + frame_offsets(nullptr), + times(nullptr), + highest_frame(0), max_frames(0), current_frame(0), + drop_start_frames(0), drop_end_frames(0), + drop_start_samples(0), drop_end_samples(0), + found_replay_gain(false), found_xing(false), + found_first_frame(false), decoded_first_frame(false), + decoder(_decoder), input_stream(_input_stream), + layer(mad_layer(0)) +{ + mad_stream_init(&stream); + mad_stream_options(&stream, MAD_OPTION_IGNORECRC); + mad_frame_init(&frame); + mad_synth_init(&synth); + mad_timer_reset(&timer); +} + +inline bool +MadDecoder::Seek(long offset) +{ + Error error; + if (!input_stream.LockSeek(offset, error)) + return false; + + mad_stream_buffer(&stream, input_buffer, 0); + stream.error = MAD_ERROR_NONE; + + return true; +} + +inline bool +MadDecoder::FillBuffer() +{ + size_t remaining, length; + unsigned char *dest; + + if (stream.next_frame != nullptr) { + remaining = stream.bufend - stream.next_frame; + memmove(input_buffer, stream.next_frame, remaining); + dest = input_buffer + remaining; + length = READ_BUFFER_SIZE - remaining; + } else { + remaining = 0; + length = READ_BUFFER_SIZE; + dest = input_buffer; + } + + /* we've exhausted the read buffer, so give up!, these potential + * mp3 frames are way too big, and thus unlikely to be mp3 frames */ + if (length == 0) + return false; + + length = decoder_read(decoder, input_stream, dest, length); + if (length == 0) + return false; + + mad_stream_buffer(&stream, input_buffer, length + remaining); + stream.error = MAD_ERROR_NONE; + + return true; +} + +#ifdef HAVE_ID3TAG +static bool +parse_id3_replay_gain_info(ReplayGainInfo &rgi, + struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + bool found = false; + + rgi.Clear(); + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (StringEqualsCaseASCII(key, "replaygain_track_gain")) { + rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value); + found = true; + } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) { + rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value); + found = true; + } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) { + rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value); + found = true; + } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) { + rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(value); + found = true; + } + + free(key); + free(value); + } + + return found || + /* fall back on RVA2 if no replaygain tags found */ + tag_rva2_parse(tag, rgi); +} +#endif + +#ifdef HAVE_ID3TAG +gcc_pure +static MixRampInfo +parse_id3_mixramp(struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + + MixRampInfo result; + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (StringEqualsCaseASCII(key, "mixramp_start")) { + result.SetStart(value); + } else if (StringEqualsCaseASCII(key, "mixramp_end")) { + result.SetEnd(value); + } + + free(key); + free(value); + } + + return result; +} +#endif + +inline void +MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) +{ +#ifdef HAVE_ID3TAG + struct id3_tag *id3_tag = nullptr; + id3_length_t count; + id3_byte_t const *id3_data; + id3_byte_t *allocated = nullptr; + + count = stream.bufend - stream.this_frame; + + if (tagsize <= count) { + id3_data = stream.this_frame; + mad_stream_skip(&(stream), tagsize); + } else { + allocated = new id3_byte_t[tagsize]; + memcpy(allocated, stream.this_frame, count); + mad_stream_skip(&(stream), count); + + if (!decoder_read_full(decoder, input_stream, + allocated + count, tagsize - count)) { + LogDebug(mad_domain, "error parsing ID3 tag"); + delete[] allocated; + return; + } + + id3_data = allocated; + } + + id3_tag = id3_tag_parse(id3_data, tagsize); + if (id3_tag == nullptr) { + delete[] allocated; + return; + } + + if (mpd_tag) { + Tag *tmp_tag = tag_id3_import(id3_tag); + if (tmp_tag != nullptr) { + delete *mpd_tag; + *mpd_tag = tmp_tag; + } + } + + if (decoder != nullptr) { + ReplayGainInfo rgi; + + if (parse_id3_replay_gain_info(rgi, id3_tag)) { + decoder_replay_gain(*decoder, &rgi); + found_replay_gain = true; + } + + decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag)); + } + + id3_tag_delete(id3_tag); + + delete[] allocated; +#else /* !HAVE_ID3TAG */ + (void)mpd_tag; + + /* This code is enabled when libid3tag is disabled. Instead + of parsing the ID3 frame, it just skips it. */ + + size_t count = stream.bufend - stream.this_frame; + + if (tagsize <= count) { + mad_stream_skip(&stream, tagsize); + } else { + mad_stream_skip(&stream, count); + decoder_skip(decoder, input_stream, tagsize - count); + } +#endif +} + +#ifndef HAVE_ID3TAG +/** + * This function emulates libid3tag when it is disabled. Instead of + * doing a real analyzation of the frame, it just checks whether the + * frame begins with the string "ID3". If so, it returns the length + * of the ID3 frame. + */ +static signed long +id3_tag_query(const void *p0, size_t length) +{ + const char *p = (const char *)p0; + + return length >= 10 && memcmp(p, "ID3", 3) == 0 + ? (p[8] << 7) + p[9] + 10 + : 0; +} +#endif /* !HAVE_ID3TAG */ + +enum mp3_action +MadDecoder::DecodeNextFrameHeader(Tag **tag) +{ + if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && + !FillBuffer()) + return DECODE_BREAK; + + if (mad_header_decode(&frame.header, &stream)) { + if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) { + signed long tagsize = id3_tag_query(stream.this_frame, + stream.bufend - + stream.this_frame); + + if (tagsize > 0) { + if (tag && !(*tag)) { + ParseId3((size_t)tagsize, tag); + } else { + mad_stream_skip(&stream, tagsize); + } + return DECODE_CONT; + } + } + if (MAD_RECOVERABLE(stream.error)) { + return DECODE_SKIP; + } else { + if (stream.error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + FormatWarning(mad_domain, + "unrecoverable frame level error: %s", + mad_stream_errorstr(&stream)); + return DECODE_BREAK; + } + } + } + + enum mad_layer new_layer = frame.header.layer; + if (layer == (mad_layer)0) { + if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) { + /* Only layer 2 and 3 have been tested to work */ + return DECODE_SKIP; + } + + layer = new_layer; + } else if (new_layer != layer) { + /* Don't decode frames with a different layer than the first */ + return DECODE_SKIP; + } + + return DECODE_OK; +} + +enum mp3_action +MadDecoder::DecodeNextFrame() +{ + if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && + !FillBuffer()) + return DECODE_BREAK; + + if (mad_frame_decode(&frame, &stream)) { + if (stream.error == MAD_ERROR_LOSTSYNC) { + signed long tagsize = id3_tag_query(stream.this_frame, + stream.bufend - + stream.this_frame); + if (tagsize > 0) { + mad_stream_skip(&stream, tagsize); + return DECODE_CONT; + } + } + if (MAD_RECOVERABLE(stream.error)) { + return DECODE_SKIP; + } else { + if (stream.error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + FormatWarning(mad_domain, + "unrecoverable frame level error: %s", + mad_stream_errorstr(&stream)); + return DECODE_BREAK; + } + } + } + + return DECODE_OK; +} + +/* xing stuff stolen from alsaplayer, and heavily modified by jat */ +#define XI_MAGIC (('X' << 8) | 'i') +#define NG_MAGIC (('n' << 8) | 'g') +#define IN_MAGIC (('I' << 8) | 'n') +#define FO_MAGIC (('f' << 8) | 'o') + +enum xing_magic { + XING_MAGIC_XING, /* VBR */ + XING_MAGIC_INFO /* CBR */ +}; + +struct xing { + long flags; /* valid fields (see below) */ + unsigned long frames; /* total number of frames */ + unsigned long bytes; /* total number of bytes */ + unsigned char toc[100]; /* 100-point seek table */ + long scale; /* VBR quality */ + enum xing_magic magic; /* header magic */ +}; + +enum { + XING_FRAMES = 0x00000001L, + XING_BYTES = 0x00000002L, + XING_TOC = 0x00000004L, + XING_SCALE = 0x00000008L +}; + +struct lame_version { + unsigned major; + unsigned minor; +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ + struct lame_version version; /* struct containing just the version */ + float peak; /* replaygain peak */ + float track_gain; /* replaygain track gain */ + float album_gain; /* replaygain album gain */ + int encoder_delay; /* # of added samples at start of mp3 */ + int encoder_padding; /* # of added samples at end of mp3 */ + int crc; /* CRC of the first 190 bytes of this frame */ +}; + +static bool +parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) +{ + unsigned long bits; + int bitlen; + int bitsleft; + int i; + + bitlen = *oldbitlen; + + if (bitlen < 16) + return false; + + bits = mad_bit_read(ptr, 16); + bitlen -= 16; + + if (bits == XI_MAGIC) { + if (bitlen < 16) + return false; + + if (mad_bit_read(ptr, 16) != NG_MAGIC) + return false; + + bitlen -= 16; + xing->magic = XING_MAGIC_XING; + } else if (bits == IN_MAGIC) { + if (bitlen < 16) + return false; + + if (mad_bit_read(ptr, 16) != FO_MAGIC) + return false; + + bitlen -= 16; + xing->magic = XING_MAGIC_INFO; + } + else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; + else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; + else + return false; + + if (bitlen < 32) + return false; + xing->flags = mad_bit_read(ptr, 32); + bitlen -= 32; + + if (xing->flags & XING_FRAMES) { + if (bitlen < 32) + return false; + xing->frames = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_BYTES) { + if (bitlen < 32) + return false; + xing->bytes = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_TOC) { + if (bitlen < 800) + return false; + for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); + bitlen -= 800; + } + + if (xing->flags & XING_SCALE) { + if (bitlen < 32) + return false; + xing->scale = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + /* Make sure we consume no less than 120 bytes (960 bits) in hopes that + * the LAME tag is found there, and not right after the Xing header */ + bitsleft = 960 - ((*oldbitlen) - bitlen); + if (bitsleft < 0) + return false; + else if (bitsleft > 0) { + mad_bit_read(ptr, bitsleft); + bitlen -= bitsleft; + } + + *oldbitlen = bitlen; + + return true; +} + +static bool +parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) +{ + int adj = 0; + int name; + int orig; + int sign; + int gain; + int i; + + /* Unlike the xing header, the lame tag has a fixed length. Fail if + * not all 36 bytes (288 bits) are there. */ + if (*bitlen < 288) + return false; + + for (i = 0; i < 9; i++) + lame->encoder[i] = (char)mad_bit_read(ptr, 8); + lame->encoder[9] = '\0'; + + *bitlen -= 72; + + /* This is technically incorrect, since the encoder might not be lame. + * But there's no other way to determine if this is a lame tag, and we + * wouldn't want to go reading a tag that's not there. */ + if (!StringStartsWith(lame->encoder, "LAME")) + return false; + + if (sscanf(lame->encoder+4, "%u.%u", + &lame->version.major, &lame->version.minor) != 2) + return false; + + FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")", + lame->version.major, lame->version.minor, lame->encoder); + + /* The reference volume was changed from the 83dB used in the + * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older + * versions, since everyone else uses 89dB instead of 83dB. + * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so + * it's impossible to make the proper adjustment for 3.95. + * Fortunately, 3.95 was only out for about a day before 3.95.1 was + * released. -- tmz */ + if (lame->version.major < 3 || + (lame->version.major == 3 && lame->version.minor < 95)) + adj = 6; + + mad_bit_read(ptr, 16); + + lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ + FormatDebug(mad_domain, "LAME peak found: %f", lame->peak); + + lame->track_gain = 0; + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 1 && orig != 0) { + lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj; + FormatDebug(mad_domain, "LAME track gain found: %f", + lame->track_gain); + } + + /* tmz reports that this isn't currently written by any version of lame + * (as of 3.97). Since we have no way of testing it, don't use it. + * Wouldn't want to go blowing someone's ears just because we read it + * wrong. :P -- jat */ + lame->album_gain = 0; +#if 0 + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 2 && orig != 0) { + lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj; + FormatDebug(mad_domain, "LAME album gain found: %f", + lame->track_gain); + } +#else + mad_bit_read(ptr, 16); +#endif + + mad_bit_read(ptr, 16); + + lame->encoder_delay = mad_bit_read(ptr, 12); + lame->encoder_padding = mad_bit_read(ptr, 12); + + FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i", + lame->encoder_delay, lame->encoder_padding); + + mad_bit_read(ptr, 80); + + lame->crc = mad_bit_read(ptr, 16); + + *bitlen -= 216; + + return true; +} + +static inline float +mp3_frame_duration(const struct mad_frame *frame) +{ + return mad_timer_count(frame->header.duration, + MAD_UNITS_MILLISECONDS) / 1000.0; +} + +inline InputStream::offset_type +MadDecoder::ThisFrameOffset() const +{ + auto offset = input_stream.GetOffset(); + + if (stream.this_frame != nullptr) + offset -= stream.bufend - stream.this_frame; + else + offset -= stream.bufend - stream.buffer; + + return offset; +} + +inline InputStream::offset_type +MadDecoder::RestIncludingThisFrame() const +{ + return input_stream.GetSize() - ThisFrameOffset(); +} + +inline void +MadDecoder::FileSizeToSongLength() +{ + InputStream::offset_type rest = RestIncludingThisFrame(); + + if (rest > 0) { + float frame_duration = mp3_frame_duration(&frame); + + total_time = (rest * 8.0) / frame.header.bitrate; + max_frames = total_time / frame_duration + FRAMES_CUSHION; + } else { + max_frames = FRAMES_CUSHION; + total_time = 0; + } +} + +inline bool +MadDecoder::DecodeFirstFrame(Tag **tag) +{ + struct xing xing; + struct lame lame; + struct mad_bitptr ptr; + int bitlen; + enum mp3_action ret; + + /* stfu gcc */ + memset(&xing, 0, sizeof(struct xing)); + xing.flags = 0; + + while (true) { + do { + ret = DecodeNextFrameHeader(tag); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_SKIP) continue; + + do { + ret = DecodeNextFrame(); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + if (ret == DECODE_OK) break; + } + + ptr = stream.anc_ptr; + bitlen = stream.anc_bitlen; + + FileSizeToSongLength(); + + /* + * if an xing tag exists, use that! + */ + if (parse_xing(&xing, &ptr, &bitlen)) { + found_xing = true; + mute_frame = MUTEFRAME_SKIP; + + if ((xing.flags & XING_FRAMES) && xing.frames) { + mad_timer_t duration = frame.header.duration; + mad_timer_multiply(&duration, xing.frames); + total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; + max_frames = xing.frames; + } + + if (parse_lame(&lame, &ptr, &bitlen)) { + if (gapless_playback && input_stream.IsSeekable()) { + drop_start_samples = lame.encoder_delay + + DECODERDELAY; + drop_end_samples = lame.encoder_padding; + } + + /* Album gain isn't currently used. See comment in + * parse_lame() for details. -- jat */ + if (decoder != nullptr && !found_replay_gain && + lame.track_gain) { + ReplayGainInfo rgi; + rgi.Clear(); + rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; + rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + decoder_replay_gain(*decoder, &rgi); + } + } + } + + if (!max_frames) + return false; + + if (max_frames > 8 * 1024 * 1024) { + FormatWarning(mad_domain, + "mp3 file header indicates too many frames: %lu", + max_frames); + return false; + } + + frame_offsets = new long[max_frames]; + times = new mad_timer_t[max_frames]; + + return true; +} + +MadDecoder::~MadDecoder() +{ + mad_synth_finish(&synth); + mad_frame_finish(&frame); + mad_stream_finish(&stream); + + delete[] frame_offsets; + delete[] times; +} + +/* this is primarily used for getting total time for tags */ +static int +mad_decoder_total_file_time(InputStream &is) +{ + MadDecoder data(nullptr, is); + return data.DecodeFirstFrame(nullptr) + ? data.total_time + 0.5 + : -1; +} + +long +MadDecoder::TimeToFrame(double t) const +{ + unsigned long i; + + for (i = 0; i < highest_frame; ++i) { + double frame_time = + mad_timer_count(times[i], + MAD_UNITS_MILLISECONDS) / 1000.; + if (frame_time >= t) + break; + } + + return i; +} + +void +MadDecoder::UpdateTimerNextFrame() +{ + if (current_frame >= highest_frame) { + /* record this frame's properties in frame_offsets + (for seeking) and times */ + bit_rate = frame.header.bitrate; + + if (current_frame >= max_frames) + /* cap current_frame */ + current_frame = max_frames - 1; + else + highest_frame++; + + frame_offsets[current_frame] = ThisFrameOffset(); + + mad_timer_add(&timer, frame.header.duration); + times[current_frame] = timer; + } else + /* get the new timer value from "times" */ + timer = times[current_frame]; + + current_frame++; + elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0; +} + +DecoderCommand +MadDecoder::SendPCM(unsigned i, unsigned pcm_length) +{ + unsigned max_samples; + + max_samples = sizeof(output_buffer) / + sizeof(output_buffer[0]) / + MAD_NCHANNELS(&frame.header); + + while (i < pcm_length) { + unsigned int num_samples = pcm_length - i; + if (num_samples > max_samples) + num_samples = max_samples; + + i += num_samples; + + mad_fixed_to_24_buffer(output_buffer, &synth, + i - num_samples, i, + MAD_NCHANNELS(&frame.header)); + num_samples *= MAD_NCHANNELS(&frame.header); + + auto cmd = decoder_data(*decoder, input_stream, output_buffer, + sizeof(output_buffer[0]) * num_samples, + bit_rate / 1000); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +inline DecoderCommand +MadDecoder::SyncAndSend() +{ + mad_synth_frame(&synth, &frame); + + if (!found_first_frame) { + unsigned int samples_per_frame = synth.pcm.length; + drop_start_frames = drop_start_samples / samples_per_frame; + drop_end_frames = drop_end_samples / samples_per_frame; + drop_start_samples = drop_start_samples % samples_per_frame; + drop_end_samples = drop_end_samples % samples_per_frame; + found_first_frame = true; + } + + if (drop_start_frames > 0) { + drop_start_frames--; + return DecoderCommand::NONE; + } else if ((drop_end_frames > 0) && + (current_frame == (max_frames + 1 - drop_end_frames))) { + /* stop decoding, effectively dropping all remaining + frames */ + return DecoderCommand::STOP; + } + + unsigned i = 0; + if (!decoded_first_frame) { + i = drop_start_samples; + decoded_first_frame = true; + } + + unsigned pcm_length = synth.pcm.length; + if (drop_end_samples && + (current_frame == max_frames - drop_end_frames)) { + if (drop_end_samples >= pcm_length) + pcm_length = 0; + else + pcm_length -= drop_end_samples; + } + + auto cmd = SendPCM(i, pcm_length); + if (cmd != DecoderCommand::NONE) + return cmd; + + if (drop_end_samples && + (current_frame == max_frames - drop_end_frames)) + /* stop decoding, effectively dropping + * all remaining samples */ + return DecoderCommand::STOP; + + return DecoderCommand::NONE; +} + +inline bool +MadDecoder::Read() +{ + enum mp3_action ret; + + UpdateTimerNextFrame(); + + switch (mute_frame) { + DecoderCommand cmd; + + case MUTEFRAME_SKIP: + mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_SEEK: + if (elapsed_time >= seek_where) + mute_frame = MUTEFRAME_NONE; + break; + case MUTEFRAME_NONE: + cmd = SyncAndSend(); + if (cmd == DecoderCommand::SEEK) { + unsigned long j; + + assert(input_stream.IsSeekable()); + + j = TimeToFrame(decoder_seek_where(*decoder)); + if (j < highest_frame) { + if (Seek(frame_offsets[j])) { + current_frame = j; + decoder_command_finished(*decoder); + } else + decoder_seek_error(*decoder); + } else { + seek_where = decoder_seek_where(*decoder); + mute_frame = MUTEFRAME_SEEK; + decoder_command_finished(*decoder); + } + } else if (cmd != DecoderCommand::NONE) + return false; + } + + while (true) { + bool skip = false; + + do { + Tag *tag = nullptr; + + ret = DecodeNextFrameHeader(&tag); + + if (tag != nullptr) { + decoder_tag(*decoder, input_stream, + std::move(*tag)); + delete tag; + } + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + else if (ret == DECODE_SKIP) + skip = true; + + if (mute_frame == MUTEFRAME_NONE) { + do { + ret = DecodeNextFrame(); + } while (ret == DECODE_CONT); + if (ret == DECODE_BREAK) + return false; + } + + if (!skip && ret == DECODE_OK) + break; + } + + return ret != DECODE_BREAK; +} + +static void +mp3_decode(Decoder &decoder, InputStream &input_stream) +{ + MadDecoder data(&decoder, input_stream); + + Tag *tag = nullptr; + if (!data.DecodeFirstFrame(&tag)) { + delete tag; + + if (decoder_get_command(decoder) == DecoderCommand::NONE) + LogError(mad_domain, + "input/Input does not appear to be a mp3 bit stream"); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + data.frame.header.samplerate, + SampleFormat::S24_P32, + MAD_NCHANNELS(&data.frame.header), + error)) { + LogError(error); + delete tag; + return; + } + + decoder_initialized(decoder, audio_format, + input_stream.IsSeekable(), + data.total_time); + + if (tag != nullptr) { + decoder_tag(decoder, input_stream, std::move(*tag)); + delete tag; + } + + while (data.Read()) {} +} + +static bool +mad_decoder_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + int total_time; + + total_time = mad_decoder_total_file_time(is); + if (total_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; +} + +static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr }; +static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr }; + +const struct DecoderPlugin mad_decoder_plugin = { + "mad", + mp3_plugin_init, + nullptr, + mp3_decode, + nullptr, + nullptr, + mad_decoder_scan_stream, + nullptr, + mp3_suffixes, + mp3_mime_types, +}; diff --git a/src/decoder/plugins/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx new file mode 100644 index 000000000..eb2a10d6f --- /dev/null +++ b/src/decoder/plugins/MadDecoderPlugin.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_DECODER_MAD_HXX +#define MPD_DECODER_MAD_HXX + +extern const struct DecoderPlugin mad_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx new file mode 100644 index 000000000..a1938617d --- /dev/null +++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx @@ -0,0 +1,249 @@ +/* + * 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 "MikmodDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "system/FatalError.hxx" +#include "fs/Path.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mikmod.h> + +#include <assert.h> + +static constexpr Domain mikmod_domain("mikmod"); + +/* this is largely copied from alsaplayer */ + +static constexpr size_t MIKMOD_FRAME_SIZE = 4096; + +static BOOL +mikmod_mpd_init(void) +{ + return VC_Init(); +} + +static void +mikmod_mpd_exit(void) +{ + VC_Exit(); +} + +static void +mikmod_mpd_update(void) +{ +} + +static BOOL +mikmod_mpd_is_present(void) +{ + return true; +} + +static char drv_name[] = PACKAGE_NAME; +static char drv_version[] = VERSION; + +#if (LIBMIKMOD_VERSION > 0x030106) +static char drv_alias[] = PACKAGE; +#endif + +static MDRIVER drv_mpd = { + nullptr, + drv_name, + drv_version, + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + drv_alias, +#if (LIBMIKMOD_VERSION >= 0x030200) + nullptr, /* CmdLineHelp */ +#endif + nullptr, /* CommandLine */ +#endif + mikmod_mpd_is_present, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + mikmod_mpd_init, + mikmod_mpd_exit, + nullptr, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + mikmod_mpd_update, + nullptr, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + +static bool mikmod_loop; +static unsigned mikmod_sample_rate; + +static bool +mikmod_decoder_init(const config_param ¶m) +{ + static char params[] = ""; + + mikmod_loop = param.GetBlockValue("loop", false); + mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u); + if (!audio_valid_sample_rate(mikmod_sample_rate)) + FormatFatalError("Invalid sample rate in line %d: %u", + param.line, mikmod_sample_rate); + + md_device = 0; + md_reverb = 0; + + MikMod_RegisterDriver(&drv_mpd); + MikMod_RegisterAllLoaders(); + + md_pansep = 64; + md_mixfreq = mikmod_sample_rate; + md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | + DMODE_16BITS); + + if (MikMod_Init(params)) { + FormatError(mikmod_domain, + "Could not init MikMod: %s", + MikMod_strerror(MikMod_errno)); + return false; + } + + return true; +} + +static void +mikmod_decoder_finish(void) +{ + MikMod_Exit(); +} + +static void +mikmod_decoder_file_decode(Decoder &decoder, Path path_fs) +{ + /* deconstify the path because libmikmod wants a non-const + string pointer */ + char *const path2 = const_cast<char *>(path_fs.c_str()); + + MODULE *handle; + int ret; + SBYTE buffer[MIKMOD_FRAME_SIZE]; + + handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + FormatError(mikmod_domain, + "failed to open mod: %s", path_fs.c_str()); + return; + } + + handle->loop = mikmod_loop; + + const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, false, 0); + + Player_Start(handle); + + DecoderCommand cmd = DecoderCommand::NONE; + while (cmd == DecoderCommand::NONE && Player_Active()) { + ret = VC_WriteBytes(buffer, sizeof(buffer)); + cmd = decoder_data(decoder, nullptr, buffer, ret, 0); + } + + Player_Stop(); + Player_Free(handle); +} + +static bool +mikmod_decoder_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + /* deconstify the path because libmikmod wants a non-const + string pointer */ + char *const path2 = const_cast<char *>(path_fs.c_str()); + + MODULE *handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + FormatDebug(mikmod_domain, + "Failed to open file: %s", path_fs.c_str()); + return false; + } + + Player_Free(handle); + + char *title = Player_LoadTitle(path2); + if (title != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); +#if (LIBMIKMOD_VERSION >= 0x030200) + MikMod_free(title); +#else + free(title); +#endif + } + + return true; +} + +static const char *const mikmod_decoder_suffixes[] = { + "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + nullptr +}; + +const struct DecoderPlugin mikmod_decoder_plugin = { + "mikmod", + mikmod_decoder_init, + mikmod_decoder_finish, + nullptr, + mikmod_decoder_file_decode, + mikmod_decoder_scan_file, + nullptr, + nullptr, + mikmod_decoder_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx new file mode 100644 index 000000000..27ba2a823 --- /dev/null +++ b/src/decoder/plugins/MikmodDecoderPlugin.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_DECODER_MIKMOD_HXX +#define MPD_DECODER_MIKMOD_HXX + +extern const struct DecoderPlugin mikmod_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx new file mode 100644 index 000000000..336870817 --- /dev/null +++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx @@ -0,0 +1,215 @@ +/* + * 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 "ModplugDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "tag/TagHandler.hxx" +#include "system/FatalError.hxx" +#include "util/WritableBuffer.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <libmodplug/modplug.h> + + +#include <assert.h> + +static constexpr Domain modplug_domain("modplug"); + +static constexpr size_t MODPLUG_FRAME_SIZE = 4096; +static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024; +static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; + +static int modplug_loop_count; + +static bool +modplug_decoder_init(const config_param ¶m) +{ + modplug_loop_count = param.GetBlockValue("loop_count", 0); + if (modplug_loop_count < -1) + FormatFatalError("Invalid loop count in line %d: %i", + param.line, modplug_loop_count); + + return true; +} + +static WritableBuffer<uint8_t> +mod_loadfile(Decoder *decoder, InputStream &is) +{ + const InputStream::offset_type size = is.GetSize(); + + if (size == 0) { + LogWarning(modplug_domain, "file is empty"); + return { nullptr, 0 }; + } + + if (size > MODPLUG_FILE_LIMIT) { + LogWarning(modplug_domain, "file too large"); + return { nullptr, 0 }; + } + + //known/unknown size, preallocate array, lets read in chunks + + const bool is_stream = size < 0; + + WritableBuffer<uint8_t> buffer; + buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size; + buffer.data = new uint8_t[buffer.size]; + + uint8_t *const end = buffer.end(); + uint8_t *p = buffer.begin(); + + while (true) { + size_t ret = decoder_read(decoder, is, p, end - p); + if (ret == 0) { + if (is.LockIsEOF()) + /* end of file */ + break; + + /* I/O error - skip this song */ + delete[] buffer.data; + buffer.data = nullptr; + return buffer; + } + + p += ret; + if (p == end) { + if (!is_stream) + break; + + LogWarning(modplug_domain, "stream too large"); + delete[] buffer.data; + buffer.data = nullptr; + return buffer; + } + } + + buffer.size = p - buffer.data; + return buffer; +} + +static ModPlugFile * +LoadModPlugFile(Decoder *decoder, InputStream &is) +{ + const auto buffer = mod_loadfile(decoder, is); + if (buffer.IsNull()) { + LogWarning(modplug_domain, "could not load stream"); + return nullptr; + } + + ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size); + delete[] buffer.data; + return f; +} + +static void +mod_decode(Decoder &decoder, InputStream &is) +{ + ModPlug_Settings settings; + int ret; + char audio_buffer[MODPLUG_FRAME_SIZE]; + + ModPlug_GetSettings(&settings); + /* alter setting */ + settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ + settings.mChannels = 2; + settings.mBits = 16; + settings.mFrequency = 44100; + settings.mLoopCount = modplug_loop_count; + /* insert more setting changes here */ + ModPlug_SetSettings(&settings); + + ModPlugFile *f = LoadModPlugFile(&decoder, is); + if (f == nullptr) { + LogWarning(modplug_domain, "could not decode stream"); + return; + } + + static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, + is.IsSeekable(), + ModPlug_GetLength(f) / 1000.0); + + DecoderCommand cmd; + do { + ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); + if (ret <= 0) + break; + + cmd = decoder_data(decoder, nullptr, + audio_buffer, ret, + 0); + + if (cmd == DecoderCommand::SEEK) { + float where = decoder_seek_where(decoder); + + ModPlug_Seek(f, (int)(where * 1000.0)); + + decoder_command_finished(decoder); + } + + } while (cmd != DecoderCommand::STOP); + + ModPlug_Unload(f); +} + +static bool +modplug_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + ModPlugFile *f = LoadModPlugFile(nullptr, is); + if (f == nullptr) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + ModPlug_GetLength(f) / 1000); + + const char *title = ModPlug_GetName(f); + if (title != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); + + ModPlug_Unload(f); + + return true; +} + +static const char *const mod_suffixes[] = { + "669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it", + "med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm", + "ult", "umx", "xm", + nullptr +}; + +const struct DecoderPlugin modplug_decoder_plugin = { + "modplug", + modplug_decoder_init, + nullptr, + mod_decode, + nullptr, + nullptr, + modplug_scan_stream, + nullptr, + mod_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx new file mode 100644 index 000000000..08f2ecb12 --- /dev/null +++ b/src/decoder/plugins/ModplugDecoderPlugin.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_DECODER_MODPLUG_HXX +#define MPD_DECODER_MODPLUG_HXX + +extern const struct DecoderPlugin modplug_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/Mp4v2DecoderPlugin.cxx b/src/decoder/plugins/Mp4v2DecoderPlugin.cxx new file mode 100644 index 000000000..36d02c81d --- /dev/null +++ b/src/decoder/plugins/Mp4v2DecoderPlugin.cxx @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Mp4v2DecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mp4v2/mp4v2.h> +#include <neaacdec.h> + +#include <cstdio> +#include <cstdlib> + +static constexpr Domain mp4v2_decoder_domain("mp4v2"); + +static MP4TrackId +mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder, + AudioFormat &audio_format, Error &error) +{ + uint32_t sample_rate; +#ifdef HAVE_FAAD_LONG + /* neaacdec.h declares all arguments as "unsigned long", but + internally expects uint32_t pointers. To avoid gcc + warnings, use this workaround. */ + unsigned long *sample_rate_r = (unsigned long*)&sample_rate; +#else + uint32_t *sample_rate_r = sample_rate; +#endif + + const MP4TrackId tracks = MP4GetNumberOfTracks(handle); + + for (MP4TrackId id = 1; id <= tracks; id++) { + const char* track_type = MP4GetTrackType(handle, id); + + if (track_type == 0) + continue; + + const auto obj_type = MP4GetTrackEsdsObjectTypeId(handle, id); + + if (obj_type == MP4_INVALID_AUDIO_TYPE) + continue; + if (obj_type == MP4_MPEG4_AUDIO_TYPE) { + const auto mpeg_type = MP4GetTrackAudioMpeg4Type(handle, id); + if (!MP4_IS_MPEG4_AAC_AUDIO_TYPE(mpeg_type)) + continue; + } else if (!MP4_IS_AAC_AUDIO_TYPE(obj_type)) + continue; + + if (decoder == nullptr) + /* found audio track, no decoder */ + return id; + + unsigned char *buff = nullptr; + unsigned buff_size = 0; + + if (!MP4GetTrackESConfiguration(handle, id, &buff, &buff_size)) + continue; + + uint8_t channels; + int32_t nbytes = NeAACDecInit(decoder, buff, buff_size, + sample_rate_r, &channels); + + free(buff); + + if (nbytes < 0) + /* invalid stream */ + continue; + + if (!audio_format_init_checked(audio_format, sample_rate, + SampleFormat::S16, + channels, + error)) + continue; + + return id; + } + + error.Set(mp4v2_decoder_domain, "no valid aac track found"); + + return MP4_INVALID_TRACK_ID; +} + +static NeAACDecHandle +mp4_faad_new(MP4FileHandle handle, AudioFormat &audio_format, Error &error) +{ + const NeAACDecHandle decoder = NeAACDecOpen(); + const NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + config->downMatrix = 1; + config->dontUpSampleImplicitSBR = 0; + NeAACDecSetConfiguration(decoder, config); + + const auto track = mp4_get_aac_track(handle, decoder, audio_format, error); + + if (track == MP4_INVALID_TRACK_ID) { + NeAACDecClose(decoder); + return nullptr; + } + + return decoder; +} + +static void +mp4_file_decode(Decoder &mpd_decoder, Path path_fs) +{ + const MP4FileHandle handle = MP4Read(path_fs.c_str()); + + if (handle == MP4_INVALID_FILE_HANDLE) { + FormatError(mp4v2_decoder_domain, + "unable to open file"); + return; + } + + AudioFormat audio_format; + Error error; + const NeAACDecHandle decoder = mp4_faad_new(handle, audio_format, error); + + if (decoder == nullptr) { + LogError(error); + MP4Close(handle); + return; + } + + const MP4TrackId track = mp4_get_aac_track(handle, nullptr, audio_format, error); + + /* initialize the MPD core */ + + const uint32_t scale = MP4GetTrackTimeScale(handle, track); + const float duration = ((float)MP4GetTrackDuration(handle, track)) / scale + 0.5f; + const MP4SampleId num_samples = MP4GetTrackNumberOfSamples(handle, track); + + decoder_initialized(mpd_decoder, audio_format, true, duration); + + /* the decoder loop */ + + DecoderCommand cmd = DecoderCommand::NONE; + + for (MP4SampleId sample = 1; + sample < num_samples && cmd != DecoderCommand::STOP; + sample++) { + unsigned char *data = nullptr; + unsigned int data_length = 0; + + if (cmd == DecoderCommand::SEEK) { + const double offset = decoder_seek_where(mpd_decoder); + sample = MP4GetSampleIdFromTime(handle, track, + (MP4Timestamp)(offset * (double)scale), 0); + decoder_command_finished(mpd_decoder); + } + + /* read */ + if (MP4ReadSample(handle, track, sample, &data, &data_length) == 0) { + FormatError(mp4v2_decoder_domain, "unable to read sample"); + break; + } + + /* decode it */ + NeAACDecFrameInfo frame_info; + const void *const decoded = NeAACDecDecode(decoder, &frame_info, data, data_length); + + if (frame_info.error > 0) { + FormatWarning(mp4v2_decoder_domain, + "error decoding AAC stream: %s", + NeAACDecGetErrorMessage(frame_info.error)); + break; + } + + if (frame_info.channels != audio_format.channels) { + FormatDefault(mp4v2_decoder_domain, + "channel count changed from %u to %u", + audio_format.channels, frame_info.channels); + break; + } + + if (frame_info.samplerate != audio_format.sample_rate) { + FormatDefault(mp4v2_decoder_domain, + "sample rate changed from %u to %lu", + audio_format.sample_rate, + (unsigned long)frame_info.samplerate); + break; + } + + /* update bit rate and position */ + unsigned bit_rate = 0; + + if (frame_info.samples > 0) { + bit_rate = frame_info.bytesconsumed * 8.0 * + frame_info.channels * audio_format.sample_rate / + frame_info.samples / 1000 + 0.5; + } + + /* send PCM samples to MPD */ + + cmd = decoder_data(mpd_decoder, nullptr, decoded, + (size_t)frame_info.samples * 2, + bit_rate); + + free(data); + } + + /* cleanup */ + NeAACDecClose(decoder); + MP4Close(handle); +} + +static inline void +mp4_safe_invoke_tag(const struct tag_handler *handler, void *handler_ctx, + TagType tag, const char *value) +{ + if (value != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, tag, value); +} + +static bool +mp4_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + const MP4FileHandle handle = MP4Read(path_fs.c_str()); + + if (handle == MP4_INVALID_FILE_HANDLE) + return false; + + AudioFormat tmp_audio_format; + Error error; + const MP4TrackId id = mp4_get_aac_track(handle, nullptr, tmp_audio_format, error); + + if (id == MP4_INVALID_TRACK_ID) { + LogError(error); + MP4Close(handle); + return false; + } + + const MP4Duration dur = MP4GetTrackDuration(handle, id) / + MP4GetTrackTimeScale(handle, id); + tag_handler_invoke_duration(handler, handler_ctx, dur); + + const MP4Tags* tags = MP4TagsAlloc(); + MP4TagsFetch(tags, handle); + + mp4_safe_invoke_tag(handler, handler_ctx, TAG_NAME, tags->name); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_ARTIST, tags->artist); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_ALBUM_ARTIST, tags->albumArtist); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_ALBUM, tags->album); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_COMPOSER, tags->composer); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_COMMENT, tags->comments); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_GENRE, tags->genre); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_DATE, tags->releaseDate); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_ARTIST_SORT, tags->sortArtist); + mp4_safe_invoke_tag(handler, handler_ctx, TAG_ALBUM_ARTIST_SORT, tags->sortAlbumArtist); + + char buff[8]; /* tmp buffer for index to string. */ + if (tags->track != nullptr) { + sprintf(buff, "%d", tags->track->index); + tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, buff); + } + + if (tags->disk != nullptr) { + sprintf(buff, "%d", tags->disk->index); + tag_handler_invoke_tag(handler, handler_ctx, TAG_DISC, buff); + } + + MP4TagsFree(tags); + MP4Close(handle); + + return true; +} + +static const char *const mp4_suffixes[] = { + "mp4", + "m4a", + /* "m4p", encrypted */ + /* "m4b", audio book */ + /* "m4r", ring tones */ + /* "m4v", video */ + nullptr +}; + +static const char *const mp4_mime_types[] = { + "application/mp4", + "application/m4a", + "audio/mp4", + "audio/m4a", + /* "audio/m4p", */ + /* "audio/m4b", */ + /* "audio/m4r", */ + /* "audio/m4v", */ + nullptr +}; + +const struct DecoderPlugin mp4v2_decoder_plugin = { + "mp4v2", + nullptr, + nullptr, + nullptr, + mp4_file_decode, + mp4_scan_file, + nullptr, + nullptr, + mp4_suffixes, + mp4_mime_types +}; diff --git a/src/decoder/plugins/Mp4v2DecoderPlugin.hxx b/src/decoder/plugins/Mp4v2DecoderPlugin.hxx new file mode 100644 index 000000000..57585dec4 --- /dev/null +++ b/src/decoder/plugins/Mp4v2DecoderPlugin.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_DECODER_MP4V2_HXX +#define MPD_DECODER_MP4V2_HXX + +extern const struct DecoderPlugin mp4v2_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx new file mode 100644 index 000000000..f86cb3c81 --- /dev/null +++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx @@ -0,0 +1,278 @@ +/* + * 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 "MpcdecDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <mpc/mpcdec.h> + +#include <math.h> + +struct mpc_decoder_data { + InputStream &is; + Decoder *decoder; + + mpc_decoder_data(InputStream &_is, Decoder *_decoder) + :is(_is), decoder(_decoder) {} +}; + +static constexpr Domain mpcdec_domain("mpcdec"); + +static mpc_int32_t +mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return decoder_read(data->decoder, data->is, ptr, size); +} + +static mpc_bool_t +mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is.LockSeek(offset, IgnoreError()); +} + +static mpc_int32_t +mpc_tell_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return (long)data->is.GetOffset(); +} + +static mpc_bool_t +mpc_canseek_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is.IsSeekable(); +} + +static mpc_int32_t +mpc_getsize_cb(mpc_reader *reader) +{ + struct mpc_decoder_data *data = + (struct mpc_decoder_data *)reader->data; + + return data->is.GetSize(); +} + +/* this _looks_ performance-critical, don't de-inline -- eric */ +static inline int32_t +mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) +{ + /* only doing 16-bit audio for now */ + int32_t val; + + enum { + bits = 24, + }; + + const int clip_min = -1 << (bits - 1); + const int clip_max = (1 << (bits - 1)) - 1; + +#ifdef MPC_FIXED_POINT + const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; + + if (shift < 0) + val = sample >> -shift; + else + val = sample << shift; +#else + const int float_scale = 1 << (bits - 1); + + val = sample * float_scale; +#endif + + if (val < clip_min) + val = clip_min; + else if (val > clip_max) + val = clip_max; + + return val; +} + +static void +mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src, + unsigned num_samples) +{ + while (num_samples-- > 0) + *dest++ = mpc_to_mpd_sample(*src++); +} + +static void +mpcdec_decode(Decoder &mpd_decoder, InputStream &is) +{ + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + mpc_decoder_data data(is, &mpd_decoder); + + mpc_reader reader; + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_demux *demux = mpc_demux_init(&reader); + if (demux == nullptr) { + if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP) + LogWarning(mpcdec_domain, + "Not a valid musepack stream"); + return; + } + + mpc_streaminfo info; + mpc_demux_get_info(demux, &info); + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, info.sample_freq, + SampleFormat::S24_P32, + info.channels, error)) { + LogError(error); + mpc_demux_exit(demux); + return; + } + + ReplayGainInfo rgi; + rgi.Clear(); + rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); + rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; + rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); + rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; + + decoder_replay_gain(mpd_decoder, &rgi); + + decoder_initialized(mpd_decoder, audio_format, + is.IsSeekable(), + mpc_streaminfo_get_length(&info)); + + DecoderCommand cmd = DecoderCommand::NONE; + do { + if (cmd == DecoderCommand::SEEK) { + mpc_int64_t where = decoder_seek_where(mpd_decoder) * + audio_format.sample_rate; + bool success; + + success = mpc_demux_seek_sample(demux, where) + == MPC_STATUS_OK; + if (success) + decoder_command_finished(mpd_decoder); + else + decoder_seek_error(mpd_decoder); + } + + mpc_uint32_t vbr_update_bits = 0; + + mpc_frame_info frame; + frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer; + mpc_status status = mpc_demux_decode(demux, &frame); + if (status != MPC_STATUS_OK) { + LogWarning(mpcdec_domain, + "Failed to decode sample"); + break; + } + + if (frame.bits == -1) + break; + + mpc_uint32_t ret = frame.samples; + ret *= info.channels; + + int32_t chunk[ARRAY_SIZE(sample_buffer)]; + mpc_to_mpd_buffer(chunk, sample_buffer, ret); + + long bit_rate = vbr_update_bits * audio_format.sample_rate + / 1152 / 1000; + + cmd = decoder_data(mpd_decoder, is, + chunk, ret * sizeof(chunk[0]), + bit_rate); + } while (cmd != DecoderCommand::STOP); + + mpc_demux_exit(demux); +} + +static float +mpcdec_get_file_duration(InputStream &is) +{ + mpc_decoder_data data(is, nullptr); + + mpc_reader reader; + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_demux *demux = mpc_demux_init(&reader); + if (demux == nullptr) + return -1; + + mpc_streaminfo info; + mpc_demux_get_info(demux, &info); + mpc_demux_exit(demux); + + return mpc_streaminfo_get_length(&info); +} + +static bool +mpcdec_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + float total_time = mpcdec_get_file_duration(is); + + if (total_time < 0) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; +} + +static const char *const mpcdec_suffixes[] = { "mpc", nullptr }; + +const struct DecoderPlugin mpcdec_decoder_plugin = { + "mpcdec", + nullptr, + nullptr, + mpcdec_decode, + nullptr, + nullptr, + mpcdec_scan_stream, + nullptr, + mpcdec_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx new file mode 100644 index 000000000..7f71311fa --- /dev/null +++ b/src/decoder/plugins/MpcdecDecoderPlugin.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_DECODER_MPCDEC_HXX +#define MPD_DECODER_MPCDEC_HXX + +extern const struct DecoderPlugin mpcdec_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx new file mode 100644 index 000000000..76702a08f --- /dev/null +++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "Mpg123DecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mpg123.h> + +#include <stdio.h> + +static constexpr Domain mpg123_domain("mpg123"); + +static bool +mpd_mpg123_init(gcc_unused const config_param ¶m) +{ + mpg123_init(); + + return true; +} + +static void +mpd_mpg123_finish(void) +{ + mpg123_exit(); +} + +/** + * Opens a file with an existing #mpg123_handle. + * + * @param handle a handle which was created before; on error, this + * function will not free it + * @param audio_format this parameter is filled after successful + * return + * @return true on success + */ +static bool +mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, + AudioFormat &audio_format) +{ + int error; + int channels, encoding; + long rate; + + /* mpg123_open() wants a writable string :-( */ + char *const path2 = const_cast<char *>(path_fs); + + error = mpg123_open(handle, path2); + if (error != MPG123_OK) { + FormatWarning(mpg123_domain, + "libmpg123 failed to open %s: %s", + path_fs, mpg123_plain_strerror(error)); + return false; + } + + /* obtain the audio format */ + + error = mpg123_getformat(handle, &rate, &channels, &encoding); + if (error != MPG123_OK) { + FormatWarning(mpg123_domain, + "mpg123_getformat() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + if (encoding != MPG123_ENC_SIGNED_16) { + /* other formats not yet implemented */ + FormatWarning(mpg123_domain, + "expected MPG123_ENC_SIGNED_16, got %d", + encoding); + return false; + } + + Error error2; + if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16, + channels, error2)) { + LogError(error2); + return false; + } + + return true; +} + +static void +mpd_mpg123_file_decode(Decoder &decoder, Path path_fs) +{ + mpg123_handle *handle; + int error; + off_t num_samples; + struct mpg123_frameinfo info; + + /* open the file */ + + handle = mpg123_new(nullptr, &error); + if (handle == nullptr) { + FormatError(mpg123_domain, + "mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return; + } + + AudioFormat audio_format; + if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) { + mpg123_delete(handle); + return; + } + + num_samples = mpg123_length(handle); + + /* tell MPD core we're ready */ + + decoder_initialized(decoder, audio_format, true, + (float)num_samples / + (float)audio_format.sample_rate); + + if (mpg123_info(handle, &info) != MPG123_OK) { + info.vbr = MPG123_CBR; + info.bitrate = 0; + } + + switch (info.vbr) { + case MPG123_ABR: + info.bitrate = info.abr_rate; + break; + case MPG123_CBR: + break; + default: + info.bitrate = 0; + } + + /* the decoder main loop */ + + DecoderCommand cmd; + do { + unsigned char buffer[8192]; + size_t nbytes; + + /* decode */ + + error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); + if (error != MPG123_OK) { + if (error != MPG123_DONE) + FormatWarning(mpg123_domain, + "mpg123_read() failed: %s", + mpg123_plain_strerror(error)); + break; + } + + /* update bitrate for ABR/VBR */ + if (info.vbr != MPG123_CBR) { + /* FIXME: maybe skip, as too expensive? */ + /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ + if (mpg123_info (handle, &info) != MPG123_OK) + info.bitrate = 0; + } + + /* send to MPD */ + + cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate); + + if (cmd == DecoderCommand::SEEK) { + off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; + c = mpg123_seek(handle, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else { + decoder_command_finished(decoder); + decoder_timestamp(decoder, c/(double)audio_format.sample_rate); + } + + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + /* cleanup */ + + mpg123_delete(handle); +} + +static bool +mpd_mpg123_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + mpg123_handle *handle; + int error; + off_t num_samples; + + handle = mpg123_new(nullptr, &error); + if (handle == nullptr) { + FormatError(mpg123_domain, + "mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + AudioFormat audio_format; + if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) { + mpg123_delete(handle); + return false; + } + + num_samples = mpg123_length(handle); + if (num_samples <= 0) { + mpg123_delete(handle); + return false; + } + + /* ID3 tag support not yet implemented */ + + mpg123_delete(handle); + + tag_handler_invoke_duration(handler, handler_ctx, + num_samples / audio_format.sample_rate); + return true; +} + +static const char *const mpg123_suffixes[] = { + "mp3", + nullptr +}; + +const struct DecoderPlugin mpg123_decoder_plugin = { + "mpg123", + mpd_mpg123_init, + mpd_mpg123_finish, + /* streaming not yet implemented */ + nullptr, + mpd_mpg123_file_decode, + mpd_mpg123_scan_file, + nullptr, + nullptr, + mpg123_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx new file mode 100644 index 000000000..fd089c6a4 --- /dev/null +++ b/src/decoder/plugins/Mpg123DecoderPlugin.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_DECODER_MPG123_HXX +#define MPD_DECODER_MPG123_HXX + +extern const struct DecoderPlugin mpg123_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx new file mode 100644 index 000000000..c7f39586e --- /dev/null +++ b/src/decoder/plugins/OggCodec.cxx @@ -0,0 +1,51 @@ +/* + * 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. + */ + +/* + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#include "config.h" +#include "OggCodec.hxx" +#include "../DecoderAPI.hxx" + +#include <string.h> + +enum ogg_codec +ogg_codec_detect(Decoder *decoder, InputStream &is) +{ + /* oggflac detection based on code in ogg123 and this post + * http://lists.xiph.org/pipermail/flac/2004-December/000393.html + * ogg123 trunk still doesn't have this patch as of June 2005 */ + unsigned char buf[41]; + size_t r = decoder_read(decoder, is, buf, sizeof(buf)); + if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0) + return OGG_CODEC_UNKNOWN; + + if ((memcmp(buf + 29, "FLAC", 4) == 0 && + memcmp(buf + 37, "fLaC", 4) == 0) || + memcmp(buf + 28, "FLAC", 4) == 0 || + memcmp(buf + 28, "fLaC", 4) == 0) + return OGG_CODEC_FLAC; + + if (memcmp(buf + 28, "Opus", 4) == 0) + return OGG_CODEC_OPUS; + + return OGG_CODEC_VORBIS; +} diff --git a/src/decoder/plugins/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx new file mode 100644 index 000000000..3b096561c --- /dev/null +++ b/src/decoder/plugins/OggCodec.hxx @@ -0,0 +1,40 @@ +/* + * 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. + */ + +/* + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + */ + +#ifndef MPD_OGG_CODEC_HXX +#define MPD_OGG_CODEC_HXX + +struct Decoder; +class InputStream; + +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; + +enum ogg_codec +ogg_codec_detect(Decoder *decoder, InputStream &is); + +#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/plugins/OggFind.cxx b/src/decoder/plugins/OggFind.cxx new file mode 100644 index 000000000..15e9c5c92 --- /dev/null +++ b/src/decoder/plugins/OggFind.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 "OggFind.hxx" +#include "OggSyncState.hxx" +#include "util/Error.hxx" + +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet) +{ + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r == 0) { + if (!oy.ExpectPageIn(os)) + return false; + + continue; + } else if (r > 0 && packet.e_o_s) + return true; + } +} + +bool +OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, + InputStream::offset_type offset) +{ + oy.Reset(); + + /* reset the stream to clear any previous partial packet + data */ + ogg_stream_reset(&os); + + return is.LockSeek(offset, IgnoreError()) && + oy.ExpectPageSeekIn(os); +} + +bool +OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + InputStream &is) +{ + if (!is.KnownSize()) + return false; + + if (is.GetRest() < 65536) + return OggFindEOS(oy, os, packet); + + if (!is.CheapSeeking()) + return false; + + return OggSeekPageAtOffset(oy, os, is, is.GetSize() - 65536) && + OggFindEOS(oy, os, packet); +} diff --git a/src/decoder/plugins/OggFind.hxx b/src/decoder/plugins/OggFind.hxx new file mode 100644 index 000000000..8ced7fd86 --- /dev/null +++ b/src/decoder/plugins/OggFind.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_OGG_FIND_HXX +#define MPD_OGG_FIND_HXX + +#include "check.h" +#include "input/InputStream.hxx" + +#include <ogg/ogg.h> + +class OggSyncState; + +/** + * Skip all pages/packets until an end-of-stream (EOS) packet for the + * specified stream is found. + * + * @return true if the EOS packet was found + */ +bool +OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet); + +/** + * Seek the #InputStream and find the next Ogg page. + */ +bool +OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, + InputStream::offset_type offset); + +/** + * Try to find the end-of-stream (EOS) packet. Seek to the end of the + * file if necessary. + * + * @return true if the EOS packet was found + */ +bool +OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + InputStream &is); + +#endif diff --git a/src/decoder/plugins/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx new file mode 100644 index 000000000..024902fff --- /dev/null +++ b/src/decoder/plugins/OggSyncState.hxx @@ -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. + */ + +#ifndef MPD_OGG_SYNC_STATE_HXX +#define MPD_OGG_SYNC_STATE_HXX + +#include "check.h" +#include "OggUtil.hxx" + +#include <ogg/ogg.h> + +#include <stddef.h> + +/** + * Wrapper for an ogg_sync_state. + */ +class OggSyncState { + ogg_sync_state oy; + + InputStream &is; + Decoder *const decoder; + +public: + OggSyncState(InputStream &_is, Decoder *const _decoder=nullptr) + :is(_is), decoder(_decoder) { + ogg_sync_init(&oy); + } + + ~OggSyncState() { + ogg_sync_clear(&oy); + } + + void Reset() { + ogg_sync_reset(&oy); + } + + bool Feed(size_t size) { + return OggFeed(oy, decoder, is, size); + } + + bool ExpectPage(ogg_page &page) { + return OggExpectPage(oy, page, decoder, is); + } + + bool ExpectFirstPage(ogg_stream_state &os) { + return OggExpectFirstPage(oy, os, decoder, is); + } + + bool ExpectPageIn(ogg_stream_state &os) { + return OggExpectPageIn(oy, os, decoder, is); + } + + bool ExpectPageSeek(ogg_page &page) { + return OggExpectPageSeek(oy, page, decoder, is); + } + + bool ExpectPageSeekIn(ogg_stream_state &os) { + return OggExpectPageSeekIn(oy, os, decoder, is); + } +}; + +#endif diff --git a/src/decoder/plugins/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx new file mode 100644 index 000000000..6341b0008 --- /dev/null +++ b/src/decoder/plugins/OggUtil.cxx @@ -0,0 +1,118 @@ +/* + * 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 "OggUtil.hxx" +#include "../DecoderAPI.hxx" + +bool +OggFeed(ogg_sync_state &oy, Decoder *decoder, + InputStream &input_stream, size_t size) +{ + char *buffer = ogg_sync_buffer(&oy, size); + if (buffer == nullptr) + return false; + + size_t nbytes = decoder_read(decoder, input_stream, + buffer, size); + if (nbytes == 0) + return false; + + ogg_sync_wrote(&oy, nbytes); + return true; +} + +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &input_stream) +{ + while (true) { + int r = ogg_sync_pageout(&oy, &page); + if (r != 0) + return r > 0; + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_init(&os, ogg_page_serialno(&page)); + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is) +{ + ogg_page page; + if (!OggExpectPage(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} + +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &input_stream) +{ + size_t remaining_skipped = 32768; + + while (true) { + int r = ogg_sync_pageseek(&oy, &page); + if (r > 0) + return true; + + if (r < 0) { + /* skipped -r bytes */ + size_t nbytes = -r; + if (nbytes > remaining_skipped) + /* still no ogg page - we lost our + patience, abort */ + return false; + + remaining_skipped -= nbytes; + continue; + } + + if (!OggFeed(oy, decoder, input_stream, 1024)) + return false; + } +} + +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is) +{ + ogg_page page; + if (!OggExpectPageSeek(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} diff --git a/src/decoder/plugins/OggUtil.hxx b/src/decoder/plugins/OggUtil.hxx new file mode 100644 index 000000000..94c380ef4 --- /dev/null +++ b/src/decoder/plugins/OggUtil.hxx @@ -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. + */ + +#ifndef MPD_OGG_UTIL_HXX +#define MPD_OGG_UTIL_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <stddef.h> + +class InputStream; +struct Decoder; + +/** + * Feed data from the #InputStream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &is, + size_t size); + +/** + * Feed into the #ogg_sync_state until a page gets available. Garbage + * data at the beginning is considered a fatal error. + * + * @return true if a page is available + */ +bool +OggExpectPage(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &is); + +/** + * Combines OggExpectPage(), ogg_stream_init() and + * ogg_stream_pagein(). + * + * @return true if the stream was initialized and the first page was + * delivered to it + */ +bool +OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is); + +/** + * Combines OggExpectPage() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &is); + +/** + * Combines OggExpectPageSeek() and ogg_stream_pagein(). + * + * @return true if a page was delivered to the stream + */ +bool +OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os, + Decoder *decoder, InputStream &is); + +#endif diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..8d1f75e72 --- /dev/null +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "OpusDecoderPlugin.h" +#include "OpusDomain.hxx" +#include "OpusHead.hxx" +#include "OpusTags.hxx" +#include "OggFind.hxx" +#include "OggSyncState.hxx" +#include "../DecoderAPI.hxx" +#include "OggCodec.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <string.h> +#include <stdio.h> + +static constexpr opus_int32 opus_sample_rate = 48000; + +gcc_pure +static bool +IsOpusHead(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0; +} + +gcc_pure +static bool +IsOpusTags(const ogg_packet &packet) +{ + return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0; +} + +static bool +mpd_opus_init(gcc_unused const config_param ¶m) +{ + LogDebug(opus_domain, opus_get_version_string()); + + return true; +} + +class MPDOpusDecoder { + Decoder &decoder; + InputStream &input_stream; + + ogg_stream_state os; + + OpusDecoder *opus_decoder; + opus_int16 *output_buffer; + unsigned output_size; + + bool os_initialized; + bool found_opus; + + int opus_serialno; + + ogg_int64_t eos_granulepos; + + size_t frame_size; + +public: + MPDOpusDecoder(Decoder &_decoder, + InputStream &_input_stream) + :decoder(_decoder), input_stream(_input_stream), + opus_decoder(nullptr), + output_buffer(nullptr), output_size(0), + os_initialized(false), found_opus(false) {} + ~MPDOpusDecoder(); + + bool ReadFirstPage(OggSyncState &oy); + bool ReadNextPage(OggSyncState &oy); + + DecoderCommand HandlePackets(); + DecoderCommand HandlePacket(const ogg_packet &packet); + DecoderCommand HandleBOS(const ogg_packet &packet); + DecoderCommand HandleTags(const ogg_packet &packet); + DecoderCommand HandleAudio(const ogg_packet &packet); + + bool Seek(OggSyncState &oy, double where); +}; + +MPDOpusDecoder::~MPDOpusDecoder() +{ + delete[] output_buffer; + + if (opus_decoder != nullptr) + opus_decoder_destroy(opus_decoder); + + if (os_initialized) + ogg_stream_clear(&os); +} + +inline bool +MPDOpusDecoder::ReadFirstPage(OggSyncState &oy) +{ + assert(!os_initialized); + + if (!oy.ExpectFirstPage(os)) + return false; + + os_initialized = true; + return true; +} + +inline bool +MPDOpusDecoder::ReadNextPage(OggSyncState &oy) +{ + assert(os_initialized); + + ogg_page page; + if (!oy.ExpectPage(page)) + return false; + + const auto page_serialno = ogg_page_serialno(&page); + if (page_serialno != os.serialno) + ogg_stream_reset_serialno(&os, page_serialno); + + ogg_stream_pagein(&os, &page); + return true; +} + +inline DecoderCommand +MPDOpusDecoder::HandlePackets() +{ + ogg_packet packet; + while (ogg_stream_packetout(&os, &packet) == 1) { + auto cmd = HandlePacket(packet); + if (cmd != DecoderCommand::NONE) + return cmd; + } + + return DecoderCommand::NONE; +} + +inline DecoderCommand +MPDOpusDecoder::HandlePacket(const ogg_packet &packet) +{ + if (packet.e_o_s) + return DecoderCommand::STOP; + + if (packet.b_o_s) + return HandleBOS(packet); + else if (!found_opus) + return DecoderCommand::STOP; + + if (IsOpusTags(packet)) + return HandleTags(packet); + + return HandleAudio(packet); +} + +/** + * Load the end-of-stream packet and restore the previous file + * position. + */ +static bool +LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, + ogg_packet &packet) +{ + if (!is.CheapSeeking()) + /* we do this for local files only, because seeking + around remote files is expensive and not worth the + troubl */ + return -1; + + const auto old_offset = is.GetOffset(); + if (old_offset < 0) + return -1; + + /* create temporary Ogg objects for seeking and parsing the + EOS packet */ + OggSyncState oy(is, decoder); + ogg_stream_state os; + ogg_stream_init(&os, serialno); + + bool result = OggSeekFindEOS(oy, os, packet, is); + ogg_stream_clear(&os); + + /* restore the previous file position */ + is.Seek(old_offset, IgnoreError()); + + return result; +} + +/** + * Load the end-of-stream granulepos and restore the previous file + * position. + * + * @return -1 on error + */ +gcc_pure +static ogg_int64_t +LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) +{ + ogg_packet packet; + if (!LoadEOSPacket(is, decoder, serialno, packet)) + return -1; + + return packet.granulepos; +} + +inline DecoderCommand +MPDOpusDecoder::HandleBOS(const ogg_packet &packet) +{ + assert(packet.b_o_s); + + if (found_opus || !IsOpusHead(packet)) + return DecoderCommand::STOP; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) + return DecoderCommand::STOP; + + assert(opus_decoder == nullptr); + assert(output_buffer == nullptr); + + opus_serialno = os.serialno; + found_opus = true; + + /* TODO: parse attributes from the OpusHead (sample rate, + channels, ...) */ + + int opus_error; + opus_decoder = opus_decoder_create(opus_sample_rate, channels, + &opus_error); + if (opus_decoder == nullptr) { + FormatError(opus_domain, "libopus error: %s", + opus_strerror(opus_error)); + return DecoderCommand::STOP; + } + + eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, + opus_serialno); + const double duration = eos_granulepos >= 0 + ? double(eos_granulepos) / opus_sample_rate + : -1.0; + + const AudioFormat audio_format(opus_sample_rate, + SampleFormat::S16, channels); + decoder_initialized(decoder, audio_format, + eos_granulepos > 0, duration); + frame_size = audio_format.GetFrameSize(); + + /* allocate an output buffer for 16 bit PCM samples big enough + to hold a quarter second, larger than 120ms required by + libopus */ + output_size = audio_format.sample_rate / 4; + output_buffer = new opus_int16[output_size * audio_format.channels]; + + return decoder_get_command(decoder); +} + +inline DecoderCommand +MPDOpusDecoder::HandleTags(const ogg_packet &packet) +{ + ReplayGainInfo rgi; + rgi.Clear(); + + TagBuilder tag_builder; + + DecoderCommand cmd; + if (ScanOpusTags(packet.packet, packet.bytes, + &rgi, + &add_tag_handler, &tag_builder) && + !tag_builder.IsEmpty()) { + decoder_replay_gain(decoder, &rgi); + + Tag tag = tag_builder.Commit(); + cmd = decoder_tag(decoder, input_stream, std::move(tag)); + } else + cmd = decoder_get_command(decoder); + + return cmd; +} + +inline DecoderCommand +MPDOpusDecoder::HandleAudio(const ogg_packet &packet) +{ + assert(opus_decoder != nullptr); + + int nframes = opus_decode(opus_decoder, + (const unsigned char*)packet.packet, + packet.bytes, + output_buffer, output_size, + 0); + if (nframes < 0) { + LogError(opus_domain, opus_strerror(nframes)); + return DecoderCommand::STOP; + } + + if (nframes > 0) { + const size_t nbytes = nframes * frame_size; + auto cmd = decoder_data(decoder, input_stream, + output_buffer, nbytes, + 0); + if (cmd != DecoderCommand::NONE) + return cmd; + + if (packet.granulepos > 0) + decoder_timestamp(decoder, + double(packet.granulepos) + / opus_sample_rate); + } + + return DecoderCommand::NONE; +} + +bool +MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) +{ + assert(eos_granulepos > 0); + assert(input_stream.IsSeekable()); + assert(input_stream.KnownSize()); + assert(input_stream.GetOffset() >= 0); + + const ogg_int64_t where_granulepos(where_s * opus_sample_rate); + + /* interpolate the file offset where we expect to find the + given granule position */ + /* TODO: implement binary search */ + InputStream::offset_type offset(where_granulepos * input_stream.GetSize() + / eos_granulepos); + + if (!OggSeekPageAtOffset(oy, os, input_stream, offset)) + return false; + + decoder_timestamp(decoder, where_s); + return true; +} + +static void +mpd_opus_stream_decode(Decoder &decoder, + InputStream &input_stream) +{ + if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_OPUS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream.LockRewind(IgnoreError()); + + MPDOpusDecoder d(decoder, input_stream); + OggSyncState oy(input_stream, &decoder); + + if (!d.ReadFirstPage(oy)) + return; + + while (true) { + auto cmd = d.HandlePackets(); + if (cmd == DecoderCommand::SEEK) { + if (d.Seek(oy, decoder_seek_where(decoder))) + decoder_command_finished(decoder); + else + decoder_seek_error(decoder); + + continue; + } + + if (cmd != DecoderCommand::NONE) + break; + + if (!d.ReadNextPage(oy)) + break; + } +} + +static bool +mpd_opus_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + OggSyncState oy(is); + + ogg_stream_state os; + if (!oy.ExpectFirstPage(os)) + return false; + + /* read at most two more pages */ + unsigned remaining_pages = 2; + + bool result = false; + + ogg_packet packet; + while (true) { + int r = ogg_stream_packetout(&os, &packet); + if (r < 0) { + result = false; + break; + } + + if (r == 0) { + if (remaining_pages-- == 0) + break; + + if (!oy.ExpectPageIn(os)) { + result = false; + break; + } + + continue; + } + + if (packet.b_o_s) { + if (!IsOpusHead(packet)) + break; + + unsigned channels; + if (!ScanOpusHeader(packet.packet, packet.bytes, channels) || + !audio_valid_channel_count(channels)) { + result = false; + break; + } + + result = true; + } else if (!result) + break; + else if (IsOpusTags(packet)) { + if (!ScanOpusTags(packet.packet, packet.bytes, + nullptr, + handler, handler_ctx)) + result = false; + + break; + } + } + + if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) + tag_handler_invoke_duration(handler, handler_ctx, + packet.granulepos / opus_sample_rate); + + ogg_stream_clear(&os); + + return result; +} + +static const char *const opus_suffixes[] = { + "opus", + "ogg", + "oga", + nullptr +}; + +static const char *const opus_mime_types[] = { + "audio/opus", + nullptr +}; + +const struct DecoderPlugin opus_decoder_plugin = { + "opus", + mpd_opus_init, + nullptr, + mpd_opus_stream_decode, + nullptr, + nullptr, + mpd_opus_scan_stream, + nullptr, + opus_suffixes, + opus_mime_types, +}; diff --git a/src/decoder/plugins/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h new file mode 100644 index 000000000..260dab99a --- /dev/null +++ b/src/decoder/plugins/OpusDecoderPlugin.h @@ -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_DECODER_OPUS_H +#define MPD_DECODER_OPUS_H + +extern const struct DecoderPlugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx new file mode 100644 index 000000000..1efd64a48 --- /dev/null +++ b/src/decoder/plugins/OpusDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OpusDomain.hxx" +#include "util/Domain.hxx" + +const Domain opus_domain("opus"); diff --git a/src/decoder/plugins/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx new file mode 100644 index 000000000..fb19e0301 --- /dev/null +++ b/src/decoder/plugins/OpusDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_DOMAIN_HXX +#define MPD_OPUS_DOMAIN_HXX + +#include "check.h" + +extern const class Domain opus_domain; + +#endif diff --git a/src/decoder/plugins/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx new file mode 100644 index 000000000..bfa41d618 --- /dev/null +++ b/src/decoder/plugins/OpusHead.cxx @@ -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. + */ + +#include "config.h" +#include "OpusHead.hxx" + +#include <stdint.h> + +struct OpusHead { + char signature[8]; + uint8_t version, channels; + uint16_t pre_skip; + uint32_t sample_rate; + uint16_t output_gain; + uint8_t channel_mapping; +}; + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r) +{ + const OpusHead *h = (const OpusHead *)data; + if (size < 19 || (h->version & 0xf0) != 0) + return false; + + channels_r = h->channels; + return true; +} diff --git a/src/decoder/plugins/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx new file mode 100644 index 000000000..c478d8d90 --- /dev/null +++ b/src/decoder/plugins/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * 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_OPUS_HEAD_HXX +#define MPD_OPUS_HEAD_HXX + +#include "check.h" + +#include <stddef.h> + +bool +ScanOpusHeader(const void *data, size_t size, unsigned &channels_r); + +#endif diff --git a/src/decoder/plugins/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx new file mode 100644 index 000000000..c5b8e9107 --- /dev/null +++ b/src/decoder/plugins/OpusReader.hxx @@ -0,0 +1,101 @@ +/* + * 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_OPUS_READER_HXX +#define MPD_OPUS_READER_HXX + +#include "check.h" + +#include <algorithm> + +#include <stdint.h> +#include <string.h> + +class OpusReader { + const uint8_t *p, *const end; + +public: + OpusReader(const void *_p, size_t size) + :p((const uint8_t *)_p), end(p + size) {} + + bool Skip(size_t length) { + p += length; + return p <= end; + } + + const void *Read(size_t length) { + const uint8_t *result = p; + return Skip(length) + ? result + : nullptr; + } + + bool Expect(const void *value, size_t length) { + const void *data = Read(length); + return data != nullptr && memcmp(value, data, length) == 0; + } + + bool ReadByte(uint8_t &value_r) { + if (p >= end) + return false; + + value_r = *p++; + return true; + } + + bool ReadShort(uint16_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8); + return true; + } + + bool ReadWord(uint32_t &value_r) { + const uint8_t *value = (const uint8_t *)Read(sizeof(value_r)); + if (value == nullptr) + return false; + + value_r = value[0] | (value[1] << 8) + | (value[2] << 16) | (value[3] << 24); + return true; + } + + bool SkipString() { + uint32_t length; + return ReadWord(length) && Skip(length); + } + + char *ReadString() { + uint32_t length; + if (!ReadWord(length)) + return nullptr; + + const char *src = (const char *)Read(length); + if (src == nullptr) + return nullptr; + + char *dest = new char[length + 1]; + *std::copy_n(src, length, dest) = 0; + return dest; + } +}; + +#endif diff --git a/src/decoder/plugins/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx new file mode 100644 index 000000000..aff5479c0 --- /dev/null +++ b/src/decoder/plugins/OpusTags.cxx @@ -0,0 +1,102 @@ +/* + * 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 "OpusTags.hxx" +#include "OpusReader.hxx" +#include "XiphTags.hxx" +#include "tag/TagHandler.hxx" +#include "tag/Tag.hxx" +#include "ReplayGainInfo.hxx" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +gcc_pure +static TagType +ParseOpusTagName(const char *name) +{ + TagType type = tag_name_parse_i(name); + if (type != TAG_NUM_OF_ITEM_TYPES) + return type; + + return tag_table_lookup_i(xiph_tags, name); +} + +static void +ScanOneOpusTag(const char *name, const char *value, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx) +{ + if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { + /* R128_TRACK_GAIN is a Q7.8 fixed point number in + dB */ + + char *endptr; + long l = strtol(value, &endptr, 10); + if (endptr > value && *endptr == 0) + rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.; + } + + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + TagType t = ParseOpusTagName(name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx) +{ + OpusReader r(data, size); + if (!r.Expect("OpusTags", 8)) + return false; + + if (handler->pair == nullptr && handler->tag == nullptr) + return true; + + if (!r.SkipString()) + return false; + + uint32_t n; + if (!r.ReadWord(n)) + return false; + + while (n-- > 0) { + char *p = r.ReadString(); + if (p == nullptr) + return false; + + char *eq = strchr(p, '='); + if (eq != nullptr && eq > p) { + *eq = 0; + + ScanOneOpusTag(p, eq + 1, rgi, handler, ctx); + } + + delete[] p; + } + + return true; +} diff --git a/src/decoder/plugins/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx new file mode 100644 index 000000000..be3ac3a8d --- /dev/null +++ b/src/decoder/plugins/OpusTags.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OPUS_TAGS_HXX +#define MPD_OPUS_TAGS_HXX + +#include "check.h" + +#include <stddef.h> + +struct ReplayGainInfo; + +bool +ScanOpusTags(const void *data, size_t size, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/plugins/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx new file mode 100644 index 000000000..3b9c60691 --- /dev/null +++ b/src/decoder/plugins/PcmDecoderPlugin.cxx @@ -0,0 +1,111 @@ +/* + * 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 "PcmDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "util/ByteReverse.hxx" +#include "Log.hxx" + +#include <string.h> + +static void +pcm_stream_decode(Decoder &decoder, InputStream &is) +{ + static constexpr AudioFormat audio_format = { + 44100, + SampleFormat::S16, + 2, + }; + + const char *const mime = is.GetMimeType(); + const bool reverse_endian = mime != nullptr && + strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; + + const double time_to_size = audio_format.GetTimeToSize(); + + float total_time = -1; + const auto size = is.GetSize(); + if (size >= 0) + total_time = size / time_to_size; + + decoder_initialized(decoder, audio_format, + is.IsSeekable(), total_time); + + DecoderCommand cmd; + do { + char buffer[4096]; + + size_t nbytes = decoder_read(decoder, is, + buffer, sizeof(buffer)); + + if (nbytes == 0 && is.LockIsEOF()) + break; + + if (reverse_endian) + /* make sure we deliver samples in host byte order */ + reverse_bytes_16((uint16_t *)buffer, + (uint16_t *)buffer, + (uint16_t *)(buffer + nbytes)); + + cmd = nbytes > 0 + ? decoder_data(decoder, is, + buffer, nbytes, 0) + : decoder_get_command(decoder); + if (cmd == DecoderCommand::SEEK) { + InputStream::offset_type offset(time_to_size * + decoder_seek_where(decoder)); + + Error error; + if (is.LockSeek(offset, error)) { + decoder_command_finished(decoder); + } else { + LogError(error); + decoder_seek_error(decoder); + } + + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); +} + +static const char *const pcm_mime_types[] = { + /* for streams obtained by the cdio_paranoia input plugin */ + "audio/x-mpd-cdda-pcm", + + /* same as above, but with reverse byte order */ + "audio/x-mpd-cdda-pcm-reverse", + + nullptr +}; + +const struct DecoderPlugin pcm_decoder_plugin = { + "pcm", + nullptr, + nullptr, + pcm_stream_decode, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + pcm_mime_types, +}; diff --git a/src/decoder/plugins/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx new file mode 100644 index 000000000..3582e5856 --- /dev/null +++ b/src/decoder/plugins/PcmDecoderPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Not really a decoder; this plugin forwards its input data "as-is". + * + * It was written only to support the "cdio_paranoia" input plugin, + * which does not need a decoder. + */ + +#ifndef MPD_DECODER_PCM_HXX +#define MPD_DECODER_PCM_HXX + +extern const struct DecoderPlugin pcm_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx new file mode 100644 index 000000000..e3e3b8d96 --- /dev/null +++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx @@ -0,0 +1,435 @@ +/* + * 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 "SidplayDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/FormatString.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#include <sidplay/sidplay2.h> +#include <sidplay/builders/resid.h> +#include <sidplay/utils/SidTuneMod.h> + +#define SUBTUNE_PREFIX "tune_" + +static constexpr Domain sidplay_domain("sidplay"); + +static GPatternSpec *path_with_subtune; +static const char *songlength_file; +static GKeyFile *songlength_database; + +static bool all_files_are_containers; +static unsigned default_songlength; + +static bool filter_setting; + +static GKeyFile * +sidplay_load_songlength_db(const char *path) +{ + GError *error = nullptr; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + FormatError(sidplay_domain, + "unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return nullptr; + } + + /* replace any ; comment characters with # */ + for (gsize i = 0; i < size; i++) + if (data[i] == ';') + data[i] = '#'; + + GKeyFile *db = g_key_file_new(); + bool success = g_key_file_load_from_data(db, data, size, + G_KEY_FILE_NONE, &error); + g_free(data); + if (!success) { + FormatError(sidplay_domain, + "unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return nullptr; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const config_param ¶m) +{ + /* read the songlengths database file */ + songlength_file = param.GetBlockValue("songlength_database"); + if (songlength_file != nullptr) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength = param.GetBlockValue("default_songlength", 0u); + + all_files_are_containers = + param.GetBlockValue("all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting = param.GetBlockValue("filter", true); + + return true; +} + +static void +sidplay_finish() +{ + g_pattern_spec_free(path_with_subtune); + + if(songlength_database) + g_key_file_free(songlength_database); +} + +/** + * returns the file path stripped of any /tune_xxx.sid subtune + * suffix + */ +static char * +get_container_name(Path path_fs) +{ + char *path_container = strdup(path_fs.c_str()); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, nullptr)) + return path_container; + + char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if(ptr) *ptr='\0'; + + return path_container; +} + +/** + * returns tune number from file.sid/tune_xxx.sid style path or 1 if + * no subtune is appended + */ +static unsigned +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, nullptr)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, nullptr, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static int +get_song_length(Path path_fs) +{ + if (songlength_database == nullptr) + return -1; + + char *sid_file = get_container_name(path_fs); + SidTuneMod tune(sid_file); + free(sid_file); + if(!tune) { + LogWarning(sidplay_domain, + "failed to load file for calculating md5 sum"); + return -1; + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + const unsigned song_num = get_song_num(path_fs.c_str()); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, nullptr); + if(!values || song_num>num_items) { + g_strfreev(values); + return -1; + } + + int minutes=strtol(values[song_num-1], nullptr, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, nullptr, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return (minutes*60)+seconds; +} + +static void +sidplay_file_decode(Decoder &decoder, Path path_fs) +{ + int channels; + + /* load the tune */ + + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, nullptr, true); + free(path_container); + if (!tune) { + LogWarning(sidplay_domain, "failed to load file"); + return; + } + + const int song_num = get_song_num(path_fs.c_str()); + tune.selectSong(song_num); + + int song_len=get_song_length(path_fs); + if(song_len==-1) song_len=default_songlength; + + /* initialize the player */ + + sidplay2 player; + int iret = player.load(&tune); + if (iret != 0) { + FormatWarning(sidplay_domain, + "sidplay2.load() failed: %s", player.error()); + return; + } + + /* initialize the builder */ + + ReSIDBuilder builder("ReSID"); + if (!builder) { + LogWarning(sidplay_domain, + "failed to initialize ReSIDBuilder"); + return; + } + + builder.create(player.info().maxsids); + if (!builder) { + LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); + return; + } + + builder.filter(filter_setting); + if (!builder) { + LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed"); + return; + } + + /* configure the player */ + + sid2_config_t config = player.config(); + + config.clockDefault = SID2_CLOCK_PAL; + config.clockForced = true; + config.clockSpeed = SID2_CLOCK_CORRECT; + config.frequency = 48000; + config.optimisation = SID2_DEFAULT_OPTIMISATION; + + config.precision = 16; + config.sidDefault = SID2_MOS6581; + config.sidEmulation = &builder; + config.sidModel = SID2_MODEL_CORRECT; + config.sidSamples = true; + config.sampleFormat = IsLittleEndian() + ? SID2_LITTLE_SIGNED + : SID2_BIG_SIGNED; + if (tune.isStereo()) { + config.playback = sid2_stereo; + channels = 2; + } else { + config.playback = sid2_mono; + channels = 1; + } + + iret = player.config(config); + if (iret != 0) { + FormatWarning(sidplay_domain, + "sidplay2.config() failed: %s", player.error()); + return; + } + + /* initialize the MPD decoder */ + + const AudioFormat audio_format(48000, SampleFormat::S16, channels); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, true, (float)song_len); + + /* .. and play */ + + const unsigned timebase = player.timebase(); + song_len *= timebase; + + DecoderCommand cmd; + do { + char buffer[4096]; + size_t nbytes; + + nbytes = player.play(buffer, sizeof(buffer)); + if (nbytes == 0) + break; + + decoder_timestamp(decoder, (double)player.time() / timebase); + + cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0); + + if (cmd == DecoderCommand::SEEK) { + unsigned data_time = player.time(); + unsigned target_time = (unsigned) + (decoder_seek_where(decoder) * timebase); + + /* can't rewind so return to zero and seek forward */ + if(target_time<data_time) { + player.stop(); + data_time=0; + } + + /* ignore data until target time is reached */ + while(data_time<target_time) { + nbytes=player.play(buffer, sizeof(buffer)); + if(nbytes==0) + break; + data_time = player.time(); + } + + decoder_command_finished(decoder); + } + + if (song_len > 0 && player.time() >= (unsigned)song_len) + break; + + } while (cmd != DecoderCommand::STOP); +} + +static bool +sidplay_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + const int song_num = get_song_num(path_fs.c_str()); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, nullptr, true); + free(path_container); + if (!tune) + return false; + + const SidTuneInfo &info = tune.getInfo(); + + /* title */ + const char *title; + if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char tag_title[1024]; + snprintf(tag_title, sizeof(tag_title), + "%s (%d/%d)", + title, song_num, info.songs); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + } else + tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); + + /* artist */ + if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, + info.infoString[1]); + + /* track */ + char track[16]; + sprintf(track, "%d", song_num); + tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); + + /* time */ + int song_len=get_song_length(path_fs); + if (song_len >= 0) + tag_handler_invoke_duration(handler, handler_ctx, song_len); + + return true; +} + +static char * +sidplay_container_scan(Path path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs.c_str(), nullptr, true); + if (!tune) + return nullptr; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return nullptr; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum); + } else + return nullptr; +} + +static const char *const sidplay_suffixes[] = { + "sid", + "mus", + "str", + "prg", + "P00", + nullptr +}; + +extern const struct DecoderPlugin sidplay_decoder_plugin; +const struct DecoderPlugin sidplay_decoder_plugin = { + "sidplay", + sidplay_init, + sidplay_finish, + nullptr, /* stream_decode() */ + sidplay_file_decode, + sidplay_scan_file, + nullptr, /* stream_tag() */ + sidplay_container_scan, + sidplay_suffixes, + nullptr, /* mime_types */ +}; diff --git a/src/decoder/plugins/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx new file mode 100644 index 000000000..58786e646 --- /dev/null +++ b/src/decoder/plugins/SidplayDecoderPlugin.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_DECODER_SIDPLAY_HXX +#define MPD_DECODER_SIDPLAY_HXX + +extern const struct DecoderPlugin sidplay_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx new file mode 100644 index 000000000..c656302f9 --- /dev/null +++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx @@ -0,0 +1,308 @@ +/* + * 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 "SndfileDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <sndfile.h> + +static constexpr Domain sndfile_domain("sndfile"); + +static bool +sndfile_init(gcc_unused const config_param ¶m) +{ + LogDebug(sndfile_domain, sf_version_string()); + return true; +} + +struct SndfileInputStream { + Decoder *const decoder; + InputStream &is; + + size_t Read(void *buffer, size_t size) { + /* libsndfile chokes on partial reads; therefore + always force full reads */ + return decoder_read_full(decoder, is, buffer, size) + ? size + : 0; + } +}; + +static sf_count_t +sndfile_vio_get_filelen(void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + const InputStream &is = sis.is; + + return is.GetSize(); +} + +static sf_count_t +sndfile_vio_seek(sf_count_t _offset, int whence, void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + InputStream &is = sis.is; + + InputStream::offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + Error error; + if (!is.LockSeek(offset, error)) { + LogError(error, "Seek failed"); + return -1; + } + + return is.GetOffset(); +} + +static sf_count_t +sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + + return sis.Read(ptr, count); +} + +static sf_count_t +sndfile_vio_write(gcc_unused const void *ptr, + gcc_unused sf_count_t count, + gcc_unused void *user_data) +{ + /* no writing! */ + return -1; +} + +static sf_count_t +sndfile_vio_tell(void *user_data) +{ + SndfileInputStream &sis = *(SndfileInputStream *)user_data; + const InputStream &is = sis.is; + + return is.GetOffset(); +} + +/** + * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a + * libsndfile stream. + */ +static SF_VIRTUAL_IO vio = { + sndfile_vio_get_filelen, + sndfile_vio_seek, + sndfile_vio_read, + sndfile_vio_write, + sndfile_vio_tell, +}; + +/** + * Converts a frame number to a timestamp (in seconds). + */ +static float +frame_to_time(sf_count_t frame, const AudioFormat *audio_format) +{ + return (float)frame / (float)audio_format->sample_rate; +} + +/** + * Converts a timestamp (in seconds) to a frame number. + */ +static sf_count_t +time_to_frame(float t, const AudioFormat *audio_format) +{ + return (sf_count_t)(t * audio_format->sample_rate); +} + +static void +sndfile_stream_decode(Decoder &decoder, InputStream &is) +{ + SF_INFO info; + + info.format = 0; + + SndfileInputStream sis{&decoder, is}; + SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); + if (sf == nullptr) { + LogWarning(sndfile_domain, "sf_open_virtual() failed"); + return; + } + + /* for now, always read 32 bit samples. Later, we could lower + MPD's CPU usage by reading 16 bit samples with + sf_readf_short() on low-quality source files. */ + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, info.samplerate, + SampleFormat::S32, + info.channels, error)) { + LogError(error); + return; + } + + decoder_initialized(decoder, audio_format, info.seekable, + frame_to_time(info.frames, &audio_format)); + + int buffer[4096]; + + const size_t frame_size = audio_format.GetFrameSize(); + const sf_count_t read_frames = sizeof(buffer) / frame_size; + + DecoderCommand cmd; + do { + sf_count_t num_frames = sf_readf_int(sf, buffer, read_frames); + if (num_frames <= 0) + break; + + cmd = decoder_data(decoder, is, + buffer, num_frames * frame_size, + 0); + if (cmd == DecoderCommand::SEEK) { + sf_count_t c = + time_to_frame(decoder_seek_where(decoder), + &audio_format); + c = sf_seek(sf, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + } while (cmd == DecoderCommand::NONE); + + sf_close(sf); +} + +static void +sndfile_handle_tag(SNDFILE *sf, int str, TagType tag, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value = sf_get_string(sf, str); + if (value != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, tag, value); +} + +static constexpr struct { + int8_t str; + TagType tag; +} sndfile_tags[] = { + { SF_STR_TITLE, TAG_TITLE }, + { SF_STR_ARTIST, TAG_ARTIST }, + { SF_STR_COMMENT, TAG_COMMENT }, + { SF_STR_DATE, TAG_DATE }, + { SF_STR_ALBUM, TAG_ALBUM }, + { SF_STR_TRACKNUMBER, TAG_TRACK }, + { SF_STR_GENRE, TAG_GENRE }, +}; + +static bool +sndfile_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + SF_INFO info; + + info.format = 0; + + SndfileInputStream sis{nullptr, is}; + SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis); + if (sf == nullptr) + return false; + + if (!audio_valid_sample_rate(info.samplerate)) { + sf_close(sf); + FormatWarning(sndfile_domain, + "Invalid sample rate in %s", is.GetURI()); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, + info.frames / info.samplerate); + + for (auto i : sndfile_tags) + sndfile_handle_tag(sf, i.str, i.tag, handler, handler_ctx); + + sf_close(sf); + + return true; +} + +static const char *const sndfile_suffixes[] = { + "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ + "au", "snd", /* Sun / DEC / NeXT */ + "paf", /* Paris Audio File */ + "iff", "svx", /* Commodore Amiga IFF / SVX */ + "sf", /* IRCAM */ + "voc", /* Creative */ + "w64", /* Soundforge */ + "pvf", /* Portable Voice Format */ + "xi", /* Fasttracker */ + "htk", /* HMM Tool Kit */ + "caf", /* Apple */ + "sd2", /* Sound Designer II */ + + /* libsndfile also supports FLAC and Ogg Vorbis, but only by + linking with libFLAC and libvorbis - we can do better, we + have native plugins for these libraries */ + + nullptr +}; + +static const char *const sndfile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + + /* what are the MIME types of the other supported formats? */ + + nullptr +}; + +const struct DecoderPlugin sndfile_decoder_plugin = { + "sndfile", + sndfile_init, + nullptr, + sndfile_stream_decode, + nullptr, + nullptr, + sndfile_scan_stream, + nullptr, + sndfile_suffixes, + sndfile_mime_types, +}; diff --git a/src/decoder/plugins/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx new file mode 100644 index 000000000..d56acdd5a --- /dev/null +++ b/src/decoder/plugins/SndfileDecoderPlugin.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_DECODER_SNDFILE_HXX +#define MPD_DECODER_SNDFILE_HXX + +extern const struct DecoderPlugin sndfile_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx new file mode 100644 index 000000000..2a0820ab5 --- /dev/null +++ b/src/decoder/plugins/VorbisComments.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisComments.hxx" +#include "XiphTags.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "ReplayGainInfo.hxx" +#include "util/ASCII.hxx" +#include "util/SplitString.hxx" + +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +static const char * +vorbis_comment_value(const char *comment, const char *needle) +{ + size_t len = strlen(needle); + + if (StringEqualsCaseASCII(comment, needle, len) && + comment[len] == '=') + return comment + len + 1; + + return nullptr; +} + +bool +vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) +{ + rgi.Clear(); + + const char *temp; + bool found = false; + + while (*comments) { + if ((temp = + vorbis_comment_value(*comments, "replaygain_track_gain"))) { + rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_gain"))) { + rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_track_peak"))) { + rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_peak"))) { + rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); + found = true; + } + + comments++; + } + + return found; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +vorbis_copy_comment(const char *comment, + const char *name, TagType tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static void +vorbis_scan_comment(const char *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != nullptr) { + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) + tag_handler_invoke_pair(handler, handler_ctx, + split.GetFirst(), + split.GetSecond()); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) + if (vorbis_copy_comment(comment, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(comment, + tag_item_names[i], TagType(i), + handler, handler_ctx)) + return; +} + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx) +{ + while (*comments) + vorbis_scan_comment(*comments++, + handler, handler_ctx); + +} + +Tag * +vorbis_comments_to_tag(char **comments) +{ + TagBuilder tag_builder; + vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); + return tag_builder.IsEmpty() + ? nullptr + : tag_builder.CommitNew(); +} diff --git a/src/decoder/plugins/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx new file mode 100644 index 000000000..893c89277 --- /dev/null +++ b/src/decoder/plugins/VorbisComments.hxx @@ -0,0 +1,39 @@ +/* + * 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_VORBIS_COMMENTS_HXX +#define MPD_VORBIS_COMMENTS_HXX + +#include "check.h" + +struct ReplayGainInfo; +struct tag_handler; +struct Tag; + +bool +vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); + +void +vorbis_comments_scan(char **comments, + const tag_handler *handler, void *handler_ctx); + +Tag * +vorbis_comments_to_tag(char **comments); + +#endif diff --git a/src/decoder/plugins/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx new file mode 100644 index 000000000..72542e3a2 --- /dev/null +++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx @@ -0,0 +1,380 @@ +/* + * 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 "VorbisDecoderPlugin.h" +#include "VorbisComments.hxx" +#include "VorbisDomain.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "OggCodec.hxx" +#include "util/Error.hxx" +#include "util/Macros.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +#ifndef HAVE_TREMOR +#define OV_EXCLUDE_STATIC_CALLBACKS +#include <vorbis/vorbisfile.h> +#else +#include <tremor/ivorbisfile.h> +/* Macros to make Tremor's API look like libogg. Tremor always + returns host-byte-order 16-bit signed data, and uses integer + milliseconds where libogg uses double seconds. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include <errno.h> + +struct VorbisInputStream { + Decoder *const decoder; + + InputStream &input_stream; + bool seekable; + + VorbisInputStream(Decoder *_decoder, InputStream &_is) + :decoder(_decoder), input_stream(_is), + seekable(input_stream.CheapSeeking()) {} +}; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) +{ + VorbisInputStream *vis = (VorbisInputStream *)data; + size_t ret = decoder_read(vis->decoder, vis->input_stream, + ptr, size * nmemb); + + errno = 0; + + return ret / size; +} + +static int ogg_seek_cb(void *data, ogg_int64_t _offset, int whence) +{ + VorbisInputStream *vis = (VorbisInputStream *)data; + InputStream &is = vis->input_stream; + + if (!vis->seekable || + (vis->decoder != nullptr && + decoder_get_command(*vis->decoder) == DecoderCommand::STOP)) + return -1; + + InputStream::offset_type offset = _offset; + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + return is.LockSeek(offset, IgnoreError()) + ? 0 : -1; +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(gcc_unused void *data) +{ + return 0; +} + +static long ogg_tell_cb(void *data) +{ + VorbisInputStream *vis = (VorbisInputStream *)data; + + return (long)vis->input_stream.GetOffset(); +} + +static const ov_callbacks vorbis_is_callbacks = { + ogg_read_cb, + ogg_seek_cb, + ogg_close_cb, + ogg_tell_cb, +}; + +static const char * +vorbis_strerror(int code) +{ + switch (code) { + case OV_EREAD: + return "read error"; + + case OV_ENOTVORBIS: + return "not vorbis stream"; + + case OV_EVERSION: + return "vorbis version mismatch"; + + case OV_EBADHEADER: + return "invalid vorbis header"; + + case OV_EFAULT: + return "internal logic error"; + + default: + return "unknown error"; + } +} + +static bool +vorbis_is_open(VorbisInputStream *vis, OggVorbis_File *vf) +{ + int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); + if (ret < 0) { + if (vis->decoder == nullptr || + decoder_get_command(*vis->decoder) == DecoderCommand::NONE) + FormatWarning(vorbis_domain, + "Failed to open Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return false; + } + + return true; +} + +static void +vorbis_send_comments(Decoder &decoder, InputStream &is, + char **comments) +{ + Tag *tag = vorbis_comments_to_tag(comments); + if (!tag) + return; + + decoder_tag(decoder, is, std::move(*tag)); + delete tag; +} + +#ifndef HAVE_TREMOR +static void +vorbis_interleave(float *dest, const float *const*src, + unsigned nframes, unsigned channels) +{ + for (const float *const*src_end = src + channels; + src != src_end; ++src, ++dest) { + float *gcc_restrict d = dest; + for (const float *gcc_restrict s = *src, *s_end = s + nframes; + s != s_end; ++s, d += channels) + *d = *s; + } +} +#endif + +/* public */ + +static bool +vorbis_init(gcc_unused const config_param ¶m) +{ +#ifndef HAVE_TREMOR + LogDebug(vorbis_domain, vorbis_version_string()); +#endif + return true; +} + +static void +vorbis_stream_decode(Decoder &decoder, + InputStream &input_stream) +{ + if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS) + return; + + /* rewind the stream, because ogg_codec_detect() has + moved it */ + input_stream.LockRewind(IgnoreError()); + + VorbisInputStream vis(&decoder, input_stream); + OggVorbis_File vf; + if (!vorbis_is_open(&vis, &vf)) + return; + + const vorbis_info *vi = ov_info(&vf, -1); + if (vi == nullptr) { + LogWarning(vorbis_domain, "ov_info() has failed"); + return; + } + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, vi->rate, +#ifdef HAVE_TREMOR + SampleFormat::S16, +#else + SampleFormat::FLOAT, +#endif + vi->channels, error)) { + LogError(error); + return; + } + + float total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, audio_format, vis.seekable, total_time); + +#ifdef HAVE_TREMOR + char buffer[4096]; +#else + float buffer[2048]; + const int frames_per_buffer = + ARRAY_SIZE(buffer) / audio_format.channels; + const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; +#endif + + int prev_section = -1; + unsigned kbit_rate = 0; + + DecoderCommand cmd = decoder_get_command(decoder); + do { + if (cmd == DecoderCommand::SEEK) { + double seek_where = decoder_seek_where(decoder); + if (0 == ov_time_seek_page(&vf, seek_where)) { + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + + int current_section; + +#ifdef HAVE_TREMOR + long nbytes = ov_read(&vf, buffer, sizeof(buffer), + IsBigEndian(), 2, 1, + ¤t_section); +#else + float **per_channel; + long nframes = ov_read_float(&vf, &per_channel, + frames_per_buffer, + ¤t_section); + long nbytes = nframes; + if (nframes > 0) { + vorbis_interleave(buffer, + (const float*const*)per_channel, + nframes, audio_format.channels); + nbytes *= frame_size; + } +#endif + + if (nbytes == OV_HOLE) /* bad packet */ + nbytes = 0; + else if (nbytes <= 0) + /* break on EOF or other error */ + break; + + if (current_section != prev_section) { + vi = ov_info(&vf, -1); + if (vi == nullptr) { + LogWarning(vorbis_domain, + "ov_info() has failed"); + break; + } + + if (vi->rate != (long)audio_format.sample_rate || + vi->channels != (int)audio_format.channels) { + /* we don't support audio format + change yet */ + LogWarning(vorbis_domain, + "audio format change, stopping here"); + break; + } + + char **comments = ov_comment(&vf, -1)->user_comments; + vorbis_send_comments(decoder, input_stream, comments); + + ReplayGainInfo rgi; + if (vorbis_comments_to_replay_gain(rgi, comments)) + decoder_replay_gain(decoder, &rgi); + + prev_section = current_section; + } + + long test = ov_bitrate_instant(&vf); + if (test > 0) + kbit_rate = test / 1000; + + cmd = decoder_data(decoder, input_stream, + buffer, nbytes, + kbit_rate); + } while (cmd != DecoderCommand::STOP); + + ov_clear(&vf); +} + +static bool +vorbis_scan_stream(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + VorbisInputStream vis(nullptr, is); + OggVorbis_File vf; + + if (!vorbis_is_open(&vis, &vf)) + return false; + + tag_handler_invoke_duration(handler, handler_ctx, + (int)(ov_time_total(&vf, -1) + 0.5)); + + vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, + handler, handler_ctx); + + ov_clear(&vf); + return true; +} + +static const char *const vorbis_suffixes[] = { + "ogg", "oga", nullptr +}; + +static const char *const vorbis_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/vorbis", + "audio/vorbis+ogg", + "audio/x-ogg", + "audio/x-vorbis", + "audio/x-vorbis+ogg", + nullptr +}; + +const struct DecoderPlugin vorbis_decoder_plugin = { + "vorbis", + vorbis_init, + nullptr, + vorbis_stream_decode, + nullptr, + nullptr, + vorbis_scan_stream, + nullptr, + vorbis_suffixes, + vorbis_mime_types +}; diff --git a/src/decoder/plugins/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h new file mode 100644 index 000000000..b54df2e97 --- /dev/null +++ b/src/decoder/plugins/VorbisDecoderPlugin.h @@ -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_DECODER_VORBIS_H +#define MPD_DECODER_VORBIS_H + +extern const struct DecoderPlugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx new file mode 100644 index 000000000..e3d880efa --- /dev/null +++ b/src/decoder/plugins/VorbisDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "VorbisDomain.hxx" +#include "util/Domain.hxx" + +const Domain vorbis_domain("vorbis"); diff --git a/src/decoder/plugins/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx new file mode 100644 index 000000000..48715e328 --- /dev/null +++ b/src/decoder/plugins/VorbisDomain.hxx @@ -0,0 +1,29 @@ +/* + * 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_VORBIS_DOMAIN_HXX +#define MPD_VORBIS_DOMAIN_HXX + +#include "check.h" + +class Domain; + +extern const Domain vorbis_domain; + +#endif diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx new file mode 100644 index 000000000..2f60090c1 --- /dev/null +++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx @@ -0,0 +1,573 @@ +/* + * 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 "WavpackDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/ApeTag.hxx" +#include "fs/Path.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <wavpack/wavpack.h> +#include <glib.h> + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#define ERRORLEN 80 + +static constexpr Domain wavpack_domain("wavpack"); + +/** A pointer type for format converter function. */ +typedef void (*format_samples_t)( + int bytes_per_sample, + void *buffer, uint32_t count +); + +/* + * This function has been borrowed from the tiny player found on + * wavpack.com. Modifications were required because mpd only handles + * max 24-bit samples. + */ +static void +format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) +{ + int32_t *src = (int32_t *)buffer; + + switch (bytes_per_sample) { + case 1: { + int8_t *dst = (int8_t *)buffer; + /* + * The asserts like the following one are because we do the + * formatting of samples within a single buffer. The size + * of the output samples never can be greater than the size + * of the input ones. Otherwise we would have an overflow. + */ + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 8-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + case 2: { + uint16_t *dst = (uint16_t *)buffer; + static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size"); + + /* pass through and align 16-bit samples */ + while (count--) { + *dst++ = *src++; + } + break; + } + + case 3: + case 4: + /* do nothing */ + break; + } +} + +/* + * This function converts floating point sample data to 24-bit integer. + */ +static void +format_samples_float(gcc_unused int bytes_per_sample, void *buffer, + uint32_t count) +{ + float *p = (float *)buffer; + + while (count--) { + *p /= (1 << 23); + ++p; + } +} + +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static SampleFormat +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SampleFormat::FLOAT; + + switch (bytes_per_sample) { + case 1: + return SampleFormat::S8; + + case 2: + return SampleFormat::S16; + + case 3: + return SampleFormat::S24_P32; + + case 4: + return SampleFormat::S32; + + default: + return SampleFormat::UNDEFINED; + } +} + +/* + * This does the main decoding thing. + * Requires an already opened WavpackContext. + */ +static void +wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek) +{ + bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + SampleFormat sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), error)) { + LogError(error); + return; + } + + const format_samples_t format_samples = is_float + ? format_samples_float + : format_samples_int; + + const float total_time = float(WavpackGetNumSamples(wpc)) + / audio_format.sample_rate; + + const int bytes_per_sample = WavpackGetBytesPerSample(wpc); + const int output_sample_size = audio_format.GetFrameSize(); + + /* wavpack gives us all kind of samples in a 32-bit space */ + int32_t chunk[1024]; + const uint32_t samples_requested = ARRAY_SIZE(chunk) / + audio_format.channels; + + decoder_initialized(decoder, audio_format, can_seek, total_time); + + DecoderCommand cmd = decoder_get_command(decoder); + while (cmd != DecoderCommand::STOP) { + if (cmd == DecoderCommand::SEEK) { + if (can_seek) { + unsigned where = decoder_seek_where(decoder) * + audio_format.sample_rate; + + if (WavpackSeekSample(wpc, where)) { + decoder_command_finished(decoder); + } else { + decoder_seek_error(decoder); + } + } else { + decoder_seek_error(decoder); + } + } + + uint32_t samples_got = WavpackUnpackSamples(wpc, chunk, + samples_requested); + if (samples_got == 0) + break; + + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + + 0.5); + format_samples(bytes_per_sample, chunk, + samples_got * audio_format.channels); + + cmd = decoder_data(decoder, nullptr, chunk, + samples_got * output_sample_size, + bitrate); + } +} + +/** + * Locate and parse a floating point tag. Returns true if it was + * found. + */ +static bool +wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) +{ + char buffer[64]; + if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0) + return false; + + *value_r = atof(buffer); + return true; +} + +static bool +wavpack_replaygain(ReplayGainInfo &rgi, + WavpackContext *wpc) +{ + rgi.Clear(); + + bool found = false; + found |= wavpack_tag_float(wpc, "replaygain_track_gain", + &rgi.tuples[REPLAY_GAIN_TRACK].gain); + found |= wavpack_tag_float(wpc, "replaygain_track_peak", + &rgi.tuples[REPLAY_GAIN_TRACK].peak); + found |= wavpack_tag_float(wpc, "replaygain_album_gain", + &rgi.tuples[REPLAY_GAIN_ALBUM].gain); + found |= wavpack_tag_float(wpc, "replaygain_album_peak", + &rgi.tuples[REPLAY_GAIN_ALBUM].peak); + + return found; +} + +static void +wavpack_scan_tag_item(WavpackContext *wpc, const char *name, + TagType type, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[1024]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_tag(handler, handler_ctx, type, buffer); + +} + +static void +wavpack_scan_pair(WavpackContext *wpc, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[8192]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_pair(handler, handler_ctx, name, buffer); +} + +/* + * Reads metainfo from the specified file. + */ +static bool +wavpack_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + char error[ERRORLEN]; + WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error, + OPEN_TAGS, 0); + if (wpc == nullptr) { + FormatError(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + path_fs.c_str(), error); + return false; + } + + tag_handler_invoke_duration(handler, handler_ctx, + WavpackGetNumSamples(wpc) / + WavpackGetSampleRate(wpc)); + + /* the WavPack format implies APEv2 tags, which means we can + reuse the mapping from tag_ape.c */ + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + const char *name = tag_item_names[i]; + if (name != nullptr) + wavpack_scan_tag_item(wpc, name, (TagType)i, + handler, handler_ctx); + } + + for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i) + wavpack_scan_tag_item(wpc, i->name, i->type, + handler, handler_ctx); + + if (handler->pair != nullptr) { + char name[64]; + + for (int i = 0, n = WavpackGetNumTagItems(wpc); + i < n; ++i) { + int len = WavpackGetTagItemIndexed(wpc, i, name, + sizeof(name)); + if (len <= 0 || (unsigned)len >= sizeof(name)) + continue; + + wavpack_scan_pair(wpc, name, handler, handler_ctx); + } + } + + WavpackCloseFile(wpc); + + return true; +} + +/* + * mpd input_stream <=> WavpackStreamReader wrapper callbacks + */ + +/* This struct is needed for per-stream last_byte storage. */ +struct WavpackInput { + Decoder &decoder; + InputStream &is; + /* Needed for push_back_byte() */ + int last_byte; + + constexpr WavpackInput(Decoder &_decoder, InputStream &_is) + :decoder(_decoder), is(_is), last_byte(EOF) {} + + int32_t ReadBytes(void *data, size_t bcount); +}; + +/** + * Little wrapper for struct WavpackInput to cast from void *. + */ +static WavpackInput * +wpin(void *id) +{ + assert(id); + return (WavpackInput *)id; +} + +static int32_t +wavpack_input_read_bytes(void *id, void *data, int32_t bcount) +{ + return wpin(id)->ReadBytes(data, bcount); +} + +int32_t +WavpackInput::ReadBytes(void *data, size_t bcount) +{ + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (last_byte != EOF) { + *buf++ = last_byte; + last_byte = EOF; + --bcount; + ++i; + } + + /* wavpack fails if we return a partial read, so we just wait + until the buffer is full */ + while (bcount > 0) { + size_t nbytes = decoder_read(&decoder, is, buf, bcount); + if (nbytes == 0) { + /* EOF, error or a decoder command */ + break; + } + + i += nbytes; + bcount -= nbytes; + buf += nbytes; + } + + return i; +} + +static uint32_t +wavpack_input_get_pos(void *id) +{ + return wpin(id)->is.GetOffset(); +} + +static int +wavpack_input_set_pos_abs(void *id, uint32_t pos) +{ + return wpin(id)->is.LockSeek(pos, IgnoreError()) ? 0 : -1; +} + +static int +wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) +{ + InputStream &is = wpin(id)->is; + + InputStream::offset_type offset = delta; + switch (mode) { + case SEEK_SET: + break; + + case SEEK_CUR: + offset += is.GetOffset(); + break; + + case SEEK_END: + if (!is.KnownSize()) + return -1; + + offset += is.GetSize(); + break; + + default: + return -1; + } + + return is.LockSeek(offset, IgnoreError()) ? 0 : -1; +} + +static int +wavpack_input_push_back_byte(void *id, int c) +{ + if (wpin(id)->last_byte == EOF) { + wpin(id)->last_byte = c; + return c; + } else { + return EOF; + } +} + +static uint32_t +wavpack_input_get_length(void *id) +{ + if (!wpin(id)->is.KnownSize()) + return 0; + + return wpin(id)->is.GetSize(); +} + +static int +wavpack_input_can_seek(void *id) +{ + return wpin(id)->is.IsSeekable(); +} + +static WavpackStreamReader mpd_is_reader = { + wavpack_input_read_bytes, + wavpack_input_get_pos, + wavpack_input_set_pos_abs, + wavpack_input_set_pos_rel, + wavpack_input_push_back_byte, + wavpack_input_get_length, + wavpack_input_can_seek, + nullptr /* no need to write edited tags */ +}; + +static WavpackInput * +wavpack_open_wvc(Decoder &decoder, const char *uri) +{ + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + if (uri == nullptr) + return nullptr; + + char *wvc_url = g_strconcat(uri, "c", nullptr); + + InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError()); + g_free(wvc_url); + + if (is_wvc == nullptr) + return nullptr; + + return new WavpackInput(decoder, *is_wvc); +} + +/* + * Decodes a stream. + */ +static void +wavpack_streamdecode(Decoder &decoder, InputStream &is) +{ + int open_flags = OPEN_NORMALIZE; + bool can_seek = is.IsSeekable(); + + WavpackInput *wvc = wavpack_open_wvc(decoder, is.GetURI()); + if (wvc != nullptr) { + open_flags |= OPEN_WVC; + can_seek &= wvc->is.IsSeekable(); + } + + if (!can_seek) { + open_flags |= OPEN_STREAMING; + } + + WavpackInput isp(decoder, is); + + char error[ERRORLEN]; + WavpackContext *wpc = + WavpackOpenFileInputEx(&mpd_is_reader, &isp, wvc, + error, open_flags, 23); + + if (wpc == nullptr) { + FormatError(wavpack_domain, + "failed to open WavPack stream: %s", error); + return; + } + + wavpack_decode(decoder, wpc, can_seek); + + WavpackCloseFile(wpc); + + if (wvc != nullptr) { + delete &wvc->is; + delete wvc; + } +} + +/* + * Decodes a file. + */ +static void +wavpack_filedecode(Decoder &decoder, Path path_fs) +{ + char error[ERRORLEN]; + WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error, + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, + 23); + if (wpc == nullptr) { + FormatWarning(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + path_fs.c_str(), error); + return; + } + + ReplayGainInfo rgi; + if (wavpack_replaygain(rgi, wpc)) + decoder_replay_gain(decoder, &rgi); + + wavpack_decode(decoder, wpc, true); + + WavpackCloseFile(wpc); +} + +static char const *const wavpack_suffixes[] = { + "wv", + nullptr +}; + +static char const *const wavpack_mime_types[] = { + "audio/x-wavpack", + nullptr +}; + +const struct DecoderPlugin wavpack_decoder_plugin = { + "wavpack", + nullptr, + nullptr, + wavpack_streamdecode, + wavpack_filedecode, + wavpack_scan_file, + nullptr, + nullptr, + wavpack_suffixes, + wavpack_mime_types +}; diff --git a/src/decoder/plugins/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx new file mode 100644 index 000000000..2e5f9bd42 --- /dev/null +++ b/src/decoder/plugins/WavpackDecoderPlugin.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_DECODER_WAVPACK_HXX +#define MPD_DECODER_WAVPACK_HXX + +extern const struct DecoderPlugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx new file mode 100644 index 000000000..a3a4b2745 --- /dev/null +++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx @@ -0,0 +1,159 @@ +/* + * 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 "WildmidiDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "fs/Path.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +extern "C" { +#include <wildmidi_lib.h> +} + +static constexpr Domain wildmidi_domain("wildmidi"); + +static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000; + +static bool +wildmidi_init(const config_param ¶m) +{ + Error error; + const AllocatedPath path = + param.GetBlockPath("config_file", + "/etc/timidity/timidity.cfg", + error); + if (path.IsNull()) + FatalError(error); + + if (!FileExists(path)) { + const auto utf8 = path.ToUTF8(); + FormatDebug(wildmidi_domain, + "configuration file does not exist: %s", + utf8.c_str()); + return false; + } + + return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0; +} + +static void +wildmidi_finish(void) +{ + WildMidi_Shutdown(); +} + +static void +wildmidi_file_decode(Decoder &decoder, Path path_fs) +{ + static constexpr AudioFormat audio_format = { + WILDMIDI_SAMPLE_RATE, + SampleFormat::S16, + 2, + }; + midi *wm; + const struct _WM_Info *info; + + wm = WildMidi_Open(path_fs.c_str()); + if (wm == nullptr) + return; + + info = WildMidi_GetInfo(wm); + if (info == nullptr) { + WildMidi_Close(wm); + return; + } + + decoder_initialized(decoder, audio_format, true, + info->approx_total_samples / WILDMIDI_SAMPLE_RATE); + + DecoderCommand cmd; + do { + char buffer[4096]; + int len; + + info = WildMidi_GetInfo(wm); + if (info == nullptr) + break; + + len = WildMidi_GetOutput(wm, buffer, sizeof(buffer)); + if (len <= 0) + break; + + cmd = decoder_data(decoder, nullptr, buffer, len, 0); + + if (cmd == DecoderCommand::SEEK) { + unsigned long seek_where = WILDMIDI_SAMPLE_RATE * + decoder_seek_where(decoder); + + WildMidi_FastSeek(wm, &seek_where); + decoder_command_finished(decoder); + cmd = DecoderCommand::NONE; + } + + } while (cmd == DecoderCommand::NONE); + + WildMidi_Close(wm); +} + +static bool +wildmidi_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + midi *wm = WildMidi_Open(path_fs.c_str()); + if (wm == nullptr) + return false; + + const struct _WM_Info *info = WildMidi_GetInfo(wm); + if (info == nullptr) { + WildMidi_Close(wm); + return false; + } + + int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; + tag_handler_invoke_duration(handler, handler_ctx, duration); + + WildMidi_Close(wm); + + return true; +} + +static const char *const wildmidi_suffixes[] = { + "mid", + nullptr +}; + +const struct DecoderPlugin wildmidi_decoder_plugin = { + "wildmidi", + wildmidi_init, + wildmidi_finish, + nullptr, + wildmidi_file_decode, + wildmidi_scan_file, + nullptr, + nullptr, + wildmidi_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx new file mode 100644 index 000000000..fc87aab80 --- /dev/null +++ b/src/decoder/plugins/WildmidiDecoderPlugin.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_DECODER_WILDMIDI_HXX +#define MPD_DECODER_WILDMIDI_HXX + +extern const struct DecoderPlugin wildmidi_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx new file mode 100644 index 000000000..11a0bcd42 --- /dev/null +++ b/src/decoder/plugins/XiphTags.cxx @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/* This File contains additional Tags for Xiph-Based Formats like Ogg-Vorbis, + * Flac and Opus which will be used in addition to the Tags in tag/TagNames.c + * see https://www.xiph.org/vorbis/doc/v-comment.html for further Info + */ +#include "config.h" +#include "XiphTags.hxx" + +const struct tag_table xiph_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { "description", TAG_COMMENT }, + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/decoder/plugins/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx new file mode 100644 index 000000000..48a27425f --- /dev/null +++ b/src/decoder/plugins/XiphTags.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_XIPH_TAGS_HXX +#define MPD_XIPH_TAGS_HXX + +#include "check.h" +#include "tag/TagTable.hxx" + +extern const struct tag_table xiph_tags[]; + +#endif diff --git a/src/encoder/EncoderAPI.hxx b/src/encoder/EncoderAPI.hxx new file mode 100644 index 000000000..b147eac21 --- /dev/null +++ b/src/encoder/EncoderAPI.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This header is included by encoder plugins. + * + */ + +#ifndef MPD_ENCODER_API_HXX +#define MPD_ENCODER_API_HXX + +// IWYU pragma: begin_exports + +#include "EncoderPlugin.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +#endif diff --git a/src/encoder/EncoderList.cxx b/src/encoder/EncoderList.cxx new file mode 100644 index 000000000..4bca5a4fe --- /dev/null +++ b/src/encoder/EncoderList.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "EncoderList.hxx" +#include "EncoderPlugin.hxx" +#include "plugins/NullEncoderPlugin.hxx" +#include "plugins/WaveEncoderPlugin.hxx" +#include "plugins/VorbisEncoderPlugin.hxx" +#include "plugins/OpusEncoderPlugin.hxx" +#include "plugins/FlacEncoderPlugin.hxx" +#include "plugins/ShineEncoderPlugin.hxx" +#include "plugins/LameEncoderPlugin.hxx" +#include "plugins/TwolameEncoderPlugin.hxx" + +#include <string.h> + +const EncoderPlugin *const encoder_plugins[] = { + &null_encoder_plugin, +#ifdef ENABLE_VORBIS_ENCODER + &vorbis_encoder_plugin, +#endif +#ifdef HAVE_OPUS + &opus_encoder_plugin, +#endif +#ifdef ENABLE_LAME_ENCODER + &lame_encoder_plugin, +#endif +#ifdef ENABLE_TWOLAME_ENCODER + &twolame_encoder_plugin, +#endif +#ifdef ENABLE_WAVE_ENCODER + &wave_encoder_plugin, +#endif +#ifdef ENABLE_FLAC_ENCODER + &flac_encoder_plugin, +#endif +#ifdef ENABLE_SHINE_ENCODER + &shine_encoder_plugin, +#endif + nullptr +}; + +const EncoderPlugin * +encoder_plugin_get(const char *name) +{ + encoder_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return nullptr; +} diff --git a/src/encoder/EncoderList.hxx b/src/encoder/EncoderList.hxx new file mode 100644 index 000000000..e18d8ec74 --- /dev/null +++ b/src/encoder/EncoderList.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_ENCODER_LIST_HXX +#define MPD_ENCODER_LIST_HXX + +struct EncoderPlugin; + +extern const EncoderPlugin *const encoder_plugins[]; + +#define encoder_plugins_for_each(plugin) \ + for (const EncoderPlugin *plugin, \ + *const*encoder_plugin_iterator = &encoder_plugins[0]; \ + (plugin = *encoder_plugin_iterator) != nullptr; \ + ++encoder_plugin_iterator) + +/** + * Looks up an encoder plugin by its name. + * + * @param name the encoder name to look for + * @return the encoder plugin with the specified name, or nullptr if none + * was found + */ +const EncoderPlugin * +encoder_plugin_get(const char *name); + +#endif diff --git a/src/encoder/EncoderPlugin.hxx b/src/encoder/EncoderPlugin.hxx new file mode 100644 index 000000000..95e4e5838 --- /dev/null +++ b/src/encoder/EncoderPlugin.hxx @@ -0,0 +1,321 @@ +/* + * 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_ENCODER_PLUGIN_HXX +#define MPD_ENCODER_PLUGIN_HXX + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> + +struct EncoderPlugin; +struct AudioFormat; +struct config_param; +struct Tag; +class Error; + +struct Encoder { + const EncoderPlugin &plugin; + +#ifndef NDEBUG + bool open, pre_tag, tag, end; +#endif + + explicit Encoder(const EncoderPlugin &_plugin) + :plugin(_plugin) +#ifndef NDEBUG + , open(false) +#endif + {} +}; + +struct EncoderPlugin { + const char *name; + + Encoder *(*init)(const config_param ¶m, + Error &error); + + void (*finish)(Encoder *encoder); + + bool (*open)(Encoder *encoder, + AudioFormat &audio_format, + Error &error); + + void (*close)(Encoder *encoder); + + bool (*end)(Encoder *encoder, Error &error); + + bool (*flush)(Encoder *encoder, Error &error); + + bool (*pre_tag)(Encoder *encoder, Error &error); + + bool (*tag)(Encoder *encoder, const Tag *tag, + Error &error); + + bool (*write)(Encoder *encoder, + const void *data, size_t length, + Error &error); + + size_t (*read)(Encoder *encoder, void *dest, size_t length); + + const char *(*get_mime_type)(Encoder *encoder); +}; + +/** + * Creates a new encoder object. + * + * @param plugin the encoder plugin + * @param param optional configuration + * @param error location to store the error occurring, or nullptr to ignore errors. + * @return an encoder object on success, nullptr on failure + */ +static inline Encoder * +encoder_init(const EncoderPlugin &plugin, const config_param ¶m, + Error &error_r) +{ + return plugin.init(param, error_r); +} + +/** + * Frees an encoder object. + * + * @param encoder the encoder + */ +static inline void +encoder_finish(Encoder *encoder) +{ + assert(!encoder->open); + + encoder->plugin.finish(encoder); +} + +/** + * Opens an encoder object. You must call this prior to using it. + * Before you free it, you must call encoder_close(). You may open + * and close (reuse) one encoder any number of times. + * + * After this function returns successfully and before the first + * encoder_write() call, you should invoke encoder_read() to obtain + * the file header. + * + * @param encoder the encoder + * @param audio_format the encoder's input audio format; the plugin + * may modify the struct to adapt it to its abilities + * @return true on success + */ +static inline bool +encoder_open(Encoder *encoder, AudioFormat &audio_format, + Error &error) +{ + assert(!encoder->open); + + bool success = encoder->plugin.open(encoder, audio_format, error); +#ifndef NDEBUG + encoder->open = success; + encoder->pre_tag = encoder->tag = encoder->end = false; +#endif + return success; +} + +/** + * Closes an encoder object. This disables the encoder, and readies + * it for reusal by calling encoder_open() again. + * + * @param encoder the encoder + */ +static inline void +encoder_close(Encoder *encoder) +{ + assert(encoder->open); + + if (encoder->plugin.close != nullptr) + encoder->plugin.close(encoder); + +#ifndef NDEBUG + encoder->open = false; +#endif +} + +/** + * Ends the stream: flushes the encoder object, generate an + * end-of-stream marker (if applicable), make everything which might + * currently be buffered available by encoder_read(). + * + * After this function has been called, the encoder may not be usable + * for more data, and only encoder_read() and encoder_close() can be + * called. + * + * @param encoder the encoder + * @return true on success + */ +static inline bool +encoder_end(Encoder *encoder, Error &error) +{ + assert(encoder->open); + assert(!encoder->end); + +#ifndef NDEBUG + encoder->end = true; +#endif + + /* this method is optional */ + return encoder->plugin.end != nullptr + ? encoder->plugin.end(encoder, error) + : true; +} + +/** + * Flushes an encoder object, make everything which might currently be + * buffered available by encoder_read(). + * + * @param encoder the encoder + * @return true on success + */ +static inline bool +encoder_flush(Encoder *encoder, Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + + /* this method is optional */ + return encoder->plugin.flush != nullptr + ? encoder->plugin.flush(encoder, error) + : true; +} + +/** + * Prepare for sending a tag to the encoder. This is used by some + * encoders to flush the previous sub-stream, in preparation to begin + * a new one. + * + * @param encoder the encoder + * @param tag the tag object + * @return true on success + */ +static inline bool +encoder_pre_tag(Encoder *encoder, Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + + /* this method is optional */ + bool success = encoder->plugin.pre_tag != nullptr + ? encoder->plugin.pre_tag(encoder, error) + : true; + +#ifndef NDEBUG + encoder->pre_tag = success; +#endif + return success; +} + +/** + * Sends a tag to the encoder. + * + * Instructions: call encoder_pre_tag(); then obtain flushed data with + * encoder_read(); finally call encoder_tag(). + * + * @param encoder the encoder + * @param tag the tag object + * @return true on success + */ +static inline bool +encoder_tag(Encoder *encoder, const Tag *tag, Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(encoder->tag); + assert(!encoder->end); + +#ifndef NDEBUG + encoder->tag = false; +#endif + + /* this method is optional */ + return encoder->plugin.tag != nullptr + ? encoder->plugin.tag(encoder, tag, error) + : true; +} + +/** + * Writes raw PCM data to the encoder. + * + * @param encoder the encoder + * @param data the buffer containing PCM samples + * @param length the length of the buffer in bytes + * @return true on success + */ +static inline bool +encoder_write(Encoder *encoder, const void *data, size_t length, + Error &error) +{ + assert(encoder->open); + assert(!encoder->pre_tag); + assert(!encoder->tag); + assert(!encoder->end); + + return encoder->plugin.write(encoder, data, length, error); +} + +/** + * Reads encoded data from the encoder. + * + * Call this repeatedly until no more data is returned. + * + * @param encoder the encoder + * @param dest the destination buffer to copy to + * @param length the maximum length of the destination buffer + * @return the number of bytes written to #dest + */ +static inline size_t +encoder_read(Encoder *encoder, void *dest, size_t length) +{ + assert(encoder->open); + assert(!encoder->pre_tag || !encoder->tag); + +#ifndef NDEBUG + if (encoder->pre_tag) { + encoder->pre_tag = false; + encoder->tag = true; + } +#endif + + return encoder->plugin.read(encoder, dest, length); +} + +/** + * Get mime type of encoded content. + * + * @param plugin the encoder plugin + * @return an constant string, nullptr on failure + */ +static inline const char * +encoder_get_mime_type(Encoder *encoder) +{ + /* this method is optional */ + return encoder->plugin.get_mime_type != nullptr + ? encoder->plugin.get_mime_type(encoder) + : nullptr; +} + +#endif diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx deleted file mode 100644 index fa7ed992d..000000000 --- a/src/encoder/FlacEncoderPlugin.cxx +++ /dev/null @@ -1,342 +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 "FlacEncoderPlugin.hxx" -#include "EncoderAPI.hxx" -#include "AudioFormat.hxx" -#include "pcm/PcmBuffer.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/fifo_buffer.h" - -extern "C" { -#include "util/growing_fifo.h" -} - -#include <assert.h> -#include <string.h> - -#include <FLAC/stream_encoder.h> - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -#error libFLAC is too old -#endif - -struct flac_encoder { - Encoder encoder; - - AudioFormat audio_format; - unsigned compression; - - FLAC__StreamEncoder *fse; - - PcmBuffer expand_buffer; - - /** - * This buffer will hold encoded data from libFLAC until it is - * picked up with flac_encoder_read(). - */ - struct fifo_buffer *output_buffer; - - flac_encoder():encoder(flac_encoder_plugin) {} -}; - -static constexpr Domain flac_encoder_domain("vorbis_encoder"); - -static bool -flac_encoder_configure(struct flac_encoder *encoder, const config_param ¶m, - gcc_unused Error &error) -{ - encoder->compression = param.GetBlockValue("compression", 5u); - - return true; -} - -static Encoder * -flac_encoder_init(const config_param ¶m, Error &error) -{ - flac_encoder *encoder = new flac_encoder(); - - /* load configuration from "param" */ - if (!flac_encoder_configure(encoder, param, error)) { - /* configuration has failed, roll back and return error */ - delete encoder; - return nullptr; - } - - return &encoder->encoder; -} - -static void -flac_encoder_finish(Encoder *_encoder) -{ - struct flac_encoder *encoder = (struct flac_encoder *)_encoder; - - /* the real libFLAC cleanup was already performed by - flac_encoder_close(), so no real work here */ - delete encoder; -} - -static bool -flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, - Error &error) -{ - if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, - encoder->compression)) { - error.Format(config_domain, - "error setting flac compression to %d", - encoder->compression); - return false; - } - - if ( !FLAC__stream_encoder_set_channels(encoder->fse, - encoder->audio_format.channels)) { - error.Format(config_domain, - "error setting flac channels num to %d", - encoder->audio_format.channels); - return false; - } - if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse, - bits_per_sample)) { - error.Format(config_domain, - "error setting flac bit format to %d", - bits_per_sample); - return false; - } - if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse, - encoder->audio_format.sample_rate)) { - error.Format(config_domain, - "error setting flac sample rate to %d", - encoder->audio_format.sample_rate); - return false; - } - return true; -} - -static FLAC__StreamEncoderWriteStatus -flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse, - const FLAC__byte data[], - size_t bytes, - gcc_unused unsigned samples, - gcc_unused unsigned current_frame, void *client_data) -{ - struct flac_encoder *encoder = (struct flac_encoder *) client_data; - - //transfer data to buffer - growing_fifo_append(&encoder->output_buffer, data, bytes); - - return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; -} - -static void -flac_encoder_close(Encoder *_encoder) -{ - struct flac_encoder *encoder = (struct flac_encoder *)_encoder; - - FLAC__stream_encoder_delete(encoder->fse); - - encoder->expand_buffer.Clear(); - fifo_buffer_free(encoder->output_buffer); -} - -static bool -flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) -{ - struct flac_encoder *encoder = (struct flac_encoder *)_encoder; - unsigned bits_per_sample; - - encoder->audio_format = audio_format; - - /* FIXME: flac should support 32bit as well */ - switch (audio_format.format) { - case SampleFormat::S8: - bits_per_sample = 8; - break; - - case SampleFormat::S16: - bits_per_sample = 16; - break; - - case SampleFormat::S24_P32: - bits_per_sample = 24; - break; - - default: - bits_per_sample = 24; - audio_format.format = SampleFormat::S24_P32; - } - - /* allocate the encoder */ - encoder->fse = FLAC__stream_encoder_new(); - if (encoder->fse == nullptr) { - error.Set(flac_encoder_domain, "flac_new() failed"); - return false; - } - - if (!flac_encoder_setup(encoder, bits_per_sample, error)) { - FLAC__stream_encoder_delete(encoder->fse); - return false; - } - - encoder->output_buffer = growing_fifo_new(); - - /* this immediately outputs data through callback */ - - { - FLAC__StreamEncoderInitStatus init_status; - - init_status = FLAC__stream_encoder_init_stream(encoder->fse, - flac_write_callback, - nullptr, nullptr, nullptr, encoder); - - if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { - error.Format(flac_encoder_domain, - "failed to initialize encoder: %s\n", - FLAC__StreamEncoderInitStatusString[init_status]); - flac_encoder_close(_encoder); - return false; - } - } - - return true; -} - - -static bool -flac_encoder_flush(Encoder *_encoder, gcc_unused Error &error) -{ - struct flac_encoder *encoder = (struct flac_encoder *)_encoder; - - (void) FLAC__stream_encoder_finish(encoder->fse); - return true; -} - -static inline void -pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) -{ - while (num_samples > 0) { - *out++ = *in++; - --num_samples; - } -} - -static inline void -pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples) -{ - while (num_samples > 0) { - *out++ = *in++; - --num_samples; - } -} - -static bool -flac_encoder_write(Encoder *_encoder, - const void *data, size_t length, - gcc_unused Error &error) -{ - struct flac_encoder *encoder = (struct flac_encoder *)_encoder; - unsigned num_frames, num_samples; - void *exbuffer; - const void *buffer = nullptr; - - /* format conversion */ - - num_frames = length / encoder->audio_format.GetFrameSize(); - num_samples = num_frames * encoder->audio_format.channels; - - switch (encoder->audio_format.format) { - case SampleFormat::S8: - exbuffer = encoder->expand_buffer.Get(length * 4); - pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data, - num_samples); - buffer = exbuffer; - break; - - case SampleFormat::S16: - exbuffer = encoder->expand_buffer.Get(length * 2); - pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data, - num_samples); - buffer = exbuffer; - break; - - case SampleFormat::S24_P32: - case SampleFormat::S32: - /* nothing need to be done; format is the same for - both mpd and libFLAC */ - buffer = data; - break; - - default: - gcc_unreachable(); - } - - /* feed samples to encoder */ - - if (!FLAC__stream_encoder_process_interleaved(encoder->fse, - (const FLAC__int32 *)buffer, - num_frames)) { - error.Set(flac_encoder_domain, "flac encoder process failed"); - return false; - } - - return true; -} - -static size_t -flac_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - struct flac_encoder *encoder = (struct flac_encoder *)_encoder; - - size_t max_length; - const char *src = (const char *) - fifo_buffer_read(encoder->output_buffer, &max_length); - if (src == nullptr) - return 0; - - if (length > max_length) - length = max_length; - - memcpy(dest, src, length); - fifo_buffer_consume(encoder->output_buffer, length); - return length; -} - -static const char * -flac_encoder_get_mime_type(gcc_unused Encoder *_encoder) -{ - return "audio/flac"; -} - -const EncoderPlugin flac_encoder_plugin = { - "flac", - flac_encoder_init, - flac_encoder_finish, - flac_encoder_open, - flac_encoder_close, - flac_encoder_flush, - flac_encoder_flush, - nullptr, - nullptr, - flac_encoder_write, - flac_encoder_read, - flac_encoder_get_mime_type, -}; - diff --git a/src/encoder/FlacEncoderPlugin.hxx b/src/encoder/FlacEncoderPlugin.hxx deleted file mode 100644 index 928a7f93e..000000000 --- a/src/encoder/FlacEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_FLAC_HXX -#define MPD_ENCODER_FLAC_HXX - -extern const struct EncoderPlugin flac_encoder_plugin; - -#endif diff --git a/src/encoder/LameEncoderPlugin.cxx b/src/encoder/LameEncoderPlugin.cxx deleted file mode 100644 index 06082d16b..000000000 --- a/src/encoder/LameEncoderPlugin.cxx +++ /dev/null @@ -1,293 +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 "LameEncoderPlugin.hxx" -#include "EncoderAPI.hxx" -#include "AudioFormat.hxx" -#include "ConfigError.hxx" -#include "util/NumberParser.hxx" -#include "util/ReusableArray.hxx" -#include "util/Manual.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <lame/lame.h> - -#include <assert.h> -#include <string.h> - -struct LameEncoder final { - Encoder encoder; - - AudioFormat audio_format; - float quality; - int bitrate; - - lame_global_flags *gfp; - - Manual<ReusableArray<unsigned char, 32768>> output_buffer; - unsigned char *output_begin, *output_end; - - LameEncoder():encoder(lame_encoder_plugin) {} - - bool Configure(const config_param ¶m, Error &error); -}; - -static constexpr Domain lame_encoder_domain("lame_encoder"); - -bool -LameEncoder::Configure(const config_param ¶m, Error &error) -{ - const char *value; - char *endptr; - - value = param.GetBlockValue("quality"); - if (value != nullptr) { - /* a quality was configured (VBR) */ - - quality = ParseDouble(value, &endptr); - - if (*endptr != '\0' || quality < -1.0 || quality > 10.0) { - error.Format(config_domain, - "quality \"%s\" is not a number in the " - "range -1 to 10", - value); - return false; - } - - if (param.GetBlockValue("bitrate") != nullptr) { - error.Set(config_domain, - "quality and bitrate are both defined"); - return false; - } - } else { - /* a bit rate was configured */ - - value = param.GetBlockValue("bitrate"); - if (value == nullptr) { - error.Set(config_domain, - "neither bitrate nor quality defined"); - return false; - } - - quality = -2.0; - bitrate = ParseInt(value, &endptr); - - if (*endptr != '\0' || bitrate <= 0) { - error.Set(config_domain, - "bitrate should be a positive integer"); - return false; - } - } - - return true; -} - -static Encoder * -lame_encoder_init(const config_param ¶m, Error &error) -{ - LameEncoder *encoder = new LameEncoder(); - - /* load configuration from "param" */ - if (!encoder->Configure(param, error)) { - /* configuration has failed, roll back and return error */ - delete encoder; - return nullptr; - } - - return &encoder->encoder; -} - -static void -lame_encoder_finish(Encoder *_encoder) -{ - LameEncoder *encoder = (LameEncoder *)_encoder; - - /* the real liblame cleanup was already performed by - lame_encoder_close(), so no real work here */ - delete encoder; -} - -static bool -lame_encoder_setup(LameEncoder *encoder, Error &error) -{ - if (encoder->quality >= -1.0) { - /* a quality was configured (VBR) */ - - if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) { - error.Set(lame_encoder_domain, - "error setting lame VBR mode"); - return false; - } - if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) { - error.Set(lame_encoder_domain, - "error setting lame VBR quality"); - return false; - } - } else { - /* a bit rate was configured */ - - if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) { - error.Set(lame_encoder_domain, - "error setting lame bitrate"); - return false; - } - } - - if (0 != lame_set_num_channels(encoder->gfp, - encoder->audio_format.channels)) { - error.Set(lame_encoder_domain, - "error setting lame num channels"); - return false; - } - - if (0 != lame_set_in_samplerate(encoder->gfp, - encoder->audio_format.sample_rate)) { - error.Set(lame_encoder_domain, - "error setting lame sample rate"); - return false; - } - - if (0 != lame_set_out_samplerate(encoder->gfp, - encoder->audio_format.sample_rate)) { - error.Set(lame_encoder_domain, - "error setting lame out sample rate"); - return false; - } - - if (0 > lame_init_params(encoder->gfp)) { - error.Set(lame_encoder_domain, - "error initializing lame params"); - return false; - } - - return true; -} - -static bool -lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) -{ - LameEncoder *encoder = (LameEncoder *)_encoder; - - audio_format.format = SampleFormat::S16; - audio_format.channels = 2; - - encoder->audio_format = audio_format; - - encoder->gfp = lame_init(); - if (encoder->gfp == nullptr) { - error.Set(lame_encoder_domain, "lame_init() failed"); - return false; - } - - if (!lame_encoder_setup(encoder, error)) { - lame_close(encoder->gfp); - return false; - } - - encoder->output_buffer.Construct(); - encoder->output_begin = encoder->output_end = nullptr; - - return true; -} - -static void -lame_encoder_close(Encoder *_encoder) -{ - LameEncoder *encoder = (LameEncoder *)_encoder; - - lame_close(encoder->gfp); - encoder->output_buffer.Destruct(); -} - -static bool -lame_encoder_write(Encoder *_encoder, - const void *data, size_t length, - gcc_unused Error &error) -{ - LameEncoder *encoder = (LameEncoder *)_encoder; - const int16_t *src = (const int16_t*)data; - - assert(encoder->output_begin == encoder->output_end); - - const unsigned num_frames = - length / encoder->audio_format.GetFrameSize(); - const unsigned num_samples = - length / encoder->audio_format.GetSampleSize(); - - /* worst-case formula according to LAME documentation */ - const size_t output_buffer_size = 5 * num_samples / 4 + 7200; - const auto output_buffer = encoder->output_buffer->Get(output_buffer_size); - - /* this is for only 16-bit audio */ - - int bytes_out = lame_encode_buffer_interleaved(encoder->gfp, - const_cast<short *>(src), - num_frames, - output_buffer, - output_buffer_size); - - if (bytes_out < 0) { - error.Set(lame_encoder_domain, "lame encoder failed"); - return false; - } - - encoder->output_begin = output_buffer; - encoder->output_end = output_buffer + bytes_out; - return true; -} - -static size_t -lame_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - LameEncoder *encoder = (LameEncoder *)_encoder; - - const auto begin = encoder->output_begin; - assert(begin <= encoder->output_end); - const size_t remainning = encoder->output_end - begin; - if (length > remainning) - length = remainning; - - memcpy(dest, begin, length); - - encoder->output_begin = begin + length; - return length; -} - -static const char * -lame_encoder_get_mime_type(gcc_unused Encoder *_encoder) -{ - return "audio/mpeg"; -} - -const EncoderPlugin lame_encoder_plugin = { - "lame", - lame_encoder_init, - lame_encoder_finish, - lame_encoder_open, - lame_encoder_close, - nullptr, - nullptr, - nullptr, - nullptr, - lame_encoder_write, - lame_encoder_read, - lame_encoder_get_mime_type, -}; diff --git a/src/encoder/LameEncoderPlugin.hxx b/src/encoder/LameEncoderPlugin.hxx deleted file mode 100644 index 49832baee..000000000 --- a/src/encoder/LameEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_LAME_HXX -#define MPD_ENCODER_LAME_HXX - -extern const struct EncoderPlugin lame_encoder_plugin; - -#endif diff --git a/src/encoder/NullEncoderPlugin.cxx b/src/encoder/NullEncoderPlugin.cxx deleted file mode 100644 index 3b1aae5e2..000000000 --- a/src/encoder/NullEncoderPlugin.cxx +++ /dev/null @@ -1,117 +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 "NullEncoderPlugin.hxx" -#include "EncoderAPI.hxx" -#include "util/fifo_buffer.h" -extern "C" { -#include "util/growing_fifo.h" -} -#include "Compiler.h" - -#include <assert.h> -#include <string.h> - -struct NullEncoder final { - Encoder encoder; - - struct fifo_buffer *buffer; - - NullEncoder():encoder(null_encoder_plugin) {} -}; - -static Encoder * -null_encoder_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - NullEncoder *encoder = new NullEncoder(); - return &encoder->encoder; -} - -static void -null_encoder_finish(Encoder *_encoder) -{ - NullEncoder *encoder = (NullEncoder *)_encoder; - - delete encoder; -} - -static void -null_encoder_close(Encoder *_encoder) -{ - NullEncoder *encoder = (NullEncoder *)_encoder; - - fifo_buffer_free(encoder->buffer); -} - - -static bool -null_encoder_open(Encoder *_encoder, - gcc_unused AudioFormat &audio_format, - gcc_unused Error &error) -{ - NullEncoder *encoder = (NullEncoder *)_encoder; - encoder->buffer = growing_fifo_new(); - return true; -} - -static bool -null_encoder_write(Encoder *_encoder, - const void *data, size_t length, - gcc_unused Error &error) -{ - NullEncoder *encoder = (NullEncoder *)_encoder; - - growing_fifo_append(&encoder->buffer, data, length); - return length; -} - -static size_t -null_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - NullEncoder *encoder = (NullEncoder *)_encoder; - - size_t max_length; - const void *src = fifo_buffer_read(encoder->buffer, &max_length); - if (src == nullptr) - return 0; - - if (length > max_length) - length = max_length; - - memcpy(dest, src, length); - fifo_buffer_consume(encoder->buffer, length); - return length; -} - -const EncoderPlugin null_encoder_plugin = { - "null", - null_encoder_init, - null_encoder_finish, - null_encoder_open, - null_encoder_close, - nullptr, - nullptr, - nullptr, - nullptr, - null_encoder_write, - null_encoder_read, - nullptr, -}; diff --git a/src/encoder/NullEncoderPlugin.hxx b/src/encoder/NullEncoderPlugin.hxx deleted file mode 100644 index b741a2f6d..000000000 --- a/src/encoder/NullEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_NULL_HXX -#define MPD_ENCODER_NULL_HXX - -extern const struct EncoderPlugin null_encoder_plugin; - -#endif diff --git a/src/encoder/OggSerial.cxx b/src/encoder/OggSerial.cxx deleted file mode 100644 index 0d4fc5a9f..000000000 --- a/src/encoder/OggSerial.cxx +++ /dev/null @@ -1,43 +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 "OggSerial.hxx" -#include "system/Clock.hxx" -#include "Compiler.h" - -#include <atomic> - -static std::atomic_uint next_ogg_serial; - -int -GenerateOggSerial() -{ - unsigned serial = ++next_ogg_serial; - if (gcc_unlikely(serial < 16)) { - /* first-time initialization: seed with a clock value, - which is random enough for our use */ - - /* this code is not race-free, but good enough */ - const unsigned seed = MonotonicClockMS(); - next_ogg_serial = serial = seed; - } - - return serial; -} - diff --git a/src/encoder/OggSerial.hxx b/src/encoder/OggSerial.hxx deleted file mode 100644 index 2e5a020c6..000000000 --- a/src/encoder/OggSerial.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OGG_SERIAL_HXX -#define MPD_OGG_SERIAL_HXX - -/** - * Generate the next pseudo-random Ogg serial. - */ -int -GenerateOggSerial(); - -#endif diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx deleted file mode 100644 index e8dcdf970..000000000 --- a/src/encoder/OggStream.hxx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OGG_STREAM_HXX -#define MPD_OGG_STREAM_HXX - -#include "check.h" - -#include <ogg/ogg.h> - -#include <assert.h> -#include <string.h> -#include <stdint.h> - -class OggStream { - ogg_stream_state state; - - bool flush; - -#ifndef NDEBUG - bool initialized; -#endif - -public: -#ifndef NDEBUG - OggStream():initialized(false) {} - ~OggStream() { - assert(!initialized); - } -#endif - - void Initialize(int serialno) { - assert(!initialized); - - ogg_stream_init(&state, serialno); - - /* set "flush" to true, so the caller gets the full - headers on the first read() */ - flush = true; - -#ifndef NDEBUG - initialized = true; -#endif - } - - void Reinitialize(int serialno) { - assert(initialized); - - ogg_stream_reset_serialno(&state, serialno); - - /* set "flush" to true, so the caller gets the full - headers on the first read() */ - flush = true; - } - - void Deinitialize() { - assert(initialized); - - ogg_stream_clear(&state); - -#ifndef NDEBUG - initialized = false; -#endif - } - - void Flush() { - assert(initialized); - - flush = true; - } - - void PacketIn(const ogg_packet &packet) { - assert(initialized); - - ogg_stream_packetin(&state, - const_cast<ogg_packet *>(&packet)); - } - - bool PageOut(ogg_page &page) { - int result = ogg_stream_pageout(&state, &page); - if (result == 0 && flush) { - flush = false; - result = ogg_stream_flush(&state, &page); - } - - return result != 0; - } - - size_t PageOut(void *_buffer, size_t size) { - ogg_page page; - if (!PageOut(page)) - return 0; - - assert(page.header_len > 0 || page.body_len > 0); - - size_t header_len = (size_t)page.header_len; - size_t body_len = (size_t)page.body_len; - assert(header_len <= size); - - if (header_len + body_len > size) - /* TODO: better overflow handling */ - body_len = size - header_len; - - uint8_t *buffer = (uint8_t *)_buffer; - memcpy(buffer, page.header, header_len); - memcpy(buffer + header_len, page.body, body_len); - - return header_len + body_len; - } -}; - -#endif diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx deleted file mode 100644 index 243dc0836..000000000 --- a/src/encoder/OpusEncoderPlugin.cxx +++ /dev/null @@ -1,420 +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 "OpusEncoderPlugin.hxx" -#include "OggStream.hxx" -#include "OggSerial.hxx" -#include "EncoderAPI.hxx" -#include "AudioFormat.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.hxx" - -#include <opus.h> -#include <ogg/ogg.h> - -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> - -struct opus_encoder { - /** the base class */ - Encoder encoder; - - /* configuration */ - - opus_int32 bitrate; - int complexity; - int signal; - - /* runtime information */ - - AudioFormat audio_format; - - size_t frame_size; - - size_t buffer_frames, buffer_size, buffer_position; - uint8_t *buffer; - - OpusEncoder *enc; - - unsigned char buffer2[1275 * 3 + 7]; - - OggStream stream; - - int lookahead; - - ogg_int64_t packetno; - - ogg_int64_t granulepos; - - opus_encoder():encoder(opus_encoder_plugin) {} -}; - -static constexpr Domain opus_encoder_domain("opus_encoder"); - -static bool -opus_encoder_configure(struct opus_encoder *encoder, - const config_param ¶m, Error &error) -{ - const char *value = param.GetBlockValue("bitrate", "auto"); - if (strcmp(value, "auto") == 0) - encoder->bitrate = OPUS_AUTO; - else if (strcmp(value, "max") == 0) - encoder->bitrate = OPUS_BITRATE_MAX; - else { - char *endptr; - encoder->bitrate = strtoul(value, &endptr, 10); - if (endptr == value || *endptr != 0 || - encoder->bitrate < 500 || encoder->bitrate > 512000) { - error.Set(config_domain, "Invalid bit rate"); - return false; - } - } - - encoder->complexity = param.GetBlockValue("complexity", 10u); - if (encoder->complexity > 10) { - error.Format(config_domain, "Invalid complexity"); - return false; - } - - value = param.GetBlockValue("signal", "auto"); - if (strcmp(value, "auto") == 0) - encoder->signal = OPUS_AUTO; - else if (strcmp(value, "voice") == 0) - encoder->signal = OPUS_SIGNAL_VOICE; - else if (strcmp(value, "music") == 0) - encoder->signal = OPUS_SIGNAL_MUSIC; - else { - error.Format(config_domain, "Invalid signal"); - return false; - } - - return true; -} - -static Encoder * -opus_encoder_init(const config_param ¶m, Error &error) -{ - opus_encoder *encoder = new opus_encoder(); - - /* load configuration from "param" */ - if (!opus_encoder_configure(encoder, param, error)) { - /* configuration has failed, roll back and return error */ - delete encoder; - return NULL; - } - - return &encoder->encoder; -} - -static void -opus_encoder_finish(Encoder *_encoder) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - - /* the real libopus cleanup was already performed by - opus_encoder_close(), so no real work here */ - delete encoder; -} - -static bool -opus_encoder_open(Encoder *_encoder, - AudioFormat &audio_format, - Error &error) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - - /* libopus supports only 48 kHz */ - audio_format.sample_rate = 48000; - - if (audio_format.channels > 2) - audio_format.channels = 1; - - switch (audio_format.format) { - case SampleFormat::S16: - case SampleFormat::FLOAT: - break; - - case SampleFormat::S8: - audio_format.format = SampleFormat::S16; - break; - - default: - audio_format.format = SampleFormat::FLOAT; - break; - } - - encoder->audio_format = audio_format; - encoder->frame_size = audio_format.GetFrameSize(); - - int error_code; - encoder->enc = opus_encoder_create(audio_format.sample_rate, - audio_format.channels, - OPUS_APPLICATION_AUDIO, - &error_code); - if (encoder->enc == nullptr) { - error.Set(opus_encoder_domain, error_code, - opus_strerror(error_code)); - return false; - } - - opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate)); - opus_encoder_ctl(encoder->enc, - OPUS_SET_COMPLEXITY(encoder->complexity)); - opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal)); - - opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead)); - - encoder->buffer_frames = audio_format.sample_rate / 50; - encoder->buffer_size = encoder->frame_size * encoder->buffer_frames; - encoder->buffer_position = 0; - encoder->buffer = (unsigned char *)g_malloc(encoder->buffer_size); - - encoder->stream.Initialize(GenerateOggSerial()); - encoder->packetno = 0; - - return true; -} - -static void -opus_encoder_close(Encoder *_encoder) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - - encoder->stream.Deinitialize(); - g_free(encoder->buffer); - opus_encoder_destroy(encoder->enc); -} - -static bool -opus_encoder_do_encode(struct opus_encoder *encoder, bool eos, - Error &error) -{ - assert(encoder->buffer_position == encoder->buffer_size); - - opus_int32 result = - encoder->audio_format.format == SampleFormat::S16 - ? opus_encode(encoder->enc, - (const opus_int16 *)encoder->buffer, - encoder->buffer_frames, - encoder->buffer2, - sizeof(encoder->buffer2)) - : opus_encode_float(encoder->enc, - (const float *)encoder->buffer, - encoder->buffer_frames, - encoder->buffer2, - sizeof(encoder->buffer2)); - if (result < 0) { - error.Set(opus_encoder_domain, "Opus encoder error"); - return false; - } - - encoder->granulepos += encoder->buffer_frames; - - ogg_packet packet; - packet.packet = encoder->buffer2; - packet.bytes = result; - packet.b_o_s = false; - packet.e_o_s = eos; - packet.granulepos = encoder->granulepos; - packet.packetno = encoder->packetno++; - encoder->stream.PacketIn(packet); - - encoder->buffer_position = 0; - - return true; -} - -static bool -opus_encoder_end(Encoder *_encoder, Error &error) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - - encoder->stream.Flush(); - - memset(encoder->buffer + encoder->buffer_position, 0, - encoder->buffer_size - encoder->buffer_position); - encoder->buffer_position = encoder->buffer_size; - - return opus_encoder_do_encode(encoder, true, error); -} - -static bool -opus_encoder_flush(Encoder *_encoder, gcc_unused Error &error) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - - encoder->stream.Flush(); - return true; -} - -static bool -opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames, - Error &error) -{ - size_t fill_bytes = fill_frames * encoder->frame_size; - - while (fill_bytes > 0) { - size_t nbytes = - encoder->buffer_size - encoder->buffer_position; - if (nbytes > fill_bytes) - nbytes = fill_bytes; - - memset(encoder->buffer + encoder->buffer_position, - 0, nbytes); - encoder->buffer_position += nbytes; - fill_bytes -= nbytes; - - if (encoder->buffer_position == encoder->buffer_size && - !opus_encoder_do_encode(encoder, false, error)) - return false; - } - - return true; -} - -static bool -opus_encoder_write(Encoder *_encoder, - const void *_data, size_t length, - Error &error) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - const uint8_t *data = (const uint8_t *)_data; - - if (encoder->lookahead > 0) { - /* generate some silence at the beginning of the - stream */ - - assert(encoder->buffer_position == 0); - - if (!opus_encoder_write_silence(encoder, encoder->lookahead, - error)) - return false; - - encoder->lookahead = 0; - } - - while (length > 0) { - size_t nbytes = - encoder->buffer_size - encoder->buffer_position; - if (nbytes > length) - nbytes = length; - - memcpy(encoder->buffer + encoder->buffer_position, - data, nbytes); - data += nbytes; - length -= nbytes; - encoder->buffer_position += nbytes; - - if (encoder->buffer_position == encoder->buffer_size && - !opus_encoder_do_encode(encoder, false, error)) - return false; - } - - return true; -} - -static void -opus_encoder_generate_head(struct opus_encoder *encoder) -{ - unsigned char header[19]; - memcpy(header, "OpusHead", 8); - header[8] = 1; - header[9] = encoder->audio_format.channels; - *(uint16_t *)(header + 10) = ToLE16(encoder->lookahead); - *(uint32_t *)(header + 12) = - ToLE32(encoder->audio_format.sample_rate); - header[16] = 0; - header[17] = 0; - header[18] = 0; - - ogg_packet packet; - packet.packet = header; - packet.bytes = 19; - packet.b_o_s = true; - packet.e_o_s = false; - packet.granulepos = 0; - packet.packetno = encoder->packetno++; - encoder->stream.PacketIn(packet); - encoder->stream.Flush(); -} - -static void -opus_encoder_generate_tags(struct opus_encoder *encoder) -{ - const char *version = opus_get_version_string(); - size_t version_length = strlen(version); - - size_t comments_size = 8 + 4 + version_length + 4; - unsigned char *comments = (unsigned char *)g_malloc(comments_size); - memcpy(comments, "OpusTags", 8); - *(uint32_t *)(comments + 8) = ToLE32(version_length); - memcpy(comments + 12, version, version_length); - *(uint32_t *)(comments + 12 + version_length) = ToLE32(0); - - ogg_packet packet; - packet.packet = comments; - packet.bytes = comments_size; - packet.b_o_s = false; - packet.e_o_s = false; - packet.granulepos = 0; - packet.packetno = encoder->packetno++; - encoder->stream.PacketIn(packet); - encoder->stream.Flush(); - - g_free(comments); -} - -static size_t -opus_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - struct opus_encoder *encoder = (struct opus_encoder *)_encoder; - - if (encoder->packetno == 0) - opus_encoder_generate_head(encoder); - else if (encoder->packetno == 1) - opus_encoder_generate_tags(encoder); - - return encoder->stream.PageOut(dest, length); -} - -static const char * -opus_encoder_get_mime_type(gcc_unused Encoder *_encoder) -{ - return "audio/ogg"; -} - -const EncoderPlugin opus_encoder_plugin = { - "opus", - opus_encoder_init, - opus_encoder_finish, - opus_encoder_open, - opus_encoder_close, - opus_encoder_end, - opus_encoder_flush, - nullptr, - nullptr, - opus_encoder_write, - opus_encoder_read, - opus_encoder_get_mime_type, -}; diff --git a/src/encoder/OpusEncoderPlugin.hxx b/src/encoder/OpusEncoderPlugin.hxx deleted file mode 100644 index 3bb55e051..000000000 --- a/src/encoder/OpusEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_OPUS_H -#define MPD_ENCODER_OPUS_H - -extern const struct EncoderPlugin opus_encoder_plugin; - -#endif diff --git a/src/encoder/TwolameEncoderPlugin.cxx b/src/encoder/TwolameEncoderPlugin.cxx deleted file mode 100644 index 543e71d64..000000000 --- a/src/encoder/TwolameEncoderPlugin.cxx +++ /dev/null @@ -1,314 +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 "TwolameEncoderPlugin.hxx" -#include "EncoderAPI.hxx" -#include "AudioFormat.hxx" -#include "ConfigError.hxx" -#include "util/NumberParser.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <twolame.h> - -#include <assert.h> -#include <string.h> - -struct TwolameEncoder final { - Encoder encoder; - - AudioFormat audio_format; - float quality; - int bitrate; - - twolame_options *options; - - unsigned char output_buffer[32768]; - size_t output_buffer_length; - size_t output_buffer_position; - - /** - * Call libtwolame's flush function when the output_buffer is - * empty? - */ - bool flush; - - TwolameEncoder():encoder(twolame_encoder_plugin) {} - - bool Configure(const config_param ¶m, Error &error); -}; - -static constexpr Domain twolame_encoder_domain("twolame_encoder"); - -bool -TwolameEncoder::Configure(const config_param ¶m, Error &error) -{ - const char *value; - char *endptr; - - value = param.GetBlockValue("quality"); - if (value != nullptr) { - /* a quality was configured (VBR) */ - - quality = ParseDouble(value, &endptr); - - if (*endptr != '\0' || quality < -1.0 || quality > 10.0) { - error.Format(config_domain, - "quality \"%s\" is not a number in the " - "range -1 to 10", - value); - return false; - } - - if (param.GetBlockValue("bitrate") != nullptr) { - error.Set(config_domain, - "quality and bitrate are both defined"); - return false; - } - } else { - /* a bit rate was configured */ - - value = param.GetBlockValue("bitrate"); - if (value == nullptr) { - error.Set(config_domain, - "neither bitrate nor quality defined"); - return false; - } - - quality = -2.0; - bitrate = ParseInt(value, &endptr); - - if (*endptr != '\0' || bitrate <= 0) { - error.Set(config_domain, - "bitrate should be a positive integer"); - return false; - } - } - - return true; -} - -static Encoder * -twolame_encoder_init(const config_param ¶m, Error &error_r) -{ - FormatDebug(twolame_encoder_domain, - "libtwolame version %s", get_twolame_version()); - - TwolameEncoder *encoder = new TwolameEncoder(); - - /* load configuration from "param" */ - if (!encoder->Configure(param, error_r)) { - /* configuration has failed, roll back and return error */ - delete encoder; - return nullptr; - } - - return &encoder->encoder; -} - -static void -twolame_encoder_finish(Encoder *_encoder) -{ - TwolameEncoder *encoder = (TwolameEncoder *)_encoder; - - /* the real libtwolame cleanup was already performed by - twolame_encoder_close(), so no real work here */ - delete encoder; -} - -static bool -twolame_encoder_setup(TwolameEncoder *encoder, Error &error) -{ - if (encoder->quality >= -1.0) { - /* a quality was configured (VBR) */ - - if (0 != twolame_set_VBR(encoder->options, true)) { - error.Set(twolame_encoder_domain, - "error setting twolame VBR mode"); - return false; - } - if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) { - error.Set(twolame_encoder_domain, - "error setting twolame VBR quality"); - return false; - } - } else { - /* a bit rate was configured */ - - if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) { - error.Set(twolame_encoder_domain, - "error setting twolame bitrate"); - return false; - } - } - - if (0 != twolame_set_num_channels(encoder->options, - encoder->audio_format.channels)) { - error.Set(twolame_encoder_domain, - "error setting twolame num channels"); - return false; - } - - if (0 != twolame_set_in_samplerate(encoder->options, - encoder->audio_format.sample_rate)) { - error.Set(twolame_encoder_domain, - "error setting twolame sample rate"); - return false; - } - - if (0 > twolame_init_params(encoder->options)) { - error.Set(twolame_encoder_domain, - "error initializing twolame params"); - return false; - } - - return true; -} - -static bool -twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, - Error &error) -{ - TwolameEncoder *encoder = (TwolameEncoder *)_encoder; - - audio_format.format = SampleFormat::S16; - audio_format.channels = 2; - - encoder->audio_format = audio_format; - - encoder->options = twolame_init(); - if (encoder->options == nullptr) { - error.Set(twolame_encoder_domain, "twolame_init() failed"); - return false; - } - - if (!twolame_encoder_setup(encoder, error)) { - twolame_close(&encoder->options); - return false; - } - - encoder->output_buffer_length = 0; - encoder->output_buffer_position = 0; - encoder->flush = false; - - return true; -} - -static void -twolame_encoder_close(Encoder *_encoder) -{ - TwolameEncoder *encoder = (TwolameEncoder *)_encoder; - - twolame_close(&encoder->options); -} - -static bool -twolame_encoder_flush(Encoder *_encoder, gcc_unused Error &error) -{ - TwolameEncoder *encoder = (TwolameEncoder *)_encoder; - - encoder->flush = true; - return true; -} - -static bool -twolame_encoder_write(Encoder *_encoder, - const void *data, size_t length, - gcc_unused Error &error) -{ - TwolameEncoder *encoder = (TwolameEncoder *)_encoder; - const int16_t *src = (const int16_t*)data; - - assert(encoder->output_buffer_position == - encoder->output_buffer_length); - - const unsigned num_frames = - length / encoder->audio_format.GetFrameSize(); - - int bytes_out = twolame_encode_buffer_interleaved(encoder->options, - src, num_frames, - encoder->output_buffer, - sizeof(encoder->output_buffer)); - if (bytes_out < 0) { - error.Set(twolame_encoder_domain, "twolame encoder failed"); - return false; - } - - encoder->output_buffer_length = (size_t)bytes_out; - encoder->output_buffer_position = 0; - return true; -} - -static size_t -twolame_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - TwolameEncoder *encoder = (TwolameEncoder *)_encoder; - - assert(encoder->output_buffer_position <= - encoder->output_buffer_length); - - if (encoder->output_buffer_position == encoder->output_buffer_length && - encoder->flush) { - int ret = twolame_encode_flush(encoder->options, - encoder->output_buffer, - sizeof(encoder->output_buffer)); - if (ret > 0) { - encoder->output_buffer_length = (size_t)ret; - encoder->output_buffer_position = 0; - } - - encoder->flush = false; - } - - - const size_t remainning = encoder->output_buffer_length - - encoder->output_buffer_position; - if (length > remainning) - length = remainning; - - memcpy(dest, encoder->output_buffer + encoder->output_buffer_position, - length); - - encoder->output_buffer_position += length; - - return length; -} - -static const char * -twolame_encoder_get_mime_type(gcc_unused Encoder *_encoder) -{ - return "audio/mpeg"; -} - -const EncoderPlugin twolame_encoder_plugin = { - "twolame", - twolame_encoder_init, - twolame_encoder_finish, - twolame_encoder_open, - twolame_encoder_close, - twolame_encoder_flush, - twolame_encoder_flush, - nullptr, - nullptr, - twolame_encoder_write, - twolame_encoder_read, - twolame_encoder_get_mime_type, -}; diff --git a/src/encoder/TwolameEncoderPlugin.hxx b/src/encoder/TwolameEncoderPlugin.hxx deleted file mode 100644 index dd8a536f6..000000000 --- a/src/encoder/TwolameEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_TWOLAME_HXX -#define MPD_ENCODER_TWOLAME_HXX - -extern const struct EncoderPlugin twolame_encoder_plugin; - -#endif diff --git a/src/encoder/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx deleted file mode 100644 index 5b40aaea1..000000000 --- a/src/encoder/VorbisEncoderPlugin.cxx +++ /dev/null @@ -1,367 +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 "VorbisEncoderPlugin.hxx" -#include "OggStream.hxx" -#include "OggSerial.hxx" -#include "EncoderAPI.hxx" -#include "tag/Tag.hxx" -#include "AudioFormat.hxx" -#include "ConfigError.hxx" -#include "util/NumberParser.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <vorbis/vorbisenc.h> - -#include <glib.h> - -#include <assert.h> - -struct vorbis_encoder { - /** the base class */ - Encoder encoder; - - /* configuration */ - - float quality; - int bitrate; - - /* runtime information */ - - AudioFormat audio_format; - - vorbis_dsp_state vd; - vorbis_block vb; - vorbis_info vi; - - OggStream stream; - - vorbis_encoder():encoder(vorbis_encoder_plugin) {} -}; - -static constexpr Domain vorbis_encoder_domain("vorbis_encoder"); - -static bool -vorbis_encoder_configure(struct vorbis_encoder *encoder, - const config_param ¶m, Error &error) -{ - const char *value = param.GetBlockValue("quality"); - if (value != nullptr) { - /* a quality was configured (VBR) */ - - char *endptr; - encoder->quality = ParseDouble(value, &endptr); - - if (*endptr != '\0' || encoder->quality < -1.0 || - encoder->quality > 10.0) { - error.Format(config_domain, - "quality \"%s\" is not a number in the " - "range -1 to 10", - value); - return false; - } - - if (param.GetBlockValue("bitrate") != nullptr) { - error.Set(config_domain, - "quality and bitrate are both defined"); - return false; - } - } else { - /* a bit rate was configured */ - - value = param.GetBlockValue("bitrate"); - if (value == nullptr) { - error.Set(config_domain, - "neither bitrate nor quality defined"); - return false; - } - - encoder->quality = -2.0; - - char *endptr; - encoder->bitrate = ParseInt(value, &endptr); - if (*endptr != '\0' || encoder->bitrate <= 0) { - error.Set(config_domain, - "bitrate should be a positive integer"); - return false; - } - } - - return true; -} - -static Encoder * -vorbis_encoder_init(const config_param ¶m, Error &error) -{ - vorbis_encoder *encoder = new vorbis_encoder(); - - /* load configuration from "param" */ - if (!vorbis_encoder_configure(encoder, param, error)) { - /* configuration has failed, roll back and return error */ - delete encoder; - return nullptr; - } - - return &encoder->encoder; -} - -static void -vorbis_encoder_finish(Encoder *_encoder) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - /* the real libvorbis/libogg cleanup was already performed by - vorbis_encoder_close(), so no real work here */ - delete encoder; -} - -static bool -vorbis_encoder_reinit(struct vorbis_encoder *encoder, Error &error) -{ - vorbis_info_init(&encoder->vi); - - if (encoder->quality >= -1.0) { - /* a quality was configured (VBR) */ - - if (0 != vorbis_encode_init_vbr(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, - encoder->quality * 0.1)) { - error.Set(vorbis_encoder_domain, - "error initializing vorbis vbr"); - vorbis_info_clear(&encoder->vi); - return false; - } - } else { - /* a bit rate was configured */ - - if (0 != vorbis_encode_init(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, -1.0, - encoder->bitrate * 1000, -1.0)) { - error.Set(vorbis_encoder_domain, - "error initializing vorbis encoder"); - vorbis_info_clear(&encoder->vi); - return false; - } - } - - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - encoder->stream.Initialize(GenerateOggSerial()); - - return true; -} - -static void -vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) -{ - ogg_packet packet, comments, codebooks; - - vorbis_analysis_headerout(&encoder->vd, vc, - &packet, &comments, &codebooks); - - encoder->stream.PacketIn(packet); - encoder->stream.PacketIn(comments); - encoder->stream.PacketIn(codebooks); -} - -static void -vorbis_encoder_send_header(struct vorbis_encoder *encoder) -{ - vorbis_comment vc; - - vorbis_comment_init(&vc); - vorbis_encoder_headerout(encoder, &vc); - vorbis_comment_clear(&vc); -} - -static bool -vorbis_encoder_open(Encoder *_encoder, - AudioFormat &audio_format, - Error &error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - audio_format.format = SampleFormat::FLOAT; - - encoder->audio_format = audio_format; - - if (!vorbis_encoder_reinit(encoder, error)) - return false; - - vorbis_encoder_send_header(encoder); - - return true; -} - -static void -vorbis_encoder_clear(struct vorbis_encoder *encoder) -{ - encoder->stream.Deinitialize(); - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_info_clear(&encoder->vi); -} - -static void -vorbis_encoder_close(Encoder *_encoder) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - vorbis_encoder_clear(encoder); -} - -static void -vorbis_encoder_blockout(struct vorbis_encoder *encoder) -{ - while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, nullptr); - vorbis_bitrate_addblock(&encoder->vb); - - ogg_packet packet; - while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - encoder->stream.PacketIn(packet); - } -} - -static bool -vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - encoder->stream.Flush(); - return true; -} - -static bool -vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - vorbis_analysis_wrote(&encoder->vd, 0); - vorbis_encoder_blockout(encoder); - - /* reinitialize vorbis_dsp_state and vorbis_block to reset the - end-of-stream marker */ - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - - encoder->stream.Flush(); - return true; -} - -static void -copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag *tag) -{ - for (unsigned i = 0; i < tag->num_items; i++) { - const TagItem &item = *tag->items[i]; - char *name = g_ascii_strup(tag_item_names[item.type], -1); - vorbis_comment_add_tag(vc, name, item.value); - g_free(name); - } -} - -static bool -vorbis_encoder_tag(Encoder *_encoder, const Tag *tag, - gcc_unused Error &error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - vorbis_comment comment; - - /* write the vorbis_comment object */ - - vorbis_comment_init(&comment); - copy_tag_to_vorbis_comment(&comment, tag); - - /* reset ogg_stream_state and begin a new stream */ - - encoder->stream.Reinitialize(GenerateOggSerial()); - - /* send that vorbis_comment to the ogg_stream_state */ - - vorbis_encoder_headerout(encoder, &comment); - vorbis_comment_clear(&comment); - - return true; -} - -static void -interleaved_to_vorbis_buffer(float **dest, const float *src, - unsigned num_frames, unsigned num_channels) -{ - for (unsigned i = 0; i < num_frames; i++) - for (unsigned j = 0; j < num_channels; j++) - dest[j][i] = *src++; -} - -static bool -vorbis_encoder_write(Encoder *_encoder, - const void *data, size_t length, - gcc_unused Error &error) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - unsigned num_frames = length / encoder->audio_format.GetFrameSize(); - - /* this is for only 16-bit audio */ - - interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, - num_frames), - (const float *)data, - num_frames, - encoder->audio_format.channels); - - vorbis_analysis_wrote(&encoder->vd, num_frames); - vorbis_encoder_blockout(encoder); - return true; -} - -static size_t -vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; - - return encoder->stream.PageOut(dest, length); -} - -static const char * -vorbis_encoder_get_mime_type(gcc_unused Encoder *_encoder) -{ - return "audio/ogg"; -} - -const EncoderPlugin vorbis_encoder_plugin = { - "vorbis", - vorbis_encoder_init, - vorbis_encoder_finish, - vorbis_encoder_open, - vorbis_encoder_close, - vorbis_encoder_pre_tag, - vorbis_encoder_flush, - vorbis_encoder_pre_tag, - vorbis_encoder_tag, - vorbis_encoder_write, - vorbis_encoder_read, - vorbis_encoder_get_mime_type, -}; diff --git a/src/encoder/VorbisEncoderPlugin.hxx b/src/encoder/VorbisEncoderPlugin.hxx deleted file mode 100644 index d5d6125d2..000000000 --- a/src/encoder/VorbisEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_VORBIS_H -#define MPD_ENCODER_VORBIS_H - -extern const struct EncoderPlugin vorbis_encoder_plugin; - -#endif diff --git a/src/encoder/WaveEncoderPlugin.cxx b/src/encoder/WaveEncoderPlugin.cxx deleted file mode 100644 index acae0be9e..000000000 --- a/src/encoder/WaveEncoderPlugin.cxx +++ /dev/null @@ -1,274 +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 "WaveEncoderPlugin.hxx" -#include "EncoderAPI.hxx" -#include "system/ByteOrder.hxx" -#include "util/fifo_buffer.h" -extern "C" { -#include "util/growing_fifo.h" -} - -#include <assert.h> -#include <string.h> - -struct WaveEncoder { - Encoder encoder; - unsigned bits; - - struct fifo_buffer *buffer; - - WaveEncoder():encoder(wave_encoder_plugin) {} -}; - -struct wave_header { - uint32_t id_riff; - uint32_t riff_size; - uint32_t id_wave; - uint32_t id_fmt; - uint32_t fmt_size; - uint16_t format; - uint16_t channels; - uint32_t freq; - uint32_t byterate; - uint16_t blocksize; - uint16_t bits; - uint32_t id_data; - uint32_t data_size; -}; - -static void -fill_wave_header(struct wave_header *header, int channels, int bits, - int freq, int block_size) -{ - int data_size = 0x0FFFFFFF; - - /* constants */ - header->id_riff = ToLE32(0x46464952); - header->id_wave = ToLE32(0x45564157); - header->id_fmt = ToLE32(0x20746d66); - header->id_data = ToLE32(0x61746164); - - /* wave format */ - header->format = ToLE16(1); // PCM_FORMAT - header->channels = ToLE16(channels); - header->bits = ToLE16(bits); - header->freq = ToLE32(freq); - header->blocksize = ToLE16(block_size); - header->byterate = ToLE32(freq * block_size); - - /* chunk sizes (fake data length) */ - header->fmt_size = ToLE32(16); - header->data_size = ToLE32(data_size); - header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size)); -} - -static Encoder * -wave_encoder_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - WaveEncoder *encoder = new WaveEncoder(); - return &encoder->encoder; -} - -static void -wave_encoder_finish(Encoder *_encoder) -{ - WaveEncoder *encoder = (WaveEncoder *)_encoder; - - delete encoder; -} - -static bool -wave_encoder_open(Encoder *_encoder, - AudioFormat &audio_format, - gcc_unused Error &error) -{ - WaveEncoder *encoder = (WaveEncoder *)_encoder; - - assert(audio_format.IsValid()); - - switch (audio_format.format) { - case SampleFormat::S8: - encoder->bits = 8; - break; - - case SampleFormat::S16: - encoder->bits = 16; - break; - - case SampleFormat::S24_P32: - encoder->bits = 24; - break; - - case SampleFormat::S32: - encoder->bits = 32; - break; - - default: - audio_format.format = SampleFormat::S16; - encoder->bits = 16; - break; - } - - encoder->buffer = growing_fifo_new(); - wave_header *header = (wave_header *) - growing_fifo_write(&encoder->buffer, sizeof(*header)); - - /* create PCM wave header in initial buffer */ - fill_wave_header(header, - audio_format.channels, - encoder->bits, - audio_format.sample_rate, - (encoder->bits / 8) * audio_format.channels); - fifo_buffer_append(encoder->buffer, sizeof(*header)); - - return true; -} - -static void -wave_encoder_close(Encoder *_encoder) -{ - WaveEncoder *encoder = (WaveEncoder *)_encoder; - - fifo_buffer_free(encoder->buffer); -} - -static size_t -pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) -{ - size_t cnt = length >> 1; - while (cnt > 0) { - *dst16++ = ToLE16(*src16++); - cnt--; - } - return length; -} - -static size_t -pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) -{ - size_t cnt = length >> 2; - while (cnt > 0){ - *dst32++ = ToLE32(*src32++); - cnt--; - } - return length; -} - -static size_t -pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) -{ - uint32_t value; - uint8_t *dst_old = dst8; - - length = length >> 2; - while (length > 0){ - value = *src32++; - *dst8++ = (value) & 0xFF; - *dst8++ = (value >> 8) & 0xFF; - *dst8++ = (value >> 16) & 0xFF; - length--; - } - //correct buffer length - return (dst8 - dst_old); -} - -static bool -wave_encoder_write(Encoder *_encoder, - const void *src, size_t length, - gcc_unused Error &error) -{ - WaveEncoder *encoder = (WaveEncoder *)_encoder; - - uint8_t *dst = (uint8_t *)growing_fifo_write(&encoder->buffer, length); - - if (IsLittleEndian()) { - switch (encoder->bits) { - case 8: - case 16: - case 32:// optimized cases - memcpy(dst, src, length); - break; - case 24: - length = pcm24_to_wave(dst, (const uint32_t *)src, length); - break; - } - } else { - switch (encoder->bits) { - case 8: - memcpy(dst, src, length); - break; - case 16: - length = pcm16_to_wave((uint16_t *)dst, - (const uint16_t *)src, length); - break; - case 24: - length = pcm24_to_wave(dst, (const uint32_t *)src, length); - break; - case 32: - length = pcm32_to_wave((uint32_t *)dst, - (const uint32_t *)src, length); - break; - } - } - - fifo_buffer_append(encoder->buffer, length); - return true; -} - -static size_t -wave_encoder_read(Encoder *_encoder, void *dest, size_t length) -{ - WaveEncoder *encoder = (WaveEncoder *)_encoder; - - size_t max_length; - const void *src = fifo_buffer_read(encoder->buffer, &max_length); - if (src == NULL) - return 0; - - if (length > max_length) - length = max_length; - - memcpy(dest, src, length); - fifo_buffer_consume(encoder->buffer, length); - return length; -} - -static const char * -wave_encoder_get_mime_type(gcc_unused Encoder *_encoder) -{ - return "audio/wav"; -} - -const EncoderPlugin wave_encoder_plugin = { - "wave", - wave_encoder_init, - wave_encoder_finish, - wave_encoder_open, - wave_encoder_close, - nullptr, - nullptr, - nullptr, - nullptr, - wave_encoder_write, - wave_encoder_read, - wave_encoder_get_mime_type, -}; diff --git a/src/encoder/WaveEncoderPlugin.hxx b/src/encoder/WaveEncoderPlugin.hxx deleted file mode 100644 index 190ee131e..000000000 --- a/src/encoder/WaveEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ENCODER_WAVE_HXX -#define MPD_ENCODER_WAVE_HXX - -extern const struct EncoderPlugin wave_encoder_plugin; - -#endif diff --git a/src/encoder/plugins/FlacEncoderPlugin.cxx b/src/encoder/plugins/FlacEncoderPlugin.cxx new file mode 100644 index 000000000..26987fe99 --- /dev/null +++ b/src/encoder/plugins/FlacEncoderPlugin.cxx @@ -0,0 +1,325 @@ +/* + * 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 "FlacEncoderPlugin.hxx" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "pcm/PcmBuffer.hxx" +#include "config/ConfigError.hxx" +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <FLAC/stream_encoder.h> + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#error libFLAC is too old +#endif + +struct flac_encoder { + Encoder encoder; + + AudioFormat audio_format; + unsigned compression; + + FLAC__StreamEncoder *fse; + + PcmBuffer expand_buffer; + + /** + * This buffer will hold encoded data from libFLAC until it is + * picked up with flac_encoder_read(). + */ + Manual<DynamicFifoBuffer<uint8_t>> output_buffer; + + flac_encoder():encoder(flac_encoder_plugin) {} +}; + +static constexpr Domain flac_encoder_domain("vorbis_encoder"); + +static bool +flac_encoder_configure(struct flac_encoder *encoder, const config_param ¶m, + gcc_unused Error &error) +{ + encoder->compression = param.GetBlockValue("compression", 5u); + + return true; +} + +static Encoder * +flac_encoder_init(const config_param ¶m, Error &error) +{ + flac_encoder *encoder = new flac_encoder(); + + /* load configuration from "param" */ + if (!flac_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +flac_encoder_finish(Encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + /* the real libFLAC cleanup was already performed by + flac_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, + Error &error) +{ + if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, + encoder->compression)) { + error.Format(config_domain, + "error setting flac compression to %d", + encoder->compression); + return false; + } + + if ( !FLAC__stream_encoder_set_channels(encoder->fse, + encoder->audio_format.channels)) { + error.Format(config_domain, + "error setting flac channels num to %d", + encoder->audio_format.channels); + return false; + } + if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse, + bits_per_sample)) { + error.Format(config_domain, + "error setting flac bit format to %d", + bits_per_sample); + return false; + } + if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse, + encoder->audio_format.sample_rate)) { + error.Format(config_domain, + "error setting flac sample rate to %d", + encoder->audio_format.sample_rate); + return false; + } + return true; +} + +static FLAC__StreamEncoderWriteStatus +flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse, + const FLAC__byte data[], + size_t bytes, + gcc_unused unsigned samples, + gcc_unused unsigned current_frame, void *client_data) +{ + struct flac_encoder *encoder = (struct flac_encoder *) client_data; + + //transfer data to buffer + encoder->output_buffer->Append((const uint8_t *)data, bytes); + + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + +static void +flac_encoder_close(Encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + FLAC__stream_encoder_delete(encoder->fse); + + encoder->expand_buffer.Clear(); + encoder->output_buffer.Destruct(); +} + +static bool +flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned bits_per_sample; + + encoder->audio_format = audio_format; + + /* FIXME: flac should support 32bit as well */ + switch (audio_format.format) { + case SampleFormat::S8: + bits_per_sample = 8; + break; + + case SampleFormat::S16: + bits_per_sample = 16; + break; + + case SampleFormat::S24_P32: + bits_per_sample = 24; + break; + + default: + bits_per_sample = 24; + audio_format.format = SampleFormat::S24_P32; + } + + /* allocate the encoder */ + encoder->fse = FLAC__stream_encoder_new(); + if (encoder->fse == nullptr) { + error.Set(flac_encoder_domain, "flac_new() failed"); + return false; + } + + if (!flac_encoder_setup(encoder, bits_per_sample, error)) { + FLAC__stream_encoder_delete(encoder->fse); + return false; + } + + encoder->output_buffer.Construct(8192); + + /* this immediately outputs data through callback */ + + { + FLAC__StreamEncoderInitStatus init_status; + + init_status = FLAC__stream_encoder_init_stream(encoder->fse, + flac_write_callback, + nullptr, nullptr, nullptr, encoder); + + if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + error.Format(flac_encoder_domain, + "failed to initialize encoder: %s\n", + FLAC__StreamEncoderInitStatusString[init_status]); + flac_encoder_close(_encoder); + return false; + } + } + + return true; +} + + +static bool +flac_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + (void) FLAC__stream_encoder_finish(encoder->fse); + return true; +} + +static inline void +pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) +{ + while (num_samples > 0) { + *out++ = *in++; + --num_samples; + } +} + +static inline void +pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples) +{ + while (num_samples > 0) { + *out++ = *in++; + --num_samples; + } +} + +static bool +flac_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned num_frames, num_samples; + void *exbuffer; + const void *buffer = nullptr; + + /* format conversion */ + + num_frames = length / encoder->audio_format.GetFrameSize(); + num_samples = num_frames * encoder->audio_format.channels; + + switch (encoder->audio_format.format) { + case SampleFormat::S8: + exbuffer = encoder->expand_buffer.Get(length * 4); + pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data, + num_samples); + buffer = exbuffer; + break; + + case SampleFormat::S16: + exbuffer = encoder->expand_buffer.Get(length * 2); + pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data, + num_samples); + buffer = exbuffer; + break; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + /* nothing need to be done; format is the same for + both mpd and libFLAC */ + buffer = data; + break; + + default: + gcc_unreachable(); + } + + /* feed samples to encoder */ + + if (!FLAC__stream_encoder_process_interleaved(encoder->fse, + (const FLAC__int32 *)buffer, + num_frames)) { + error.Set(flac_encoder_domain, "flac encoder process failed"); + return false; + } + + return true; +} + +static size_t +flac_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + return encoder->output_buffer->Read((uint8_t *)dest, length); +} + +static const char * +flac_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/flac"; +} + +const EncoderPlugin flac_encoder_plugin = { + "flac", + flac_encoder_init, + flac_encoder_finish, + flac_encoder_open, + flac_encoder_close, + flac_encoder_flush, + flac_encoder_flush, + nullptr, + nullptr, + flac_encoder_write, + flac_encoder_read, + flac_encoder_get_mime_type, +}; + diff --git a/src/encoder/plugins/FlacEncoderPlugin.hxx b/src/encoder/plugins/FlacEncoderPlugin.hxx new file mode 100644 index 000000000..0cdc01600 --- /dev/null +++ b/src/encoder/plugins/FlacEncoderPlugin.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_ENCODER_FLAC_HXX +#define MPD_ENCODER_FLAC_HXX + +extern const struct EncoderPlugin flac_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/LameEncoderPlugin.cxx b/src/encoder/plugins/LameEncoderPlugin.cxx new file mode 100644 index 000000000..3878b52bb --- /dev/null +++ b/src/encoder/plugins/LameEncoderPlugin.cxx @@ -0,0 +1,293 @@ +/* + * 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 "LameEncoderPlugin.hxx" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/NumberParser.hxx" +#include "util/ReusableArray.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <lame/lame.h> + +#include <assert.h> +#include <string.h> + +struct LameEncoder final { + Encoder encoder; + + AudioFormat audio_format; + float quality; + int bitrate; + + lame_global_flags *gfp; + + Manual<ReusableArray<unsigned char, 32768>> output_buffer; + unsigned char *output_begin, *output_end; + + LameEncoder():encoder(lame_encoder_plugin) {} + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain lame_encoder_domain("lame_encoder"); + +bool +LameEncoder::Configure(const config_param ¶m, Error &error) +{ + const char *value; + char *endptr; + + value = param.GetBlockValue("quality"); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + quality = ParseDouble(value, &endptr); + + if (*endptr != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are both defined"); + return false; + } + } else { + /* a bit rate was configured */ + + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + quality = -2.0; + bitrate = ParseInt(value, &endptr); + + if (*endptr != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate should be a positive integer"); + return false; + } + } + + return true; +} + +static Encoder * +lame_encoder_init(const config_param ¶m, Error &error) +{ + LameEncoder *encoder = new LameEncoder(); + + /* load configuration from "param" */ + if (!encoder->Configure(param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +lame_encoder_finish(Encoder *_encoder) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + /* the real liblame cleanup was already performed by + lame_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +lame_encoder_setup(LameEncoder *encoder, Error &error) +{ + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) { + error.Set(lame_encoder_domain, + "error setting lame VBR mode"); + return false; + } + if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) { + error.Set(lame_encoder_domain, + "error setting lame VBR quality"); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) { + error.Set(lame_encoder_domain, + "error setting lame bitrate"); + return false; + } + } + + if (0 != lame_set_num_channels(encoder->gfp, + encoder->audio_format.channels)) { + error.Set(lame_encoder_domain, + "error setting lame num channels"); + return false; + } + + if (0 != lame_set_in_samplerate(encoder->gfp, + encoder->audio_format.sample_rate)) { + error.Set(lame_encoder_domain, + "error setting lame sample rate"); + return false; + } + + if (0 != lame_set_out_samplerate(encoder->gfp, + encoder->audio_format.sample_rate)) { + error.Set(lame_encoder_domain, + "error setting lame out sample rate"); + return false; + } + + if (0 > lame_init_params(encoder->gfp)) { + error.Set(lame_encoder_domain, + "error initializing lame params"); + return false; + } + + return true; +} + +static bool +lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + audio_format.format = SampleFormat::S16; + audio_format.channels = 2; + + encoder->audio_format = audio_format; + + encoder->gfp = lame_init(); + if (encoder->gfp == nullptr) { + error.Set(lame_encoder_domain, "lame_init() failed"); + return false; + } + + if (!lame_encoder_setup(encoder, error)) { + lame_close(encoder->gfp); + return false; + } + + encoder->output_buffer.Construct(); + encoder->output_begin = encoder->output_end = nullptr; + + return true; +} + +static void +lame_encoder_close(Encoder *_encoder) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + lame_close(encoder->gfp); + encoder->output_buffer.Destruct(); +} + +static bool +lame_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + const int16_t *src = (const int16_t*)data; + + assert(encoder->output_begin == encoder->output_end); + + const unsigned num_frames = + length / encoder->audio_format.GetFrameSize(); + const unsigned num_samples = + length / encoder->audio_format.GetSampleSize(); + + /* worst-case formula according to LAME documentation */ + const size_t output_buffer_size = 5 * num_samples / 4 + 7200; + const auto output_buffer = encoder->output_buffer->Get(output_buffer_size); + + /* this is for only 16-bit audio */ + + int bytes_out = lame_encode_buffer_interleaved(encoder->gfp, + const_cast<short *>(src), + num_frames, + output_buffer, + output_buffer_size); + + if (bytes_out < 0) { + error.Set(lame_encoder_domain, "lame encoder failed"); + return false; + } + + encoder->output_begin = output_buffer; + encoder->output_end = output_buffer + bytes_out; + return true; +} + +static size_t +lame_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + LameEncoder *encoder = (LameEncoder *)_encoder; + + const auto begin = encoder->output_begin; + assert(begin <= encoder->output_end); + const size_t remainning = encoder->output_end - begin; + if (length > remainning) + length = remainning; + + memcpy(dest, begin, length); + + encoder->output_begin = begin + length; + return length; +} + +static const char * +lame_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/mpeg"; +} + +const EncoderPlugin lame_encoder_plugin = { + "lame", + lame_encoder_init, + lame_encoder_finish, + lame_encoder_open, + lame_encoder_close, + nullptr, + nullptr, + nullptr, + nullptr, + lame_encoder_write, + lame_encoder_read, + lame_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/LameEncoderPlugin.hxx b/src/encoder/plugins/LameEncoderPlugin.hxx new file mode 100644 index 000000000..03e398f67 --- /dev/null +++ b/src/encoder/plugins/LameEncoderPlugin.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_ENCODER_LAME_HXX +#define MPD_ENCODER_LAME_HXX + +extern const struct EncoderPlugin lame_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/NullEncoderPlugin.cxx b/src/encoder/plugins/NullEncoderPlugin.cxx new file mode 100644 index 000000000..1d571d465 --- /dev/null +++ b/src/encoder/plugins/NullEncoderPlugin.cxx @@ -0,0 +1,105 @@ +/* + * 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 "NullEncoderPlugin.hxx" +#include "../EncoderAPI.hxx" +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "Compiler.h" + +#include <assert.h> + +struct NullEncoder final { + Encoder encoder; + + Manual<DynamicFifoBuffer<uint8_t>> buffer; + + NullEncoder() + :encoder(null_encoder_plugin) {} +}; + +static Encoder * +null_encoder_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + NullEncoder *encoder = new NullEncoder(); + return &encoder->encoder; +} + +static void +null_encoder_finish(Encoder *_encoder) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + delete encoder; +} + +static void +null_encoder_close(Encoder *_encoder) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + encoder->buffer.Destruct(); +} + + +static bool +null_encoder_open(Encoder *_encoder, + gcc_unused AudioFormat &audio_format, + gcc_unused Error &error) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + encoder->buffer.Construct(8192); + return true; +} + +static bool +null_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + encoder->buffer->Append((const uint8_t *)data, length); + return length; +} + +static size_t +null_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + return encoder->buffer->Read((uint8_t *)dest, length); +} + +const EncoderPlugin null_encoder_plugin = { + "null", + null_encoder_init, + null_encoder_finish, + null_encoder_open, + null_encoder_close, + nullptr, + nullptr, + nullptr, + nullptr, + null_encoder_write, + null_encoder_read, + nullptr, +}; diff --git a/src/encoder/plugins/NullEncoderPlugin.hxx b/src/encoder/plugins/NullEncoderPlugin.hxx new file mode 100644 index 000000000..6acf88e49 --- /dev/null +++ b/src/encoder/plugins/NullEncoderPlugin.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_ENCODER_NULL_HXX +#define MPD_ENCODER_NULL_HXX + +extern const struct EncoderPlugin null_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/OggSerial.cxx b/src/encoder/plugins/OggSerial.cxx new file mode 100644 index 000000000..677829439 --- /dev/null +++ b/src/encoder/plugins/OggSerial.cxx @@ -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. + */ + +#include "OggSerial.hxx" +#include "system/Clock.hxx" +#include "Compiler.h" + +#include <atomic> + +static std::atomic_uint next_ogg_serial; + +int +GenerateOggSerial() +{ + unsigned serial = ++next_ogg_serial; + if (gcc_unlikely(serial < 16)) { + /* first-time initialization: seed with a clock value, + which is random enough for our use */ + + /* this code is not race-free, but good enough */ + const unsigned seed = MonotonicClockMS(); + next_ogg_serial = serial = seed; + } + + return serial; +} + diff --git a/src/encoder/plugins/OggSerial.hxx b/src/encoder/plugins/OggSerial.hxx new file mode 100644 index 000000000..ceba8ebf9 --- /dev/null +++ b/src/encoder/plugins/OggSerial.hxx @@ -0,0 +1,29 @@ +/* + * 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_OGG_SERIAL_HXX +#define MPD_OGG_SERIAL_HXX + +/** + * Generate the next pseudo-random Ogg serial. + */ +int +GenerateOggSerial(); + +#endif diff --git a/src/encoder/plugins/OggStream.hxx b/src/encoder/plugins/OggStream.hxx new file mode 100644 index 000000000..805238c1d --- /dev/null +++ b/src/encoder/plugins/OggStream.hxx @@ -0,0 +1,128 @@ +/* + * 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_OGG_STREAM_HXX +#define MPD_OGG_STREAM_HXX + +#include "check.h" + +#include <ogg/ogg.h> + +#include <assert.h> +#include <string.h> +#include <stdint.h> + +class OggStream { + ogg_stream_state state; + + bool flush; + +#ifndef NDEBUG + bool initialized; +#endif + +public: +#ifndef NDEBUG + OggStream():initialized(false) {} + ~OggStream() { + assert(!initialized); + } +#endif + + void Initialize(int serialno) { + assert(!initialized); + + ogg_stream_init(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + +#ifndef NDEBUG + initialized = true; +#endif + } + + void Reinitialize(int serialno) { + assert(initialized); + + ogg_stream_reset_serialno(&state, serialno); + + /* set "flush" to true, so the caller gets the full + headers on the first read() */ + flush = true; + } + + void Deinitialize() { + assert(initialized); + + ogg_stream_clear(&state); + +#ifndef NDEBUG + initialized = false; +#endif + } + + void Flush() { + assert(initialized); + + flush = true; + } + + void PacketIn(const ogg_packet &packet) { + assert(initialized); + + ogg_stream_packetin(&state, + const_cast<ogg_packet *>(&packet)); + } + + bool PageOut(ogg_page &page) { + int result = ogg_stream_pageout(&state, &page); + if (result == 0 && flush) { + flush = false; + result = ogg_stream_flush(&state, &page); + } + + return result != 0; + } + + size_t PageOut(void *_buffer, size_t size) { + ogg_page page; + if (!PageOut(page)) + return 0; + + assert(page.header_len > 0 || page.body_len > 0); + + size_t header_len = (size_t)page.header_len; + size_t body_len = (size_t)page.body_len; + assert(header_len <= size); + + if (header_len + body_len > size) + /* TODO: better overflow handling */ + body_len = size - header_len; + + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(buffer, page.header, header_len); + memcpy(buffer + header_len, page.body, body_len); + + return header_len + body_len; + } +}; + +#endif diff --git a/src/encoder/plugins/OpusEncoderPlugin.cxx b/src/encoder/plugins/OpusEncoderPlugin.cxx new file mode 100644 index 000000000..27b614b86 --- /dev/null +++ b/src/encoder/plugins/OpusEncoderPlugin.cxx @@ -0,0 +1,419 @@ +/* + * 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 "OpusEncoderPlugin.hxx" +#include "OggStream.hxx" +#include "OggSerial.hxx" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/Alloc.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <assert.h> +#include <stdlib.h> + +struct opus_encoder { + /** the base class */ + Encoder encoder; + + /* configuration */ + + opus_int32 bitrate; + int complexity; + int signal; + + /* runtime information */ + + AudioFormat audio_format; + + size_t frame_size; + + size_t buffer_frames, buffer_size, buffer_position; + uint8_t *buffer; + + OpusEncoder *enc; + + unsigned char buffer2[1275 * 3 + 7]; + + OggStream stream; + + int lookahead; + + ogg_int64_t packetno; + + ogg_int64_t granulepos; + + opus_encoder():encoder(opus_encoder_plugin) {} +}; + +static constexpr Domain opus_encoder_domain("opus_encoder"); + +static bool +opus_encoder_configure(struct opus_encoder *encoder, + const config_param ¶m, Error &error) +{ + const char *value = param.GetBlockValue("bitrate", "auto"); + if (strcmp(value, "auto") == 0) + encoder->bitrate = OPUS_AUTO; + else if (strcmp(value, "max") == 0) + encoder->bitrate = OPUS_BITRATE_MAX; + else { + char *endptr; + encoder->bitrate = strtoul(value, &endptr, 10); + if (endptr == value || *endptr != 0 || + encoder->bitrate < 500 || encoder->bitrate > 512000) { + error.Set(config_domain, "Invalid bit rate"); + return false; + } + } + + encoder->complexity = param.GetBlockValue("complexity", 10u); + if (encoder->complexity > 10) { + error.Format(config_domain, "Invalid complexity"); + return false; + } + + value = param.GetBlockValue("signal", "auto"); + if (strcmp(value, "auto") == 0) + encoder->signal = OPUS_AUTO; + else if (strcmp(value, "voice") == 0) + encoder->signal = OPUS_SIGNAL_VOICE; + else if (strcmp(value, "music") == 0) + encoder->signal = OPUS_SIGNAL_MUSIC; + else { + error.Format(config_domain, "Invalid signal"); + return false; + } + + return true; +} + +static Encoder * +opus_encoder_init(const config_param ¶m, Error &error) +{ + opus_encoder *encoder = new opus_encoder(); + + /* load configuration from "param" */ + if (!opus_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +opus_encoder_finish(Encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* the real libopus cleanup was already performed by + opus_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +opus_encoder_open(Encoder *_encoder, + AudioFormat &audio_format, + Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + /* libopus supports only 48 kHz */ + audio_format.sample_rate = 48000; + + if (audio_format.channels > 2) + audio_format.channels = 1; + + switch (audio_format.format) { + case SampleFormat::S16: + case SampleFormat::FLOAT: + break; + + case SampleFormat::S8: + audio_format.format = SampleFormat::S16; + break; + + default: + audio_format.format = SampleFormat::FLOAT; + break; + } + + encoder->audio_format = audio_format; + encoder->frame_size = audio_format.GetFrameSize(); + + int error_code; + encoder->enc = opus_encoder_create(audio_format.sample_rate, + audio_format.channels, + OPUS_APPLICATION_AUDIO, + &error_code); + if (encoder->enc == nullptr) { + error.Set(opus_encoder_domain, error_code, + opus_strerror(error_code)); + return false; + } + + opus_encoder_ctl(encoder->enc, OPUS_SET_BITRATE(encoder->bitrate)); + opus_encoder_ctl(encoder->enc, + OPUS_SET_COMPLEXITY(encoder->complexity)); + opus_encoder_ctl(encoder->enc, OPUS_SET_SIGNAL(encoder->signal)); + + opus_encoder_ctl(encoder->enc, OPUS_GET_LOOKAHEAD(&encoder->lookahead)); + + encoder->buffer_frames = audio_format.sample_rate / 50; + encoder->buffer_size = encoder->frame_size * encoder->buffer_frames; + encoder->buffer_position = 0; + encoder->buffer = (unsigned char *)xalloc(encoder->buffer_size); + + encoder->stream.Initialize(GenerateOggSerial()); + encoder->packetno = 0; + + return true; +} + +static void +opus_encoder_close(Encoder *_encoder) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Deinitialize(); + free(encoder->buffer); + opus_encoder_destroy(encoder->enc); +} + +static bool +opus_encoder_do_encode(struct opus_encoder *encoder, bool eos, + Error &error) +{ + assert(encoder->buffer_position == encoder->buffer_size); + + opus_int32 result = + encoder->audio_format.format == SampleFormat::S16 + ? opus_encode(encoder->enc, + (const opus_int16 *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)) + : opus_encode_float(encoder->enc, + (const float *)encoder->buffer, + encoder->buffer_frames, + encoder->buffer2, + sizeof(encoder->buffer2)); + if (result < 0) { + error.Set(opus_encoder_domain, "Opus encoder error"); + return false; + } + + encoder->granulepos += encoder->buffer_frames; + + ogg_packet packet; + packet.packet = encoder->buffer2; + packet.bytes = result; + packet.b_o_s = false; + packet.e_o_s = eos; + packet.granulepos = encoder->granulepos; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + + encoder->buffer_position = 0; + + return true; +} + +static bool +opus_encoder_end(Encoder *_encoder, Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + + memset(encoder->buffer + encoder->buffer_position, 0, + encoder->buffer_size - encoder->buffer_position); + encoder->buffer_position = encoder->buffer_size; + + return opus_encoder_do_encode(encoder, true, error); +} + +static bool +opus_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +opus_encoder_write_silence(struct opus_encoder *encoder, unsigned fill_frames, + Error &error) +{ + size_t fill_bytes = fill_frames * encoder->frame_size; + + while (fill_bytes > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > fill_bytes) + nbytes = fill_bytes; + + memset(encoder->buffer + encoder->buffer_position, + 0, nbytes); + encoder->buffer_position += nbytes; + fill_bytes -= nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error)) + return false; + } + + return true; +} + +static bool +opus_encoder_write(Encoder *_encoder, + const void *_data, size_t length, + Error &error) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + const uint8_t *data = (const uint8_t *)_data; + + if (encoder->lookahead > 0) { + /* generate some silence at the beginning of the + stream */ + + assert(encoder->buffer_position == 0); + + if (!opus_encoder_write_silence(encoder, encoder->lookahead, + error)) + return false; + + encoder->lookahead = 0; + } + + while (length > 0) { + size_t nbytes = + encoder->buffer_size - encoder->buffer_position; + if (nbytes > length) + nbytes = length; + + memcpy(encoder->buffer + encoder->buffer_position, + data, nbytes); + data += nbytes; + length -= nbytes; + encoder->buffer_position += nbytes; + + if (encoder->buffer_position == encoder->buffer_size && + !opus_encoder_do_encode(encoder, false, error)) + return false; + } + + return true; +} + +static void +opus_encoder_generate_head(struct opus_encoder *encoder) +{ + unsigned char header[19]; + memcpy(header, "OpusHead", 8); + header[8] = 1; + header[9] = encoder->audio_format.channels; + *(uint16_t *)(header + 10) = ToLE16(encoder->lookahead); + *(uint32_t *)(header + 12) = + ToLE32(encoder->audio_format.sample_rate); + header[16] = 0; + header[17] = 0; + header[18] = 0; + + ogg_packet packet; + packet.packet = header; + packet.bytes = 19; + packet.b_o_s = true; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); +} + +static void +opus_encoder_generate_tags(struct opus_encoder *encoder) +{ + const char *version = opus_get_version_string(); + size_t version_length = strlen(version); + + size_t comments_size = 8 + 4 + version_length + 4; + unsigned char *comments = (unsigned char *)xalloc(comments_size); + memcpy(comments, "OpusTags", 8); + *(uint32_t *)(comments + 8) = ToLE32(version_length); + memcpy(comments + 12, version, version_length); + *(uint32_t *)(comments + 12 + version_length) = ToLE32(0); + + ogg_packet packet; + packet.packet = comments; + packet.bytes = comments_size; + packet.b_o_s = false; + packet.e_o_s = false; + packet.granulepos = 0; + packet.packetno = encoder->packetno++; + encoder->stream.PacketIn(packet); + encoder->stream.Flush(); + + free(comments); +} + +static size_t +opus_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + struct opus_encoder *encoder = (struct opus_encoder *)_encoder; + + if (encoder->packetno == 0) + opus_encoder_generate_head(encoder); + else if (encoder->packetno == 1) + opus_encoder_generate_tags(encoder); + + return encoder->stream.PageOut(dest, length); +} + +static const char * +opus_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/ogg"; +} + +const EncoderPlugin opus_encoder_plugin = { + "opus", + opus_encoder_init, + opus_encoder_finish, + opus_encoder_open, + opus_encoder_close, + opus_encoder_end, + opus_encoder_flush, + nullptr, + nullptr, + opus_encoder_write, + opus_encoder_read, + opus_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/OpusEncoderPlugin.hxx b/src/encoder/plugins/OpusEncoderPlugin.hxx new file mode 100644 index 000000000..4e71694b9 --- /dev/null +++ b/src/encoder/plugins/OpusEncoderPlugin.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_ENCODER_OPUS_H +#define MPD_ENCODER_OPUS_H + +extern const struct EncoderPlugin opus_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/ShineEncoderPlugin.cxx b/src/encoder/plugins/ShineEncoderPlugin.cxx new file mode 100644 index 000000000..61cb8609e --- /dev/null +++ b/src/encoder/plugins/ShineEncoderPlugin.cxx @@ -0,0 +1,271 @@ +/* + * 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 "ShineEncoderPlugin.hxx" +#include "config.h" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/Manual.hxx" +#include "util/NumberParser.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +extern "C" +{ +#include <shine/layer3.h> +} + +static constexpr size_t BUFFER_INIT_SIZE = 8192; +static constexpr unsigned CHANNELS = 2; + +struct ShineEncoder { + Encoder encoder; + + AudioFormat audio_format; + + shine_t shine; + + shine_config_t config; + + size_t frame_size; + size_t input_pos; + int16_t *stereo[CHANNELS]; + + Manual<DynamicFifoBuffer<uint8_t>> output_buffer; + + ShineEncoder():encoder(shine_encoder_plugin){} + + bool Configure(const config_param ¶m, Error &error); + + bool Setup(Error &error); + + bool WriteChunk(bool flush); +}; + +static constexpr Domain shine_encoder_domain("shine_encoder"); + +inline bool +ShineEncoder::Configure(const config_param ¶m, + gcc_unused Error &error) +{ + shine_set_config_mpeg_defaults(&config.mpeg); + config.mpeg.bitr = param.GetBlockValue("bitrate", 128); + + return true; +} + +static Encoder * +shine_encoder_init(const config_param ¶m, Error &error) +{ + ShineEncoder *encoder = new ShineEncoder(); + + /* load configuration from "param" */ + if (!encoder->Configure(param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +shine_encoder_finish(Encoder *_encoder) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + delete encoder; +} + +inline bool +ShineEncoder::Setup(Error &error) +{ + config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO; + config.wave.samplerate = audio_format.sample_rate; + config.wave.channels = + audio_format.channels == 2 ? PCM_STEREO : PCM_MONO; + + if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) { + error.Format(config_domain, + "error configuring shine. " + "samplerate %d and bitrate %d configuration" + " not supported.", + config.wave.samplerate, + config.mpeg.bitr); + + return false; + } + + shine = shine_initialise(&config); + + if (!shine) { + error.Format(config_domain, + "error initializing shine."); + + return false; + } + + frame_size = shine_samples_per_pass(shine); + + return true; +} + +static bool +shine_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + audio_format.format = SampleFormat::S16; + audio_format.channels = CHANNELS; + encoder->audio_format = audio_format; + + if (!encoder->Setup(error)) + return false; + + encoder->stereo[0] = new int16_t[encoder->frame_size]; + encoder->stereo[1] = new int16_t[encoder->frame_size]; + /* workaround for bug: + https://github.com/savonet/shine/issues/11 */ + encoder->input_pos = SHINE_MAX_SAMPLES + 1; + + encoder->output_buffer.Construct(BUFFER_INIT_SIZE); + + return true; +} + +static void +shine_encoder_close(Encoder *_encoder) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + if (encoder->input_pos > SHINE_MAX_SAMPLES) { + /* write zero chunk */ + encoder->input_pos = 0; + encoder->WriteChunk(true); + } + + shine_close(encoder->shine); + delete[] encoder->stereo[0]; + delete[] encoder->stereo[1]; + encoder->output_buffer.Destruct(); +} + +bool +ShineEncoder::WriteChunk(bool flush) +{ + if (flush || input_pos == frame_size) { + if (flush) { + /* fill remaining with 0s */ + for (; input_pos < frame_size; input_pos++) { + stereo[0][input_pos] = stereo[1][input_pos] = 0; + } + } + + int written; + const uint8_t *out = + shine_encode_buffer(shine, stereo, &written); + + if (written > 0) + output_buffer->Append(out, written); + + input_pos = 0; + } + + return true; +} + +static bool +shine_encoder_write(Encoder *_encoder, + const void *_data, size_t length, + gcc_unused Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + const int16_t *data = (const int16_t*)_data; + length /= sizeof(*data) * encoder->audio_format.channels; + size_t written = 0; + + if (encoder->input_pos > SHINE_MAX_SAMPLES) { + encoder->input_pos = 0; + } + + /* write all data to de-interleaved buffers */ + while (written < length) { + for (; + written < length + && encoder->input_pos < encoder->frame_size; + written++, encoder->input_pos++) { + const size_t base = + written * encoder->audio_format.channels; + encoder->stereo[0][encoder->input_pos] = data[base]; + encoder->stereo[1][encoder->input_pos] = data[base + 1]; + } + /* write if chunk is filled */ + encoder->WriteChunk(false); + } + + return true; +} + +static bool +shine_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + /* flush buffers and flush shine */ + encoder->WriteChunk(true); + + int written; + const uint8_t *data = shine_flush(encoder->shine, &written); + + if (written > 0) + encoder->output_buffer->Append(data, written); + + return true; +} + +static size_t +shine_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + return encoder->output_buffer->Read((uint8_t *)dest, length); +} + +static const char * +shine_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/mpeg"; +} + +const EncoderPlugin shine_encoder_plugin = { + "shine", + shine_encoder_init, + shine_encoder_finish, + shine_encoder_open, + shine_encoder_close, + shine_encoder_flush, + shine_encoder_flush, + nullptr, + nullptr, + shine_encoder_write, + shine_encoder_read, + shine_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/ShineEncoderPlugin.hxx b/src/encoder/plugins/ShineEncoderPlugin.hxx new file mode 100644 index 000000000..8b1520a74 --- /dev/null +++ b/src/encoder/plugins/ShineEncoderPlugin.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_ENCODER_SHINE_HXX +#define MPD_ENCODER_SHINE_HXX + +extern const struct EncoderPlugin shine_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/TwolameEncoderPlugin.cxx b/src/encoder/plugins/TwolameEncoderPlugin.cxx new file mode 100644 index 000000000..2eb6b2b1c --- /dev/null +++ b/src/encoder/plugins/TwolameEncoderPlugin.cxx @@ -0,0 +1,314 @@ +/* + * 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 "TwolameEncoderPlugin.hxx" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <twolame.h> + +#include <assert.h> +#include <string.h> + +struct TwolameEncoder final { + Encoder encoder; + + AudioFormat audio_format; + float quality; + int bitrate; + + twolame_options *options; + + unsigned char output_buffer[32768]; + size_t output_buffer_length; + size_t output_buffer_position; + + /** + * Call libtwolame's flush function when the output_buffer is + * empty? + */ + bool flush; + + TwolameEncoder():encoder(twolame_encoder_plugin) {} + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain twolame_encoder_domain("twolame_encoder"); + +bool +TwolameEncoder::Configure(const config_param ¶m, Error &error) +{ + const char *value; + char *endptr; + + value = param.GetBlockValue("quality"); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + quality = ParseDouble(value, &endptr); + + if (*endptr != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are both defined"); + return false; + } + } else { + /* a bit rate was configured */ + + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + quality = -2.0; + bitrate = ParseInt(value, &endptr); + + if (*endptr != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate should be a positive integer"); + return false; + } + } + + return true; +} + +static Encoder * +twolame_encoder_init(const config_param ¶m, Error &error_r) +{ + FormatDebug(twolame_encoder_domain, + "libtwolame version %s", get_twolame_version()); + + TwolameEncoder *encoder = new TwolameEncoder(); + + /* load configuration from "param" */ + if (!encoder->Configure(param, error_r)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +twolame_encoder_finish(Encoder *_encoder) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + /* the real libtwolame cleanup was already performed by + twolame_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +twolame_encoder_setup(TwolameEncoder *encoder, Error &error) +{ + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != twolame_set_VBR(encoder->options, true)) { + error.Set(twolame_encoder_domain, + "error setting twolame VBR mode"); + return false; + } + if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) { + error.Set(twolame_encoder_domain, + "error setting twolame VBR quality"); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) { + error.Set(twolame_encoder_domain, + "error setting twolame bitrate"); + return false; + } + } + + if (0 != twolame_set_num_channels(encoder->options, + encoder->audio_format.channels)) { + error.Set(twolame_encoder_domain, + "error setting twolame num channels"); + return false; + } + + if (0 != twolame_set_in_samplerate(encoder->options, + encoder->audio_format.sample_rate)) { + error.Set(twolame_encoder_domain, + "error setting twolame sample rate"); + return false; + } + + if (0 > twolame_init_params(encoder->options)) { + error.Set(twolame_encoder_domain, + "error initializing twolame params"); + return false; + } + + return true; +} + +static bool +twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, + Error &error) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + audio_format.format = SampleFormat::S16; + audio_format.channels = 2; + + encoder->audio_format = audio_format; + + encoder->options = twolame_init(); + if (encoder->options == nullptr) { + error.Set(twolame_encoder_domain, "twolame_init() failed"); + return false; + } + + if (!twolame_encoder_setup(encoder, error)) { + twolame_close(&encoder->options); + return false; + } + + encoder->output_buffer_length = 0; + encoder->output_buffer_position = 0; + encoder->flush = false; + + return true; +} + +static void +twolame_encoder_close(Encoder *_encoder) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + twolame_close(&encoder->options); +} + +static bool +twolame_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + encoder->flush = true; + return true; +} + +static bool +twolame_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + const int16_t *src = (const int16_t*)data; + + assert(encoder->output_buffer_position == + encoder->output_buffer_length); + + const unsigned num_frames = + length / encoder->audio_format.GetFrameSize(); + + int bytes_out = twolame_encode_buffer_interleaved(encoder->options, + src, num_frames, + encoder->output_buffer, + sizeof(encoder->output_buffer)); + if (bytes_out < 0) { + error.Set(twolame_encoder_domain, "twolame encoder failed"); + return false; + } + + encoder->output_buffer_length = (size_t)bytes_out; + encoder->output_buffer_position = 0; + return true; +} + +static size_t +twolame_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + TwolameEncoder *encoder = (TwolameEncoder *)_encoder; + + assert(encoder->output_buffer_position <= + encoder->output_buffer_length); + + if (encoder->output_buffer_position == encoder->output_buffer_length && + encoder->flush) { + int ret = twolame_encode_flush(encoder->options, + encoder->output_buffer, + sizeof(encoder->output_buffer)); + if (ret > 0) { + encoder->output_buffer_length = (size_t)ret; + encoder->output_buffer_position = 0; + } + + encoder->flush = false; + } + + + const size_t remainning = encoder->output_buffer_length + - encoder->output_buffer_position; + if (length > remainning) + length = remainning; + + memcpy(dest, encoder->output_buffer + encoder->output_buffer_position, + length); + + encoder->output_buffer_position += length; + + return length; +} + +static const char * +twolame_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/mpeg"; +} + +const EncoderPlugin twolame_encoder_plugin = { + "twolame", + twolame_encoder_init, + twolame_encoder_finish, + twolame_encoder_open, + twolame_encoder_close, + twolame_encoder_flush, + twolame_encoder_flush, + nullptr, + nullptr, + twolame_encoder_write, + twolame_encoder_read, + twolame_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/TwolameEncoderPlugin.hxx b/src/encoder/plugins/TwolameEncoderPlugin.hxx new file mode 100644 index 000000000..531dd3e90 --- /dev/null +++ b/src/encoder/plugins/TwolameEncoderPlugin.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_ENCODER_TWOLAME_HXX +#define MPD_ENCODER_TWOLAME_HXX + +extern const struct EncoderPlugin twolame_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/VorbisEncoderPlugin.cxx b/src/encoder/plugins/VorbisEncoderPlugin.cxx new file mode 100644 index 000000000..ecc784a47 --- /dev/null +++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx @@ -0,0 +1,364 @@ +/* + * 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 "VorbisEncoderPlugin.hxx" +#include "OggStream.hxx" +#include "OggSerial.hxx" +#include "../EncoderAPI.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <vorbis/vorbisenc.h> + +#include <glib.h> + +struct vorbis_encoder { + /** the base class */ + Encoder encoder; + + /* configuration */ + + float quality; + int bitrate; + + /* runtime information */ + + AudioFormat audio_format; + + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_info vi; + + OggStream stream; + + vorbis_encoder():encoder(vorbis_encoder_plugin) {} +}; + +static constexpr Domain vorbis_encoder_domain("vorbis_encoder"); + +static bool +vorbis_encoder_configure(struct vorbis_encoder *encoder, + const config_param ¶m, Error &error) +{ + const char *value = param.GetBlockValue("quality"); + if (value != nullptr) { + /* a quality was configured (VBR) */ + + char *endptr; + encoder->quality = ParseDouble(value, &endptr); + + if (*endptr != '\0' || encoder->quality < -1.0 || + encoder->quality > 10.0) { + error.Format(config_domain, + "quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are both defined"); + return false; + } + } else { + /* a bit rate was configured */ + + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + encoder->quality = -2.0; + + char *endptr; + encoder->bitrate = ParseInt(value, &endptr); + if (*endptr != '\0' || encoder->bitrate <= 0) { + error.Set(config_domain, + "bitrate should be a positive integer"); + return false; + } + } + + return true; +} + +static Encoder * +vorbis_encoder_init(const config_param ¶m, Error &error) +{ + vorbis_encoder *encoder = new vorbis_encoder(); + + /* load configuration from "param" */ + if (!vorbis_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + delete encoder; + return nullptr; + } + + return &encoder->encoder; +} + +static void +vorbis_encoder_finish(Encoder *_encoder) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + /* the real libvorbis/libogg cleanup was already performed by + vorbis_encoder_close(), so no real work here */ + delete encoder; +} + +static bool +vorbis_encoder_reinit(struct vorbis_encoder *encoder, Error &error) +{ + vorbis_info_init(&encoder->vi); + + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != vorbis_encode_init_vbr(&encoder->vi, + encoder->audio_format.channels, + encoder->audio_format.sample_rate, + encoder->quality * 0.1)) { + error.Set(vorbis_encoder_domain, + "error initializing vorbis vbr"); + vorbis_info_clear(&encoder->vi); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != vorbis_encode_init(&encoder->vi, + encoder->audio_format.channels, + encoder->audio_format.sample_rate, -1.0, + encoder->bitrate * 1000, -1.0)) { + error.Set(vorbis_encoder_domain, + "error initializing vorbis encoder"); + vorbis_info_clear(&encoder->vi); + return false; + } + } + + vorbis_analysis_init(&encoder->vd, &encoder->vi); + vorbis_block_init(&encoder->vd, &encoder->vb); + encoder->stream.Initialize(GenerateOggSerial()); + + return true; +} + +static void +vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) +{ + ogg_packet packet, comments, codebooks; + + vorbis_analysis_headerout(&encoder->vd, vc, + &packet, &comments, &codebooks); + + encoder->stream.PacketIn(packet); + encoder->stream.PacketIn(comments); + encoder->stream.PacketIn(codebooks); +} + +static void +vorbis_encoder_send_header(struct vorbis_encoder *encoder) +{ + vorbis_comment vc; + + vorbis_comment_init(&vc); + vorbis_encoder_headerout(encoder, &vc); + vorbis_comment_clear(&vc); +} + +static bool +vorbis_encoder_open(Encoder *_encoder, + AudioFormat &audio_format, + Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + audio_format.format = SampleFormat::FLOAT; + + encoder->audio_format = audio_format; + + if (!vorbis_encoder_reinit(encoder, error)) + return false; + + vorbis_encoder_send_header(encoder); + + return true; +} + +static void +vorbis_encoder_clear(struct vorbis_encoder *encoder) +{ + encoder->stream.Deinitialize(); + vorbis_block_clear(&encoder->vb); + vorbis_dsp_clear(&encoder->vd); + vorbis_info_clear(&encoder->vi); +} + +static void +vorbis_encoder_close(Encoder *_encoder) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + vorbis_encoder_clear(encoder); +} + +static void +vorbis_encoder_blockout(struct vorbis_encoder *encoder) +{ + while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { + vorbis_analysis(&encoder->vb, nullptr); + vorbis_bitrate_addblock(&encoder->vb); + + ogg_packet packet; + while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) + encoder->stream.PacketIn(packet); + } +} + +static bool +vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + encoder->stream.Flush(); + return true; +} + +static bool +vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + vorbis_analysis_wrote(&encoder->vd, 0); + vorbis_encoder_blockout(encoder); + + /* reinitialize vorbis_dsp_state and vorbis_block to reset the + end-of-stream marker */ + vorbis_block_clear(&encoder->vb); + vorbis_dsp_clear(&encoder->vd); + vorbis_analysis_init(&encoder->vd, &encoder->vi); + vorbis_block_init(&encoder->vd, &encoder->vb); + + encoder->stream.Flush(); + return true; +} + +static void +copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag *tag) +{ + for (const auto &item : *tag) { + char *name = g_ascii_strup(tag_item_names[item.type], -1); + vorbis_comment_add_tag(vc, name, item.value); + g_free(name); + } +} + +static bool +vorbis_encoder_tag(Encoder *_encoder, const Tag *tag, + gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + vorbis_comment comment; + + /* write the vorbis_comment object */ + + vorbis_comment_init(&comment); + copy_tag_to_vorbis_comment(&comment, tag); + + /* reset ogg_stream_state and begin a new stream */ + + encoder->stream.Reinitialize(GenerateOggSerial()); + + /* send that vorbis_comment to the ogg_stream_state */ + + vorbis_encoder_headerout(encoder, &comment); + vorbis_comment_clear(&comment); + + return true; +} + +static void +interleaved_to_vorbis_buffer(float **dest, const float *src, + unsigned num_frames, unsigned num_channels) +{ + for (unsigned i = 0; i < num_frames; i++) + for (unsigned j = 0; j < num_channels; j++) + dest[j][i] = *src++; +} + +static bool +vorbis_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + unsigned num_frames = length / encoder->audio_format.GetFrameSize(); + + /* this is for only 16-bit audio */ + + interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, + num_frames), + (const float *)data, + num_frames, + encoder->audio_format.channels); + + vorbis_analysis_wrote(&encoder->vd, num_frames); + vorbis_encoder_blockout(encoder); + return true; +} + +static size_t +vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + + return encoder->stream.PageOut(dest, length); +} + +static const char * +vorbis_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/ogg"; +} + +const EncoderPlugin vorbis_encoder_plugin = { + "vorbis", + vorbis_encoder_init, + vorbis_encoder_finish, + vorbis_encoder_open, + vorbis_encoder_close, + vorbis_encoder_pre_tag, + vorbis_encoder_flush, + vorbis_encoder_pre_tag, + vorbis_encoder_tag, + vorbis_encoder_write, + vorbis_encoder_read, + vorbis_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/VorbisEncoderPlugin.hxx b/src/encoder/plugins/VorbisEncoderPlugin.hxx new file mode 100644 index 000000000..80703bf88 --- /dev/null +++ b/src/encoder/plugins/VorbisEncoderPlugin.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_ENCODER_VORBIS_H +#define MPD_ENCODER_VORBIS_H + +extern const struct EncoderPlugin vorbis_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/WaveEncoderPlugin.cxx b/src/encoder/plugins/WaveEncoderPlugin.cxx new file mode 100644 index 000000000..97a26e821 --- /dev/null +++ b/src/encoder/plugins/WaveEncoderPlugin.cxx @@ -0,0 +1,265 @@ +/* + * 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 "WaveEncoderPlugin.hxx" +#include "../EncoderAPI.hxx" +#include "system/ByteOrder.hxx" +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" + +#include <assert.h> +#include <string.h> + +struct WaveEncoder { + Encoder encoder; + unsigned bits; + + Manual<DynamicFifoBuffer<uint8_t>> buffer; + + WaveEncoder():encoder(wave_encoder_plugin) {} +}; + +struct wave_header { + uint32_t id_riff; + uint32_t riff_size; + uint32_t id_wave; + uint32_t id_fmt; + uint32_t fmt_size; + uint16_t format; + uint16_t channels; + uint32_t freq; + uint32_t byterate; + uint16_t blocksize; + uint16_t bits; + uint32_t id_data; + uint32_t data_size; +}; + +static void +fill_wave_header(struct wave_header *header, int channels, int bits, + int freq, int block_size) +{ + int data_size = 0x0FFFFFFF; + + /* constants */ + header->id_riff = ToLE32(0x46464952); + header->id_wave = ToLE32(0x45564157); + header->id_fmt = ToLE32(0x20746d66); + header->id_data = ToLE32(0x61746164); + + /* wave format */ + header->format = ToLE16(1); // PCM_FORMAT + header->channels = ToLE16(channels); + header->bits = ToLE16(bits); + header->freq = ToLE32(freq); + header->blocksize = ToLE16(block_size); + header->byterate = ToLE32(freq * block_size); + + /* chunk sizes (fake data length) */ + header->fmt_size = ToLE32(16); + header->data_size = ToLE32(data_size); + header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size)); +} + +static Encoder * +wave_encoder_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + WaveEncoder *encoder = new WaveEncoder(); + return &encoder->encoder; +} + +static void +wave_encoder_finish(Encoder *_encoder) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + delete encoder; +} + +static bool +wave_encoder_open(Encoder *_encoder, + AudioFormat &audio_format, + gcc_unused Error &error) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + assert(audio_format.IsValid()); + + switch (audio_format.format) { + case SampleFormat::S8: + encoder->bits = 8; + break; + + case SampleFormat::S16: + encoder->bits = 16; + break; + + case SampleFormat::S24_P32: + encoder->bits = 24; + break; + + case SampleFormat::S32: + encoder->bits = 32; + break; + + default: + audio_format.format = SampleFormat::S16; + encoder->bits = 16; + break; + } + + encoder->buffer.Construct(8192); + + auto range = encoder->buffer->Write(); + assert(range.size >= sizeof(wave_header)); + wave_header *header = (wave_header *)range.data; + + /* create PCM wave header in initial buffer */ + fill_wave_header(header, + audio_format.channels, + encoder->bits, + audio_format.sample_rate, + (encoder->bits / 8) * audio_format.channels); + + encoder->buffer->Append(sizeof(*header)); + + return true; +} + +static void +wave_encoder_close(Encoder *_encoder) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + encoder->buffer.Destruct(); +} + +static size_t +pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) +{ + size_t cnt = length >> 1; + while (cnt > 0) { + *dst16++ = ToLE16(*src16++); + cnt--; + } + return length; +} + +static size_t +pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) +{ + size_t cnt = length >> 2; + while (cnt > 0){ + *dst32++ = ToLE32(*src32++); + cnt--; + } + return length; +} + +static size_t +pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) +{ + uint32_t value; + uint8_t *dst_old = dst8; + + length = length >> 2; + while (length > 0){ + value = *src32++; + *dst8++ = (value) & 0xFF; + *dst8++ = (value >> 8) & 0xFF; + *dst8++ = (value >> 16) & 0xFF; + length--; + } + //correct buffer length + return (dst8 - dst_old); +} + +static bool +wave_encoder_write(Encoder *_encoder, + const void *src, size_t length, + gcc_unused Error &error) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + uint8_t *dst = encoder->buffer->Write(length); + + if (IsLittleEndian()) { + switch (encoder->bits) { + case 8: + case 16: + case 32:// optimized cases + memcpy(dst, src, length); + break; + case 24: + length = pcm24_to_wave(dst, (const uint32_t *)src, length); + break; + } + } else { + switch (encoder->bits) { + case 8: + memcpy(dst, src, length); + break; + case 16: + length = pcm16_to_wave((uint16_t *)dst, + (const uint16_t *)src, length); + break; + case 24: + length = pcm24_to_wave(dst, (const uint32_t *)src, length); + break; + case 32: + length = pcm32_to_wave((uint32_t *)dst, + (const uint32_t *)src, length); + break; + } + } + + encoder->buffer->Append(length); + return true; +} + +static size_t +wave_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + return encoder->buffer->Read((uint8_t *)dest, length); +} + +static const char * +wave_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/wav"; +} + +const EncoderPlugin wave_encoder_plugin = { + "wave", + wave_encoder_init, + wave_encoder_finish, + wave_encoder_open, + wave_encoder_close, + nullptr, + nullptr, + nullptr, + nullptr, + wave_encoder_write, + wave_encoder_read, + wave_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/WaveEncoderPlugin.hxx b/src/encoder/plugins/WaveEncoderPlugin.hxx new file mode 100644 index 000000000..341b98adc --- /dev/null +++ b/src/encoder/plugins/WaveEncoderPlugin.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_ENCODER_WAVE_HXX +#define MPD_ENCODER_WAVE_HXX + +extern const struct EncoderPlugin wave_encoder_plugin; + +#endif diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx index c93ea34c5..939824baa 100644 --- a/src/event/BufferedSocket.cxx +++ b/src/event/BufferedSocket.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,6 +22,9 @@ #include "system/SocketError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Compiler.h" + +#include <algorithm> BufferedSocket::ssize_t BufferedSocket::DirectRead(void *data, size_t length) diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx index db920f981..b1882de2f 100644 --- a/src/event/BufferedSocket.hxx +++ b/src/event/BufferedSocket.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 @@ -22,20 +22,19 @@ #include "check.h" #include "SocketMonitor.hxx" -#include "util/FifoBuffer.hxx" -#include "Compiler.h" +#include "util/StaticFifoBuffer.hxx" #include <assert.h> #include <stdint.h> -struct fifo_buffer; class Error; +class EventLoop; /** * A #SocketMonitor specialization that adds an input buffer. */ class BufferedSocket : protected SocketMonitor { - FifoBuffer<uint8_t, 8192> input; + StaticFifoBuffer<uint8_t, 8192> input; public: BufferedSocket(int _fd, EventLoop &_loop) diff --git a/src/event/Call.cxx b/src/event/Call.cxx index ab1d5ffbd..bc16c4e95 100644 --- a/src/event/Call.cxx +++ b/src/event/Call.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 @@ -28,9 +28,7 @@ #include <assert.h> class BlockingCallMonitor final -#ifndef USE_EPOLL : DeferredMonitor -#endif { const std::function<void()> f; @@ -40,24 +38,13 @@ class BlockingCallMonitor final bool done; public: -#ifdef USE_EPOLL - BlockingCallMonitor(EventLoop &loop, std::function<void()> &&_f) - :f(std::move(_f)), done(false) { - loop.AddCall([this](){ - this->DoRun(); - }); - } -#else BlockingCallMonitor(EventLoop &_loop, std::function<void()> &&_f) :DeferredMonitor(_loop), f(std::move(_f)), done(false) {} -#endif void Run() { -#ifndef USE_EPOLL assert(!done); Schedule(); -#endif mutex.lock(); while (!done) @@ -65,16 +52,8 @@ public: mutex.unlock(); } -#ifndef USE_EPOLL private: virtual void RunDeferred() override { - DoRun(); - } - -#else -public: -#endif - void DoRun() { assert(!done); f(); diff --git a/src/event/Call.hxx b/src/event/Call.hxx index 34d886ca5..808965de1 100644 --- a/src/event/Call.hxx +++ b/src/event/Call.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/event/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx index 62edb7817..3e824012f 100644 --- a/src/event/DeferredMonitor.cxx +++ b/src/event/DeferredMonitor.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 @@ -24,66 +24,11 @@ void DeferredMonitor::Cancel() { -#ifdef USE_EPOLL - pending = false; -#else - const ScopeLock protect(mutex); - if (source_id != 0) { - g_source_remove(source_id); - source_id = 0; - } -#endif + loop.RemoveDeferred(*this); } void DeferredMonitor::Schedule() { -#ifdef USE_EPOLL - if (!pending.exchange(true)) - fd.Write(); -#else - const ScopeLock protect(mutex); - if (source_id == 0) - source_id = loop.AddIdle(Callback, this); -#endif + loop.AddDeferred(*this); } - -#ifdef USE_EPOLL - -bool -DeferredMonitor::OnSocketReady(unsigned) -{ - fd.Read(); - - if (pending.exchange(false)) - RunDeferred(); - - return true; -} - -#else - -void -DeferredMonitor::Run() -{ - { - const ScopeLock protect(mutex); - if (source_id == 0) - /* cancelled */ - return; - - source_id = 0; - } - - RunDeferred(); -} - -gboolean -DeferredMonitor::Callback(gpointer data) -{ - DeferredMonitor &monitor = *(DeferredMonitor *)data; - monitor.Run(); - return false; -} - -#endif diff --git a/src/event/DeferredMonitor.hxx b/src/event/DeferredMonitor.hxx index 2ac832a0a..3d3ab22b7 100644 --- a/src/event/DeferredMonitor.hxx +++ b/src/event/DeferredMonitor.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 @@ -23,64 +23,31 @@ #include "check.h" #include "Compiler.h" -#ifdef USE_EPOLL -#include "SocketMonitor.hxx" -#include "WakeFD.hxx" -#else -#include "thread/Mutex.hxx" -#include <glib.h> -#endif - #include <atomic> class EventLoop; /** * Defer execution of an event into an #EventLoop. + * + * This class is thread-safe. */ -class DeferredMonitor -#ifdef USE_EPOLL - : private SocketMonitor -#endif -{ -#ifdef USE_EPOLL - std::atomic_bool pending; - WakeFD fd; -#else +class DeferredMonitor { EventLoop &loop; - Mutex mutex; - - guint source_id; -#endif + friend class EventLoop; + bool pending; public: -#ifdef USE_EPOLL DeferredMonitor(EventLoop &_loop) - :SocketMonitor(_loop), pending(false) { - SocketMonitor::Open(fd.Get()); - SocketMonitor::Schedule(SocketMonitor::READ); - } -#else - DeferredMonitor(EventLoop &_loop) - :loop(_loop), source_id(0) {} -#endif + :loop(_loop), pending(false) {} ~DeferredMonitor() { -#ifdef USE_EPOLL - /* avoid closing the WakeFD twice */ - SocketMonitor::Steal(); -#else Cancel(); -#endif } EventLoop &GetEventLoop() { -#ifdef USE_EPOLL - return SocketMonitor::GetEventLoop(); -#else return loop; -#endif } void Schedule(); @@ -88,14 +55,6 @@ public: protected: virtual void RunDeferred() = 0; - -private: -#ifdef USE_EPOLL - virtual bool OnSocketReady(unsigned flags) override final; -#else - void Run(); - static gboolean Callback(gpointer data); -#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx index 8b57b1308..457add2b0 100644 --- a/src/event/FullyBufferedSocket.cxx +++ b/src/event/FullyBufferedSocket.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 @@ -20,9 +20,9 @@ #include "config.h" #include "FullyBufferedSocket.hxx" #include "system/SocketError.hxx" -#include "util/fifo_buffer.h" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Compiler.h" #include <assert.h> #include <stdint.h> @@ -59,15 +59,14 @@ FullyBufferedSocket::Flush() { assert(IsDefined()); - size_t length; - const void *data = output.Read(&length); - if (data == nullptr) { + const auto data = output.Read(); + if (data.IsEmpty()) { IdleMonitor::Cancel(); CancelWrite(); return true; } - auto nbytes = DirectWrite(data, length); + auto nbytes = DirectWrite(data.data, data.size); if (gcc_unlikely(nbytes <= 0)) return nbytes == 0; diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx index c50bb5f61..b03152be2 100644 --- a/src/event/FullyBufferedSocket.hxx +++ b/src/event/FullyBufferedSocket.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 @@ -24,7 +24,6 @@ #include "BufferedSocket.hxx" #include "IdleMonitor.hxx" #include "util/PeakBuffer.hxx" -#include "Compiler.h" /** * A #BufferedSocket specialization that adds an output buffer. diff --git a/src/event/IdleMonitor.cxx b/src/event/IdleMonitor.cxx index c99c66b26..4af656a22 100644 --- a/src/event/IdleMonitor.cxx +++ b/src/event/IdleMonitor.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 @@ -21,38 +21,31 @@ #include "IdleMonitor.hxx" #include "Loop.hxx" +#include <assert.h> + void IdleMonitor::Cancel() { - assert(loop.IsInside()); + assert(loop.IsInsideOrNull()); if (!IsActive()) return; -#ifdef USE_EPOLL active = false; loop.RemoveIdle(*this); -#else - g_source_remove(source_id); - source_id = 0; -#endif } void IdleMonitor::Schedule() { - assert(loop.IsInside()); + assert(loop.IsInsideOrVirgin()); if (IsActive()) /* already scheduled */ return; -#ifdef USE_EPOLL active = true; loop.AddIdle(*this); -#else - source_id = loop.AddIdle(Callback, this); -#endif } void @@ -60,25 +53,8 @@ IdleMonitor::Run() { assert(loop.IsInside()); -#ifdef USE_EPOLL assert(active); active = false; -#else - assert(source_id != 0); - source_id = 0; -#endif OnIdle(); } - -#ifndef USE_EPOLL - -gboolean -IdleMonitor::Callback(gpointer data) -{ - IdleMonitor &monitor = *(IdleMonitor *)data; - monitor.Run(); - return false; -} - -#endif diff --git a/src/event/IdleMonitor.hxx b/src/event/IdleMonitor.hxx index c8e79eb1d..65aaa38cf 100644 --- a/src/event/IdleMonitor.hxx +++ b/src/event/IdleMonitor.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 @@ -22,41 +22,35 @@ #include "check.h" -#ifndef USE_EPOLL -#include <glib.h> -#endif - class EventLoop; /** * An event that runs when the EventLoop has become idle, before * waiting for more events. This class is not thread-safe; all * methods must be run from EventLoop's thread. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. */ class IdleMonitor { -#ifdef USE_EPOLL friend class EventLoop; -#endif EventLoop &loop; -#ifdef USE_EPOLL bool active; -#else - guint source_id; -#endif public: -#ifdef USE_EPOLL IdleMonitor(EventLoop &_loop) :loop(_loop), active(false) {} -#else - IdleMonitor(EventLoop &_loop) - :loop(_loop), source_id(0) {} -#endif ~IdleMonitor() { - Cancel(); +#ifndef NDEBUG + /* this check is redundant, it is only here to avoid + the assertion in Cancel() */ + if (IsActive()) +#endif + Cancel(); } EventLoop &GetEventLoop() const { @@ -64,11 +58,7 @@ public: } bool IsActive() const { -#ifdef USE_EPOLL return active; -#else - return source_id != 0; -#endif } void Schedule(); @@ -79,9 +69,6 @@ protected: private: void Run(); -#ifndef USE_EPOLL - static gboolean Callback(gpointer data); -#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx index 5aa24aea2..4ded68ff4 100644 --- a/src/event/Loop.cxx +++ b/src/event/Loop.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 @@ -20,20 +20,21 @@ #include "config.h" #include "Loop.hxx" -#ifdef USE_EPOLL - #include "system/Clock.hxx" #include "TimeoutMonitor.hxx" #include "SocketMonitor.hxx" #include "IdleMonitor.hxx" +#include "DeferredMonitor.hxx" #include <algorithm> -EventLoop::EventLoop(Default) +EventLoop::EventLoop() :SocketMonitor(*this), now_ms(::MonotonicClockMS()), - quit(false), - n_events(0), + quit(false), busy(true), +#ifndef NDEBUG + virgin(true), +#endif thread(ThreadId::Null()) { SocketMonitor::Open(wake_fd.Get()); @@ -45,45 +46,51 @@ EventLoop::~EventLoop() assert(idle.empty()); assert(timers.empty()); - /* avoid closing the WakeFD twice */ - SocketMonitor::Steal(); + /* this is necessary to get a well-defined destruction + order */ + SocketMonitor::Cancel(); } void EventLoop::Break() { - if (IsInside()) - quit = true; - else - AddCall([this]() { Break(); }); + quit = true; + wake_fd.Write(); } -void -EventLoop::Abandon(SocketMonitor &m) +bool +EventLoop::Abandon(int _fd, SocketMonitor &m) { - for (unsigned i = 0, n = n_events; i < n; ++i) - if (events[i].data.ptr == &m) - events[i].events = 0; + assert(IsInsideOrVirgin()); + + poll_result.Clear(&m); + return poll_group.Abandon(_fd); } bool EventLoop::RemoveFD(int _fd, SocketMonitor &m) { - Abandon(m); - return epoll.Remove(_fd); + assert(IsInsideOrNull()); + + poll_result.Clear(&m); + return poll_group.Remove(_fd); } void EventLoop::AddIdle(IdleMonitor &i) { + assert(IsInsideOrVirgin()); assert(std::find(idle.begin(), idle.end(), &i) == idle.end()); idle.push_back(&i); + again = true; } void EventLoop::RemoveIdle(IdleMonitor &i) { + assert(IsInsideOrVirgin()); + auto it = std::find(idle.begin(), idle.end(), &i); assert(it != idle.end()); @@ -93,12 +100,19 @@ EventLoop::RemoveIdle(IdleMonitor &i) void EventLoop::AddTimer(TimeoutMonitor &t, unsigned ms) { + /* can't use IsInsideOrVirgin() here because libavahi-client + modifies the timeout during avahi_client_free() */ + assert(IsInsideOrNull()); + timers.insert(TimerRecord(t, now_ms + ms)); + again = true; } void EventLoop::CancelTimer(TimeoutMonitor &t) { + assert(IsInsideOrNull()); + for (auto i = timers.begin(), end = timers.end(); i != end; ++i) { if (&i->timer == &t) { timers.erase(i); @@ -107,19 +121,24 @@ EventLoop::CancelTimer(TimeoutMonitor &t) } } -#endif - void EventLoop::Run() { assert(thread.IsNull()); + assert(virgin); + +#ifndef NDEBUG + virgin = false; +#endif + thread = ThreadId::GetCurrent(); -#ifdef USE_EPOLL assert(!quit); + assert(busy); do { now_ms = ::MonotonicClockMS(); + again = false; /* invoke timers */ @@ -146,7 +165,6 @@ EventLoop::Run() /* invoke idle */ - const bool idle_empty = idle.empty(); while (!idle.empty()) { IdleMonitor &m = *idle.front(); idle.pop_front(); @@ -156,7 +174,14 @@ EventLoop::Run() return; } - if (!idle_empty) + /* try to handle DeferredMonitors without WakeFD + overhead */ + mutex.lock(); + HandleDeferred(); + busy = false; + mutex.unlock(); + + if (again) /* re-evaluate timers because one of the IdleMonitors may have added a new timeout */ @@ -164,101 +189,107 @@ EventLoop::Run() /* wait for new event */ - const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms); - n_events = std::max(n, 0); + poll_group.ReadEvents(poll_result, timeout_ms); now_ms = ::MonotonicClockMS(); - assert(!quit); + mutex.lock(); + busy = true; + mutex.unlock(); /* invoke sockets */ - - for (int i = 0; i < n; ++i) { - const auto &e = events[i]; - - if (e.events != 0) { - SocketMonitor &m = *(SocketMonitor *)e.data.ptr; - m.Dispatch(e.events); - + for (int i = 0; i < poll_result.GetSize(); ++i) { + auto events = poll_result.GetEvents(i); + if (events != 0) { if (quit) break; + + auto m = (SocketMonitor *)poll_result.GetObject(i); + m->Dispatch(events); } } - n_events = 0; + poll_result.Reset(); + } while (!quit); -#else - g_main_loop_run(loop); -#endif +#ifndef NDEBUG + assert(busy); assert(thread.IsInside()); + thread = ThreadId::Null(); +#endif } -#ifdef USE_EPOLL - void -EventLoop::AddCall(std::function<void()> &&f) +EventLoop::AddDeferred(DeferredMonitor &d) { mutex.lock(); - calls.push_back(f); + if (d.pending) { + mutex.unlock(); + return; + } + + assert(std::find(deferred.begin(), + deferred.end(), &d) == deferred.end()); + + /* we don't need to wake up the EventLoop if another + DeferredMonitor has already done it */ + const bool must_wake = !busy && deferred.empty(); + + d.pending = true; + deferred.push_back(&d); + again = true; mutex.unlock(); - wake_fd.Write(); + if (must_wake) + wake_fd.Write(); } -bool -EventLoop::OnSocketReady(gcc_unused unsigned flags) +void +EventLoop::RemoveDeferred(DeferredMonitor &d) { - assert(!quit); + const ScopeLock protect(mutex); - wake_fd.Read(); + if (!d.pending) { + assert(std::find(deferred.begin(), + deferred.end(), &d) == deferred.end()); + return; + } - mutex.lock(); + d.pending = false; - while (!calls.empty() && !quit) { - auto f = std::move(calls.front()); - calls.pop_front(); + auto i = std::find(deferred.begin(), deferred.end(), &d); + assert(i != deferred.end()); + + deferred.erase(i); +} + +void +EventLoop::HandleDeferred() +{ + while (!deferred.empty() && !quit) { + DeferredMonitor &m = *deferred.front(); + assert(m.pending); + + deferred.pop_front(); + m.pending = false; mutex.unlock(); - f(); + m.RunDeferred(); mutex.lock(); } - - mutex.unlock(); - - return true; } -#else - -guint -EventLoop::AddIdle(GSourceFunc function, gpointer data) +bool +EventLoop::OnSocketReady(gcc_unused unsigned flags) { - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, nullptr); - guint id = g_source_attach(source, GetContext()); - g_source_unref(source); - return id; -} + assert(IsInside()); -GSource * -EventLoop::AddTimeout(guint interval_ms, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new(interval_ms); - g_source_set_callback(source, function, data, nullptr); - g_source_attach(source, GetContext()); - return source; -} + wake_fd.Read(); -GSource * -EventLoop::AddTimeoutSeconds(guint interval_s, - GSourceFunc function, gpointer data) -{ - GSource *source = g_timeout_source_new_seconds(interval_s); - g_source_set_callback(source, function, data, nullptr); - g_source_attach(source, GetContext()); - return source; -} + mutex.lock(); + HandleDeferred(); + mutex.unlock(); -#endif + return true; +} diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx index 62e733747..56804dc81 100644 --- a/src/event/Loop.hxx +++ b/src/event/Loop.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 @@ -24,33 +24,32 @@ #include "thread/Id.hxx" #include "Compiler.h" -#ifdef USE_EPOLL -#include "system/EPollFD.hxx" +#include "PollGroup.hxx" #include "thread/Mutex.hxx" #include "WakeFD.hxx" #include "SocketMonitor.hxx" -#include <functional> #include <list> #include <set> -#else -#include <glib.h> -#endif -#ifdef USE_EPOLL class TimeoutMonitor; class IdleMonitor; +class DeferredMonitor; class SocketMonitor; -#endif #include <assert.h> -class EventLoop final -#ifdef USE_EPOLL - : private SocketMonitor -#endif +/** + * An event loop that polls for events on file/socket descriptors. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs it, except where explicitly documented as + * thread-safe. + * + * @see SocketMonitor, MultiSocketMonitor, TimeoutMonitor, IdleMonitor + */ +class EventLoop final : SocketMonitor { -#ifdef USE_EPOLL struct TimerRecord { /** * Projected monotonic_clock_ms() value when this @@ -73,52 +72,78 @@ class EventLoop final } }; - EPollFD epoll; - WakeFD wake_fd; std::multiset<TimerRecord> timers; std::list<IdleMonitor *> idle; Mutex mutex; - std::list<std::function<void()>> calls; + std::list<DeferredMonitor *> deferred; unsigned now_ms; bool quit; - static constexpr unsigned MAX_EVENTS = 16; - unsigned n_events; - epoll_event events[MAX_EVENTS]; -#else - GMainContext *context; - GMainLoop *loop; + /** + * True when the object has been modified and another check is + * necessary before going to sleep via PollGroup::ReadEvents(). + */ + bool again; + + /** + * True when handling callbacks, false when waiting for I/O or + * timeout. + * + * Protected with #mutex. + */ + bool busy; + +#ifndef NDEBUG + /** + * True if Run() was never called. This is used for assert() + * calls. + */ + bool virgin; #endif + PollGroup poll_group; + PollResult poll_result; + /** * A reference to the thread that is currently inside Run(). */ ThreadId thread; public: -#ifdef USE_EPOLL - struct Default {}; - - EventLoop(Default dummy=Default()); + EventLoop(); ~EventLoop(); + /** + * A caching wrapper for MonotonicClockMS(). + */ unsigned GetTimeMS() const { + assert(IsInside()); + return now_ms; } + /** + * Stop execution of this #EventLoop at the next chance. This + * method is thread-safe and non-blocking: after returning, it + * is not guaranteed that the EventLoop has really stopped. + */ void Break(); bool AddFD(int _fd, unsigned flags, SocketMonitor &m) { - return epoll.Add(_fd, flags, &m); + assert(thread.IsNull() || thread.IsInside()); + + return poll_group.Add(_fd, flags, &m); } bool ModifyFD(int _fd, unsigned flags, SocketMonitor &m) { - return epoll.Modify(_fd, flags, &m); + assert(IsInside()); + + return poll_group.Modify(_fd, flags, &m); } /** @@ -126,7 +151,7 @@ public: * has been closed. This is like RemoveFD(), but does not * attempt to use #EPOLL_CTL_DEL. */ - void Abandon(SocketMonitor &m); + bool Abandon(int fd, SocketMonitor &m); bool RemoveFD(int fd, SocketMonitor &m); @@ -136,53 +161,38 @@ public: void AddTimer(TimeoutMonitor &t, unsigned ms); void CancelTimer(TimeoutMonitor &t); - void AddCall(std::function<void()> &&f); + /** + * Schedule a call to DeferredMonitor::RunDeferred(). + * + * This method is thread-safe. + */ + void AddDeferred(DeferredMonitor &d); + /** + * Cancel a pending call to DeferredMonitor::RunDeferred(). + * However after returning, the call may still be running. + * + * This method is thread-safe. + */ + void RemoveDeferred(DeferredMonitor &d); + + /** + * The main function of this class. It will loop until + * Break() gets called. Can be called only once. + */ void Run(); private: + /** + * Invoke all pending DeferredMonitors. + * + * Caller must lock the mutex. + */ + void HandleDeferred(); + virtual bool OnSocketReady(unsigned flags) override; public: -#else - EventLoop() - :context(g_main_context_new()), - loop(g_main_loop_new(context, false)), - thread(ThreadId::Null()) {} - - struct Default {}; - EventLoop(gcc_unused Default _dummy) - :context(g_main_context_ref(g_main_context_default())), - loop(g_main_loop_new(context, false)), - thread(ThreadId::Null()) {} - - ~EventLoop() { - g_main_loop_unref(loop); - g_main_context_unref(context); - } - - GMainContext *GetContext() { - return context; - } - - void WakeUp() { - g_main_context_wakeup(context); - } - - void Break() { - g_main_loop_quit(loop); - } - - void Run(); - - guint AddIdle(GSourceFunc function, gpointer data); - - GSource *AddTimeout(guint interval_ms, - GSourceFunc function, gpointer data); - - GSource *AddTimeoutSeconds(guint interval_s, - GSourceFunc function, gpointer data); -#endif /** * Are we currently running inside this EventLoop's thread? @@ -193,6 +203,20 @@ public: return thread.IsInside(); } + +#ifndef NDEBUG + gcc_pure + bool IsInsideOrVirgin() const { + return virgin || IsInside(); + } +#endif + +#ifndef NDEBUG + gcc_pure + bool IsInsideOrNull() const { + return thread.IsNull() || thread.IsInside(); + } +#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx index bd1aa6fef..ef77de425 100644 --- a/src/event/MultiSocketMonitor.cxx +++ b/src/event/MultiSocketMonitor.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 @@ -20,12 +20,12 @@ #include "config.h" #include "MultiSocketMonitor.hxx" #include "Loop.hxx" -#include "system/fd_util.h" -#include "Compiler.h" -#include <assert.h> +#include <algorithm> -#ifdef USE_EPOLL +#ifndef WIN32 +#include <poll.h> +#endif MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) :IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) { @@ -37,6 +37,40 @@ MultiSocketMonitor::~MultiSocketMonitor() } void +MultiSocketMonitor::ClearSocketList() +{ + assert(GetEventLoop().IsInsideOrNull()); + + fds.clear(); +} + +#ifndef WIN32 + +void +MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) +{ + pollfd *const end = pfds + n; + + UpdateSocketList([pfds, end](int fd) -> unsigned { + auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ + return pfd.fd == fd; + }); + if (i == end) + return 0; + + auto events = i->events; + i->events = 0; + return events; + }); + + for (auto i = pfds; i != end; ++i) + if (i->events != 0) + AddSocket(i->fd, i->events); +} + +#endif + +void MultiSocketMonitor::Prepare() { int timeout_ms = PrepareSockets(); @@ -64,100 +98,3 @@ MultiSocketMonitor::OnIdle() Prepare(); } } - -#else - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs multi_socket_monitor_source_funcs = { - MultiSocketMonitor::Prepare, - MultiSocketMonitor::Check, - MultiSocketMonitor::Dispatch, - nullptr, - nullptr, - nullptr, -}; - -MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) - :loop(_loop), - source((Source *)g_source_new(&multi_socket_monitor_source_funcs, - sizeof(*source))), - absolute_timeout_us(-1) { - source->monitor = this; - - g_source_attach(&source->base, loop.GetContext()); -} - -MultiSocketMonitor::~MultiSocketMonitor() -{ - g_source_destroy(&source->base); - g_source_unref(&source->base); - source = nullptr; -} - -bool -MultiSocketMonitor::Prepare(gint *timeout_r) -{ - int timeout_ms = *timeout_r = PrepareSockets(); - absolute_timeout_us = timeout_ms < 0 - ? uint64_t(-1) - : GetTime() + uint64_t(timeout_ms) * 1000; - - return false; -} - -bool -MultiSocketMonitor::Check() const -{ - if (GetTime() >= absolute_timeout_us) - return true; - - for (const auto &i : fds) - if (i.GetReturnedEvents() != 0) - return true; - - return false; -} - -/* - * GSource methods - * - */ - -gboolean -MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r) -{ - Source &source = *(Source *)_source; - MultiSocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - return monitor.Prepare(timeout_r); -} - -gboolean -MultiSocketMonitor::Check(GSource *_source) -{ - const Source &source = *(const Source *)_source; - const MultiSocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - return monitor.Check(); -} - -gboolean -MultiSocketMonitor::Dispatch(GSource *_source, - gcc_unused GSourceFunc callback, - gcc_unused gpointer user_data) -{ - Source &source = *(Source *)_source; - MultiSocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - monitor.Dispatch(); - return true; -} - -#endif diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx index 8ee81a508..b40ee8caa 100644 --- a/src/event/MultiSocketMonitor.hxx +++ b/src/event/MultiSocketMonitor.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 @@ -21,40 +21,38 @@ #define MPD_MULTI_SOCKET_MONITOR_HXX #include "check.h" -#include "Compiler.h" - -#ifdef USE_EPOLL #include "IdleMonitor.hxx" #include "TimeoutMonitor.hxx" #include "SocketMonitor.hxx" -#else -#include <glib.h> -#endif +#include "Compiler.h" #include <forward_list> +#include <iterator> #include <assert.h> -#include <stdint.h> #ifdef WIN32 -/* ERRORis a WIN32 macro that poisons our namespace; this is a - kludge to allow us to use it anyway */ +/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge + to allow us to use it anyway */ #ifdef ERROR #undef ERROR #endif #endif +#ifndef WIN32 +struct pollfd; +#endif + class EventLoop; /** - * Monitor multiple sockets. + * Similar to #SocketMonitor, but monitors multiple sockets. To use + * it, implement the methods PrepareSockets() and DispatchSockets(). + * In PrepareSockets(), use UpdateSocketList() and AddSocket(). + * DispatchSockets() will be called if at least one socket is ready. */ -class MultiSocketMonitor -#ifdef USE_EPOLL - : private IdleMonitor, private TimeoutMonitor -#endif +class MultiSocketMonitor : IdleMonitor, TimeoutMonitor { -#ifdef USE_EPOLL class SingleFD final : public SocketMonitor { MultiSocketMonitor &multi; @@ -99,93 +97,45 @@ class MultiSocketMonitor friend class SingleFD; bool ready, refresh; -#else - struct Source { - GSource base; - - MultiSocketMonitor *monitor; - }; - - struct SingleFD { - GPollFD pfd; - - constexpr SingleFD(gcc_unused MultiSocketMonitor &m, - int fd, unsigned events) - :pfd{fd, gushort(events), 0} {} - - constexpr int GetFD() const { - return pfd.fd; - } - - constexpr unsigned GetEvents() const { - return pfd.events; - } - - constexpr unsigned GetReturnedEvents() const { - return pfd.revents; - } - - void SetEvents(unsigned _events) { - pfd.events = _events; - } - }; - - EventLoop &loop; - Source *source; - uint64_t absolute_timeout_us; -#endif std::forward_list<SingleFD> fds; public: -#ifdef USE_EPOLL static constexpr unsigned READ = SocketMonitor::READ; static constexpr unsigned WRITE = SocketMonitor::WRITE; static constexpr unsigned ERROR = SocketMonitor::ERROR; static constexpr unsigned HANGUP = SocketMonitor::HANGUP; -#else - static constexpr unsigned READ = G_IO_IN; - static constexpr unsigned WRITE = G_IO_OUT; - static constexpr unsigned ERROR = G_IO_ERR; - static constexpr unsigned HANGUP = G_IO_HUP; -#endif MultiSocketMonitor(EventLoop &_loop); ~MultiSocketMonitor(); -#ifdef USE_EPOLL using IdleMonitor::GetEventLoop; -#else - EventLoop &GetEventLoop() { - return loop; - } -#endif public: -#ifndef USE_EPOLL - gcc_pure - uint64_t GetTime() const { - return g_source_get_time(&source->base); - } -#endif - + /** + * Invalidate the socket list. A call to PrepareSockets() is + * scheduled which will then update the list. + */ void InvalidateSockets() { -#ifdef USE_EPOLL refresh = true; IdleMonitor::Schedule(); -#else - /* no-op because GLib always calls the GSource's - "prepare" method before each poll() anyway */ -#endif } void AddSocket(int fd, unsigned events) { fds.emplace_front(*this, fd, events); -#ifndef USE_EPOLL - g_source_add_poll(&source->base, &fds.front().pfd); -#endif } + /** + * Remove all sockets. + */ + void ClearSocketList(); + + /** + * Update the known sockets by invoking the given function for + * each one; its return value is the events bit mask. A + * return value of 0 means the socket will be removed from the + * list. + */ template<typename E> void UpdateSocketList(E &&e) { for (auto prev = fds.before_begin(), end = fds.end(), @@ -198,16 +148,19 @@ public: i->SetEvents(events); prev = i; } else { -#ifdef USE_EPOLL - i->Steal(); -#else - g_source_remove_poll(&source->base, &i->pfd); -#endif fds.erase_after(prev); } } } +#ifndef WIN32 + /** + * Replace the socket list with the given file descriptors. + * The given pollfd array will be modified by this method. + */ + void ReplaceSocketList(pollfd *pfds, unsigned n); +#endif + protected: /** * @return timeout [ms] or -1 for no timeout @@ -215,7 +168,6 @@ protected: virtual int PrepareSockets() = 0; virtual void DispatchSockets() = 0; -#ifdef USE_EPOLL private: void SetReady() { ready = true; @@ -230,23 +182,6 @@ private: } virtual void OnIdle() final; - -#else -public: - /* GSource callbacks */ - static gboolean Prepare(GSource *source, gint *timeout_r); - static gboolean Check(GSource *source); - static gboolean Dispatch(GSource *source, GSourceFunc callback, - gpointer user_data); - -private: - bool Prepare(gint *timeout_r); - bool Check() const; - - void Dispatch() { - DispatchSockets(); - } -#endif }; #endif diff --git a/src/event/PollGroup.hxx b/src/event/PollGroup.hxx new file mode 100644 index 000000000..a2f176860 --- /dev/null +++ b/src/event/PollGroup.hxx @@ -0,0 +1,41 @@ +/* + * 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_EVENT_POLLGROUP_HXX +#define MPD_EVENT_POLLGROUP_HXX + +#ifdef USE_EPOLL +#include "PollGroupEPoll.hxx" +typedef PollResultEPoll PollResult; +typedef PollGroupEPoll PollGroup; +#endif + +#ifdef USE_WINSELECT +#include "PollGroupWinSelect.hxx" +typedef PollResultGeneric PollResult; +typedef PollGroupWinSelect PollGroup; +#endif + +#ifdef USE_POLL +#include "PollGroupPoll.hxx" +typedef PollResultGeneric PollResult; +typedef PollGroupPoll PollGroup; +#endif + +#endif diff --git a/src/event/PollGroupEPoll.hxx b/src/event/PollGroupEPoll.hxx new file mode 100644 index 000000000..d8edb8a1f --- /dev/null +++ b/src/event/PollGroupEPoll.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_EVENT_POLLGROUP_EPOLL_HXX +#define MPD_EVENT_POLLGROUP_EPOLL_HXX + +#include "check.h" + +#include "Compiler.h" +#include "system/EPollFD.hxx" + +#include <array> +#include <algorithm> + +class PollResultEPoll +{ + friend class PollGroupEPoll; + + std::array<epoll_event, 16> events; + int n_events; +public: + PollResultEPoll() : n_events(0) { } + + int GetSize() const { return n_events; } + unsigned GetEvents(int i) const { return events[i].events; } + void *GetObject(int i) const { return events[i].data.ptr; } + void Reset() { n_events = 0; } + + void Clear(void *obj) { + for (int i = 0; i < n_events; ++i) + if (events[i].data.ptr == obj) + events[i].events = 0; + } +}; + +class PollGroupEPoll +{ + EPollFD epoll; + + PollGroupEPoll(PollGroupEPoll &) = delete; + PollGroupEPoll &operator=(PollGroupEPoll &) = delete; +public: + static constexpr unsigned READ = EPOLLIN; + static constexpr unsigned WRITE = EPOLLOUT; + static constexpr unsigned ERROR = EPOLLERR; + static constexpr unsigned HANGUP = EPOLLHUP; + + PollGroupEPoll() = default; + + void ReadEvents(PollResultEPoll &result, int timeout_ms) { + int ret = epoll.Wait(result.events.data(), result.events.size(), + timeout_ms); + result.n_events = std::max(0, ret); + } + + bool Add(int fd, unsigned events, void *obj) { + return epoll.Add(fd, events, obj); + } + + bool Modify(int fd, unsigned events, void *obj) { + return epoll.Modify(fd, events, obj); + } + + bool Remove(int fd) { + return epoll.Remove(fd); + } + + bool Abandon(gcc_unused int fd) { + // Nothing to do in this implementation. + // Closed descriptors are automatically unregistered. + return true; + } +}; + +#endif diff --git a/src/event/PollGroupPoll.cxx b/src/event/PollGroupPoll.cxx new file mode 100644 index 000000000..402f8616f --- /dev/null +++ b/src/event/PollGroupPoll.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#ifdef USE_POLL + +#include "PollGroupPoll.hxx" + +#include <assert.h> + +PollGroupPoll::PollGroupPoll() { } +PollGroupPoll::~PollGroupPoll() { } + +bool PollGroupPoll::Add(int fd, unsigned events, void *obj) +{ + assert(items.find(fd) == items.end()); + + const size_t index = poll_events.size(); + poll_events.resize(index + 1); + auto &e = poll_events[index]; + e.fd = fd; + e.events = events; + e.revents = 0; + auto &item = items[fd]; + item.index = index; + item.obj = obj; + return true; +} + +bool PollGroupPoll::Modify(int fd, unsigned events, void *obj) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + item.obj = obj; + auto &e = poll_events[item.index]; + e.events = events; + e.revents &= events; + return true; +} + +bool PollGroupPoll::Remove(int fd) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + size_t index = item.index; + size_t last_index = poll_events.size() - 1; + if (index != last_index) { + std::swap(poll_events[index], poll_events[last_index]); + items[poll_events[index].fd].index = index; + } + poll_events.pop_back(); + items.erase(item_iter); + return true; +} + +void PollGroupPoll::ReadEvents(PollResultGeneric &result, int timeout_ms) +{ + int n = poll(poll_events.empty() ? nullptr : &poll_events[0], + poll_events.size(), timeout_ms); + + for (size_t i = 0; n > 0 && i < poll_events.size(); ++i) { + const auto &e = poll_events[i]; + if (e.revents != 0) { + result.Add(e.revents, items[e.fd].obj); + --n; + } + } +} + +#endif diff --git a/src/event/PollGroupPoll.hxx b/src/event/PollGroupPoll.hxx new file mode 100644 index 000000000..f7a3ccb4f --- /dev/null +++ b/src/event/PollGroupPoll.hxx @@ -0,0 +1,63 @@ +/* + * 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_EVENT_POLLGROUP_POLL_HXX +#define MPD_EVENT_POLLGROUP_POLL_HXX + +#include "check.h" +#include "PollResultGeneric.hxx" + +#include <vector> +#include <unordered_map> + +#include <stddef.h> +#include <sys/poll.h> + +class PollGroupPoll +{ + struct Item + { + size_t index; + void *obj; + }; + + std::vector<pollfd> poll_events; + std::unordered_map<int, Item> items; + + PollGroupPoll(PollGroupPoll &) = delete; + PollGroupPoll &operator=(PollGroupPoll &) = delete; +public: + static constexpr unsigned READ = POLLIN; + static constexpr unsigned WRITE = POLLOUT; + static constexpr unsigned ERROR = POLLERR; + static constexpr unsigned HANGUP = POLLHUP; + + PollGroupPoll(); + ~PollGroupPoll(); + + void ReadEvents(PollResultGeneric &result, int timeout_ms); + bool Add(int fd, unsigned events, void *obj); + bool Modify(int fd, unsigned events, void *obj); + bool Remove(int fd); + bool Abandon(int fd) { + return Remove(fd); + } +}; + +#endif diff --git a/src/event/PollGroupWinSelect.cxx b/src/event/PollGroupWinSelect.cxx new file mode 100644 index 000000000..26c8abd46 --- /dev/null +++ b/src/event/PollGroupWinSelect.cxx @@ -0,0 +1,161 @@ +/* + * 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" + +#ifdef USE_WINSELECT + +#include "PollGroupWinSelect.hxx" + +constexpr int EVENT_READ = 0; +constexpr int EVENT_WRITE = 1; + +static inline bool HasEvent(unsigned events, int event_id) +{ + return (events & (1 << event_id)) != 0; +} + +PollGroupWinSelect::PollGroupWinSelect() { } +PollGroupWinSelect::~PollGroupWinSelect() { } + +bool PollGroupWinSelect::CanModify(PollGroupWinSelect::Item &item, + unsigned events, int event_id) +{ + if (item.index[event_id] < 0 && HasEvent(events, event_id)) + return !event_set[event_id].IsFull(); + return true; +} + +void PollGroupWinSelect::Modify(PollGroupWinSelect::Item &item, int fd, + unsigned events, int event_id) +{ + int index = item.index[event_id]; + auto &set = event_set[event_id]; + + if (index < 0 && HasEvent(events, event_id)) + item.index[event_id] = set.Add(fd); + else if (index >= 0 && !HasEvent(events, event_id)) { + if (index != set.Size() - 1) { + set.MoveToEnd(index); + items[set[index]].index[event_id] = index; + } + set.RemoveLast(); + item.index[event_id] = -1; + } +} + +bool PollGroupWinSelect::Add(int fd, unsigned events, void *obj) +{ + assert(items.find(fd) == items.end()); + auto &item = items[fd]; + + item.index[EVENT_READ] = -1; + item.index[EVENT_WRITE] = -1; + item.obj = obj; + item.events = 0; + + if (!CanModify(item, events, EVENT_READ)) { + items.erase(fd); + return false; + } + if (!CanModify(item, events, EVENT_WRITE)) { + items.erase(fd); + return false; + } + + Modify(item, fd, events, EVENT_READ); + Modify(item, fd, events, EVENT_WRITE); + return true; +} + +bool PollGroupWinSelect::Modify(int fd, unsigned events, void *obj) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + + if (!CanModify(item, events, EVENT_READ)) + return false; + if (!CanModify(item, events, EVENT_WRITE)) + return false; + + item.obj = obj; + Modify(item, fd, events, EVENT_READ); + Modify(item, fd, events, EVENT_WRITE); + return true; +} + +bool PollGroupWinSelect::Remove(int fd) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + + Modify(item, fd, 0, EVENT_READ); + Modify(item, fd, 0, EVENT_WRITE); + items.erase(item_iter); + return true; +} + +void PollGroupWinSelect::ReadEvents(PollResultGeneric &result, int timeout_ms) +{ + bool use_sleep = event_set[EVENT_READ].IsEmpty() && + event_set[EVENT_WRITE].IsEmpty(); + + if (use_sleep) { + Sleep(timeout_ms < 0 ? INFINITE : (DWORD) timeout_ms); + return; + } + + SocketSet read_set(event_set[EVENT_READ]); + SocketSet write_set(event_set[EVENT_WRITE]); + SocketSet except_set(event_set[EVENT_WRITE]); + + timeval tv; + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + } + + int ret = select(0, + read_set.IsEmpty() ? nullptr : read_set.GetPtr(), + write_set.IsEmpty() ? nullptr : write_set.GetPtr(), + except_set.IsEmpty() ? nullptr : except_set.GetPtr(), + timeout_ms < 0 ? nullptr : &tv); + + if (ret == 0 || ret == SOCKET_ERROR) + return; + + for (int i = 0; i < read_set.Size(); ++i) + items[read_set[i]].events |= READ; + + for (int i = 0; i < write_set.Size(); ++i) + items[write_set[i]].events |= WRITE; + + for (int i = 0; i < except_set.Size(); ++i) + items[except_set[i]].events |= WRITE; + + for (auto i = items.begin(); i != items.end(); ++i) + if (i->second.events != 0) { + result.Add(i->second.events, i->second.obj); + i->second.events = 0; + } +} + +#endif diff --git a/src/event/PollGroupWinSelect.hxx b/src/event/PollGroupWinSelect.hxx new file mode 100644 index 000000000..d01067709 --- /dev/null +++ b/src/event/PollGroupWinSelect.hxx @@ -0,0 +1,111 @@ +/* + * 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_EVENT_POLLGROUP_WINSELECT_HXX +#define MPD_EVENT_POLLGROUP_WINSELECT_HXX + +#include "check.h" + +#include "PollResultGeneric.hxx" + +#include <assert.h> +#include <string.h> + +#include <unordered_map> + +#include <windows.h> +#include <winsock2.h> + +#ifdef ERROR +#undef ERROR +#endif + +class SocketSet +{ + fd_set set; +public: + SocketSet() { set.fd_count = 0; } + SocketSet(SocketSet &other) { + set.fd_count = other.set.fd_count; + memcpy(set.fd_array, + other.set.fd_array, + sizeof (SOCKET) * set.fd_count); + } + + fd_set *GetPtr() { return &set; } + int Size() { return set.fd_count; } + bool IsEmpty() { return set.fd_count == 0; } + bool IsFull() { return set.fd_count == FD_SETSIZE; } + + int operator[](int index) { + assert(index >= 0 && (u_int)index < set.fd_count); + return set.fd_array[index]; + } + + int Add(int fd) { + assert(!IsFull()); + set.fd_array[set.fd_count] = fd; + return set.fd_count++; + } + + void MoveToEnd(int index) { + assert(index >= 0 && (u_int)index < set.fd_count); + std::swap(set.fd_array[index], set.fd_array[set.fd_count - 1]); + } + + void RemoveLast() { + assert(!IsEmpty()); + --set.fd_count; + } +}; + +class PollGroupWinSelect +{ + struct Item + { + int index[2]; + void *obj; + unsigned events; + }; + + SocketSet event_set[2]; + std::unordered_map<int, Item> items; + + bool CanModify(Item &item, unsigned events, int event_id); + void Modify(Item &item, int fd, unsigned events, int event_id); + + PollGroupWinSelect(PollGroupWinSelect &) = delete; + PollGroupWinSelect &operator=(PollGroupWinSelect &) = delete; +public: + static constexpr unsigned READ = 1; + static constexpr unsigned WRITE = 2; + static constexpr unsigned ERROR = 0; + static constexpr unsigned HANGUP = 0; + + PollGroupWinSelect(); + ~PollGroupWinSelect(); + + void ReadEvents(PollResultGeneric &result, int timeout_ms); + bool Add(int fd, unsigned events, void *obj); + bool Modify(int fd, unsigned events, void *obj); + bool Remove(int fd); + bool Abandon(int fd) { return Remove(fd); } +}; + +#endif diff --git a/src/event/PollResultGeneric.hxx b/src/event/PollResultGeneric.hxx new file mode 100644 index 000000000..35daf7f08 --- /dev/null +++ b/src/event/PollResultGeneric.hxx @@ -0,0 +1,57 @@ +/* + * 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_EVENT_POLLRESULT_GENERIC_HXX +#define MPD_EVENT_POLLRESULT_GENERIC_HXX + +#include "check.h" + +#include <vector> + +class PollResultGeneric +{ + struct Item + { + unsigned events; + void *obj; + + Item() = default; + Item(unsigned _events, void *_obj) + : events(_events), obj(_obj) { } + }; + + std::vector<Item> items; +public: + int GetSize() const { return items.size(); } + unsigned GetEvents(int i) const { return items[i].events; } + void *GetObject(int i) const { return items[i].obj; } + void Reset() { items.clear(); } + + void Clear(void *obj) { + for (auto i = items.begin(); i != items.end(); ++i) + if (i->obj == obj) + i->events = 0; + } + + void Add(unsigned events, void *obj) { + items.emplace_back(events, obj); + } +}; + +#endif diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx index 781d29181..ce70a969b 100644 --- a/src/event/ServerSocket.cxx +++ b/src/event/ServerSocket.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 @@ -18,11 +18,6 @@ */ #include "config.h" - -#ifdef HAVE_STRUCT_UCRED -#define _GNU_SOURCE 1 -#endif - #include "ServerSocket.hxx" #include "system/SocketUtil.hxx" #include "system/SocketError.hxx" @@ -31,13 +26,13 @@ #include "system/fd_util.h" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" +#include "util/Alloc.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <string> +#include <algorithm> #include <sys/types.h> #include <sys/stat.h> @@ -78,7 +73,7 @@ public: parent(_parent), serial(_serial), path(AllocatedPath::Null()), address_length(_address_length), - address((sockaddr *)g_memdup(_address, _address_length)) + address((sockaddr *)xmemdup(_address, _address_length)) { assert(_address != nullptr); assert(_address_length > 0); @@ -88,7 +83,10 @@ public: OneServerSocket &operator=(const OneServerSocket &other) = delete; ~OneServerSocket() { - g_free(address); + free(address); + + if (IsDefined()) + Close(); } unsigned GetSerial() const { @@ -106,7 +104,10 @@ public: using SocketMonitor::IsDefined; using SocketMonitor::Close; - char *ToString() const; + gcc_pure + std::string ToString() const { + return sockaddr_to_string(address, address_length); + } void SetFD(int _fd) { SocketMonitor::Open(_fd); @@ -121,18 +122,6 @@ private: static constexpr Domain server_socket_domain("server_socket"); -/** - * Wraper for sockaddr_to_string() which never fails. - */ -char * -OneServerSocket::ToString() const -{ - char *p = sockaddr_to_string(address, address_length, IgnoreError()); - if (p == nullptr) - p = g_strdup("[unknown]"); - return p; -} - static int get_remote_uid(int fd) { @@ -242,23 +231,21 @@ ServerSocket::Open(Error &error) Error error2; if (!i.Open(error2)) { if (good != nullptr && good->GetSerial() == i.GetSerial()) { - char *address_string = i.ToString(); - char *good_string = good->ToString(); + const auto address_string = i.ToString(); + const auto good_string = good->ToString(); FormatWarning(server_socket_domain, "bind to '%s' failed: %s " "(continuing anyway, because " "binding to '%s' succeeded)", - address_string, error2.GetMessage(), - good_string); - g_free(address_string); - g_free(good_string); + address_string.c_str(), + error2.GetMessage(), + good_string.c_str()); } else if (bad == nullptr) { bad = &i; - char *address_string = i.ToString(); + const auto address_string = i.ToString(); error2.FormatPrefix("Failed to bind to '%s': ", - address_string); - g_free(address_string); + address_string.c_str()); last_error = std::move(error2); } diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx index facb10371..4c3fd9f1d 100644 --- a/src/event/ServerSocket.hxx +++ b/src/event/ServerSocket.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 @@ -36,6 +36,9 @@ typedef void (*server_socket_callback_t)(int fd, class OneServerSocket; +/** + * A socket that accepts incoming stream connections (e.g. TCP). + */ class ServerSocket { friend class OneServerSocket; diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx index 4f5174377..2d8fe681f 100644 --- a/src/event/SignalMonitor.cxx +++ b/src/event/SignalMonitor.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 @@ -43,6 +43,7 @@ #include <pthread.h> #endif +#include <assert.h> #include <signal.h> class SignalMonitor final : private SocketMonitor { @@ -61,14 +62,6 @@ public: #endif } - ~SignalMonitor() { - /* prevent the descriptor to be closed twice */ -#ifdef USE_SIGNALFD - if (SocketMonitor::IsDefined()) -#endif - SocketMonitor::Steal(); - } - using SocketMonitor::GetEventLoop; #ifdef USE_SIGNALFD diff --git a/src/event/SignalMonitor.hxx b/src/event/SignalMonitor.hxx index 1ecccd40b..a41e57ef9 100644 --- a/src/event/SignalMonitor.hxx +++ b/src/event/SignalMonitor.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/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx index 2b97059f7..69207287d 100644 --- a/src/event/SocketMonitor.cxx +++ b/src/event/SocketMonitor.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 @@ -28,12 +28,9 @@ #ifdef WIN32 #include <winsock2.h> #else -#include <sys/types.h> #include <sys/socket.h> #endif -#ifdef USE_EPOLL - void SocketMonitor::Dispatch(unsigned flags) { @@ -43,93 +40,19 @@ SocketMonitor::Dispatch(unsigned flags) Cancel(); } -#else - -/* - * GSource methods - * - */ - -gboolean -SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r) -{ - return false; -} - -gboolean -SocketMonitor::Check(GSource *_source) -{ - const Source &source = *(const Source *)_source; - const SocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - return monitor.Check(); -} - -gboolean -SocketMonitor::Dispatch(GSource *_source, - gcc_unused GSourceFunc callback, - gcc_unused gpointer user_data) -{ - Source &source = *(Source *)_source; - SocketMonitor &monitor = *source.monitor; - assert(_source == &monitor.source->base); - - monitor.Dispatch(); - return true; -} - -/** - * The vtable for our GSource implementation. Unfortunately, we - * cannot declare it "const", because g_source_new() takes a non-const - * pointer, for whatever reason. - */ -static GSourceFuncs socket_monitor_source_funcs = { - SocketMonitor::Prepare, - SocketMonitor::Check, - SocketMonitor::Dispatch, - nullptr, - nullptr, - nullptr, -}; - -SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop) - :fd(-1), loop(_loop), - source(nullptr) { - assert(_fd >= 0); - - Open(_fd); -} - -#endif - SocketMonitor::~SocketMonitor() { if (IsDefined()) - Close(); + Cancel(); } void SocketMonitor::Open(int _fd) { assert(fd < 0); -#ifndef USE_EPOLL - assert(source == nullptr); -#endif assert(_fd >= 0); fd = _fd; - -#ifndef USE_EPOLL - poll = {fd, 0, 0}; - - source = (Source *)g_source_new(&socket_monitor_source_funcs, - sizeof(*source)); - source->monitor = this; - - g_source_attach(&source->base, loop.GetContext()); - g_source_add_poll(&source->base, &poll); -#endif } int @@ -142,12 +65,6 @@ SocketMonitor::Steal() int result = fd; fd = -1; -#ifndef USE_EPOLL - g_source_destroy(&source->base); - g_source_unref(&source->base); - source = nullptr; -#endif - return result; } @@ -156,12 +73,9 @@ SocketMonitor::Abandon() { assert(IsDefined()); -#ifdef USE_EPOLL + int old_fd = fd; fd = -1; - loop.Abandon(*this); -#else - Steal(); -#endif + loop.Abandon(old_fd, *this); } void @@ -178,7 +92,6 @@ SocketMonitor::Schedule(unsigned flags) if (flags == GetScheduledFlags()) return; -#ifdef USE_EPOLL if (scheduled_flags == 0) loop.AddFD(fd, flags, *this); else if (flags == 0) @@ -187,12 +100,6 @@ SocketMonitor::Schedule(unsigned flags) loop.ModifyFD(fd, flags, *this); scheduled_flags = flags; -#else - poll.events = flags; - poll.revents &= flags; - - loop.WakeUp(); -#endif } SocketMonitor::ssize_t diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx index 5369ddb8a..56d4273f0 100644 --- a/src/event/SocketMonitor.hxx +++ b/src/event/SocketMonitor.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 @@ -21,12 +21,7 @@ #define MPD_SOCKET_MONITOR_HXX #include "check.h" - -#ifdef USE_EPOLL -#include <sys/epoll.h> -#else -#include <glib.h> -#endif +#include "PollGroup.hxx" #include <type_traits> @@ -34,8 +29,8 @@ #include <stddef.h> #ifdef WIN32 -/* ERRORis a WIN32 macro that poisons our namespace; this is a - kludge to allow us to use it anyway */ +/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge + to allow us to use it anyway */ #ifdef ERROR #undef ERROR #endif @@ -43,56 +38,41 @@ class EventLoop; +/** + * Monitor events on a socket. Call Schedule() to announce events + * you're interested in, or Cancel() to cancel your subscription. The + * #EventLoop will invoke virtual method OnSocketReady() as soon as + * any of the subscribed events are ready. + * + * This class does not feel responsible for closing the socket. Call + * Close() to do it manually. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. + */ class SocketMonitor { -#ifdef USE_EPOLL -#else - struct Source { - GSource base; - - SocketMonitor *monitor; - }; -#endif - int fd; EventLoop &loop; -#ifdef USE_EPOLL /** * A bit mask of events that is currently registered in the EventLoop. */ unsigned scheduled_flags; -#else - Source *source; - GPollFD poll; -#endif public: -#ifdef USE_EPOLL - static constexpr unsigned READ = EPOLLIN; - static constexpr unsigned WRITE = EPOLLOUT; - static constexpr unsigned ERROR = EPOLLERR; - static constexpr unsigned HANGUP = EPOLLHUP; -#else - static constexpr unsigned READ = G_IO_IN; - static constexpr unsigned WRITE = G_IO_OUT; - static constexpr unsigned ERROR = G_IO_ERR; - static constexpr unsigned HANGUP = G_IO_HUP; -#endif + static constexpr unsigned READ = PollGroup::READ; + static constexpr unsigned WRITE = PollGroup::WRITE; + static constexpr unsigned ERROR = PollGroup::ERROR; + static constexpr unsigned HANGUP = PollGroup::HANGUP; typedef std::make_signed<size_t>::type ssize_t; -#ifdef USE_EPOLL SocketMonitor(EventLoop &_loop) :fd(-1), loop(_loop), scheduled_flags(0) {} SocketMonitor(int _fd, EventLoop &_loop) :fd(_fd), loop(_loop), scheduled_flags(0) {} -#else - SocketMonitor(EventLoop &_loop) - :fd(-1), loop(_loop), source(nullptr) {} - - SocketMonitor(int _fd, EventLoop &_loop); -#endif ~SocketMonitor(); @@ -114,7 +94,7 @@ public: /** * "Steal" the socket descriptor. This abandons the socket - * and puts the responsibility for closing it to the caller. + * and returns it. */ int Steal(); @@ -128,11 +108,7 @@ public: unsigned GetScheduledFlags() const { assert(IsDefined()); -#ifdef USE_EPOLL return scheduled_flags; -#else - return poll.events; -#endif } void Schedule(unsigned flags); @@ -167,28 +143,7 @@ protected: virtual bool OnSocketReady(unsigned flags) = 0; public: -#ifdef USE_EPOLL void Dispatch(unsigned flags); -#else - /* GSource callbacks */ - static gboolean Prepare(GSource *source, gint *timeout_r); - static gboolean Check(GSource *source); - static gboolean Dispatch(GSource *source, GSourceFunc callback, - gpointer user_data); - -private: - bool Check() const { - assert(IsDefined()); - - return (poll.revents & poll.events) != 0; - } - - void Dispatch() { - assert(IsDefined()); - - OnSocketReady(poll.revents & poll.events); - } -#endif }; #endif diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx index cffad6b92..e04af3e4e 100644 --- a/src/event/TimeoutMonitor.cxx +++ b/src/event/TimeoutMonitor.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 @@ -25,28 +25,19 @@ void TimeoutMonitor::Cancel() { if (IsActive()) { -#ifdef USE_EPOLL active = false; loop.CancelTimer(*this); -#else - g_source_destroy(source); - g_source_unref(source); - source = nullptr; -#endif } } void + TimeoutMonitor::Schedule(unsigned ms) { Cancel(); -#ifdef USE_EPOLL active = true; loop.AddTimer(*this, ms); -#else - source = loop.AddTimeout(ms, Callback, this); -#endif } void @@ -54,31 +45,11 @@ TimeoutMonitor::ScheduleSeconds(unsigned s) { Cancel(); -#ifdef USE_EPOLL Schedule(s * 1000u); -#else - source = loop.AddTimeoutSeconds(s, Callback, this); -#endif } void TimeoutMonitor::Run() { -#ifndef USE_EPOLL - Cancel(); -#endif - OnTimeout(); } - -#ifndef USE_EPOLL - -gboolean -TimeoutMonitor::Callback(gpointer data) -{ - TimeoutMonitor &monitor = *(TimeoutMonitor *)data; - monitor.Run(); - return false; -} - -#endif diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx index 98e4e5564..414d48aa6 100644 --- a/src/event/TimeoutMonitor.hxx +++ b/src/event/TimeoutMonitor.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 @@ -22,34 +22,27 @@ #include "check.h" -#ifndef USE_EPOLL -#include <glib.h> -#endif - class EventLoop; +/** + * This class monitors a timeout. Use Schedule() to begin the timeout + * or Cancel() to cancel it. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. + */ class TimeoutMonitor { -#ifdef USE_EPOLL friend class EventLoop; -#endif EventLoop &loop; -#ifdef USE_EPOLL bool active; -#else - GSource *source; -#endif public: -#ifdef USE_EPOLL TimeoutMonitor(EventLoop &_loop) :loop(_loop), active(false) { } -#else - TimeoutMonitor(EventLoop &_loop) - :loop(_loop), source(nullptr) {} -#endif ~TimeoutMonitor() { Cancel(); @@ -60,11 +53,7 @@ public: } bool IsActive() const { -#ifdef USE_EPOLL return active; -#else - return source != nullptr; -#endif } void Schedule(unsigned ms); @@ -76,10 +65,6 @@ protected: private: void Run(); - -#ifndef USE_EPOLL - static gboolean Callback(gpointer data); -#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx index ed1baafd8..c6222b59c 100644 --- a/src/event/WakeFD.hxx +++ b/src/event/WakeFD.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/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx deleted file mode 100644 index 918a16e53..000000000 --- a/src/filter/AutoConvertFilterPlugin.cxx +++ /dev/null @@ -1,127 +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 "AutoConvertFilterPlugin.hxx" -#include "ConvertFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "AudioFormat.hxx" -#include "ConfigData.hxx" - -#include <assert.h> - -class AutoConvertFilter final : public Filter { - /** - * The underlying filter. - */ - Filter *filter; - - /** - * A convert_filter, just in case conversion is needed. nullptr - * if unused. - */ - Filter *convert; - -public: - AutoConvertFilter(Filter *_filter):filter(_filter) {} - ~AutoConvertFilter() { - delete filter; - } - - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close() override; - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, - Error &error) override; -}; - -AudioFormat -AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error) -{ - assert(in_audio_format.IsValid()); - - /* open the "real" filter */ - - AudioFormat child_audio_format = in_audio_format; - AudioFormat out_audio_format = filter->Open(child_audio_format, error); - if (!out_audio_format.IsDefined()) - return out_audio_format; - - /* need to convert? */ - - if (in_audio_format != child_audio_format) { - /* yes - create a convert_filter */ - - const config_param empty; - convert = filter_new(&convert_filter_plugin, empty, error); - if (convert == nullptr) { - filter->Close(); - return AudioFormat::Undefined(); - } - - AudioFormat audio_format2 = in_audio_format; - AudioFormat audio_format3 = - convert->Open(audio_format2, error); - if (!audio_format3.IsDefined()) { - delete convert; - filter->Close(); - return AudioFormat::Undefined(); - } - - assert(audio_format2 == in_audio_format); - - convert_filter_set(convert, child_audio_format); - } else - /* no */ - convert = nullptr; - - return out_audio_format; -} - -void -AutoConvertFilter::Close() -{ - if (convert != nullptr) { - convert->Close(); - delete convert; - } - - filter->Close(); -} - -const void * -AutoConvertFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) -{ - if (convert != nullptr) { - src = convert->FilterPCM(src, src_size, &src_size, error); - if (src == nullptr) - return nullptr; - } - - return filter->FilterPCM(src, src_size, dest_size_r, error); -} - -Filter * -autoconvert_filter_new(Filter *filter) -{ - return new AutoConvertFilter(filter); -} diff --git a/src/filter/AutoConvertFilterPlugin.hxx b/src/filter/AutoConvertFilterPlugin.hxx deleted file mode 100644 index 7db72a345..000000000 --- a/src/filter/AutoConvertFilterPlugin.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX -#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX - -class Filter; - -/** - * Creates a new "autoconvert" filter. When opened, it ensures that - * the input audio format isn't changed. If the underlying filter - * requests a different format, it automatically creates a - * convert_filter. - */ -Filter * -autoconvert_filter_new(Filter *filter); - -#endif diff --git a/src/filter/ChainFilterPlugin.cxx b/src/filter/ChainFilterPlugin.cxx deleted file mode 100644 index cb52b86ca..000000000 --- a/src/filter/ChainFilterPlugin.cxx +++ /dev/null @@ -1,181 +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 "ChainFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "AudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <list> - -#include <assert.h> - -class ChainFilter final : public Filter { - struct Child { - const char *name; - Filter *filter; - - Child(const char *_name, Filter *_filter) - :name(_name), filter(_filter) {} - ~Child() { - delete filter; - } - - Child(const Child &) = delete; - Child &operator=(const Child &) = delete; - }; - - std::list<Child> children; - -public: - void Append(const char *name, Filter *filter) { - children.emplace_back(name, filter); - } - - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close(); - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error); - -private: - /** - * Close all filters in the chain until #until is reached. - * #until itself is not closed. - */ - void CloseUntil(const Filter *until); -}; - -static constexpr Domain chain_filter_domain("chain_filter"); - -static Filter * -chain_filter_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new ChainFilter(); -} - -void -ChainFilter::CloseUntil(const Filter *until) -{ - for (auto &child : children) { - if (child.filter == until) - /* don't close this filter */ - return; - - /* close this filter */ - child.filter->Close(); - } - - /* this assertion fails if #until does not exist (anymore) */ - assert(false); - gcc_unreachable(); -} - -static AudioFormat -chain_open_child(const char *name, Filter *filter, - const AudioFormat &prev_audio_format, - Error &error) -{ - AudioFormat conv_audio_format = prev_audio_format; - const AudioFormat next_audio_format = - filter->Open(conv_audio_format, error); - if (!next_audio_format.IsDefined()) - return next_audio_format; - - if (conv_audio_format != prev_audio_format) { - struct audio_format_string s; - - filter->Close(); - - error.Format(chain_filter_domain, - "Audio format not supported by filter '%s': %s", - name, - audio_format_to_string(prev_audio_format, &s)); - return AudioFormat::Undefined(); - } - - return next_audio_format; -} - -AudioFormat -ChainFilter::Open(AudioFormat &in_audio_format, Error &error) -{ - AudioFormat audio_format = in_audio_format; - - for (auto &child : children) { - audio_format = chain_open_child(child.name, child.filter, - audio_format, error); - if (!audio_format.IsDefined()) { - /* rollback, close all children */ - CloseUntil(child.filter); - break; - } - } - - /* return the output format of the last filter */ - return audio_format; -} - -void -ChainFilter::Close() -{ - for (auto &child : children) - child.filter->Close(); -} - -const void * -ChainFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) -{ - for (auto &child : children) { - /* feed the output of the previous filter as input - into the current one */ - src = child.filter->FilterPCM(src, src_size, &src_size, - error); - if (src == nullptr) - return nullptr; - } - - /* return the output of the last filter */ - *dest_size_r = src_size; - return src; -} - -const struct filter_plugin chain_filter_plugin = { - "chain", - chain_filter_init, -}; - -Filter * -filter_chain_new(void) -{ - return new ChainFilter(); -} - -void -filter_chain_append(Filter &_chain, const char *name, Filter *filter) -{ - ChainFilter &chain = (ChainFilter &)_chain; - - chain.Append(name, filter); -} diff --git a/src/filter/ChainFilterPlugin.hxx b/src/filter/ChainFilterPlugin.hxx deleted file mode 100644 index 884c7ca19..000000000 --- a/src/filter/ChainFilterPlugin.hxx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A filter chain is a container for several filters. They are - * chained together, i.e. called in a row, one filter passing its - * output to the next one. - */ - -#ifndef MPD_FILTER_CHAIN_HXX -#define MPD_FILTER_CHAIN_HXX - -class Filter; - -/** - * Creates a new filter chain. - */ -Filter * -filter_chain_new(void); - -/** - * Appends a new filter at the end of the filter chain. You must call - * this function before the first filter_open() call. - * - * @param chain the filter chain created with filter_chain_new() - * @param filter the filter to be appended to #chain - */ -void -filter_chain_append(Filter &chain, const char *name, Filter *filter); - -#endif diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx deleted file mode 100644 index 040f8426f..000000000 --- a/src/filter/ConvertFilterPlugin.cxx +++ /dev/null @@ -1,119 +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 "ConvertFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "pcm/PcmConvert.hxx" -#include "util/Manual.hxx" -#include "AudioFormat.hxx" -#include "poison.h" - -#include <assert.h> -#include <string.h> - -class ConvertFilter final : public Filter { - /** - * The input audio format; PCM data is passed to the filter() - * method in this format. - */ - AudioFormat in_audio_format; - - /** - * The output audio format; the consumer of this plugin - * expects PCM data in this format. This defaults to - * #in_audio_format, and can be set with convert_filter_set(). - */ - AudioFormat out_audio_format; - - Manual<PcmConvert> state; - -public: - void Set(const AudioFormat &_out_audio_format) { - assert(in_audio_format.IsValid()); - assert(out_audio_format.IsValid()); - assert(_out_audio_format.IsValid()); - - out_audio_format = _out_audio_format; - } - - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close() override; - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, - Error &error) override; -}; - -static Filter * -convert_filter_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new ConvertFilter(); -} - -AudioFormat -ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) -{ - assert(audio_format.IsValid()); - - in_audio_format = out_audio_format = audio_format; - state.Construct(); - - return in_audio_format; -} - -void -ConvertFilter::Close() -{ - state.Destruct(); - - poison_undefined(&in_audio_format, sizeof(in_audio_format)); - poison_undefined(&out_audio_format, sizeof(out_audio_format)); -} - -const void * -ConvertFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) -{ - if (in_audio_format == out_audio_format) { - /* optimized special case: no-op */ - *dest_size_r = src_size; - return src; - } - - return state->Convert(in_audio_format, - src, src_size, - out_audio_format, dest_size_r, - error); -} - -const struct filter_plugin convert_filter_plugin = { - "convert", - convert_filter_init, -}; - -void -convert_filter_set(Filter *_filter, const AudioFormat out_audio_format) -{ - ConvertFilter *filter = (ConvertFilter *)_filter; - - filter->Set(out_audio_format); -} diff --git a/src/filter/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx deleted file mode 100644 index c814aaf49..000000000 --- a/src/filter/ConvertFilterPlugin.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX -#define MPD_CONVERT_FILTER_PLUGIN_HXX - -class Filter; -struct AudioFormat; - -/** - * Sets the output audio format for the specified filter. You must - * call this after the filter has been opened. Since this audio - * format switch is a violation of the filter API, this filter must be - * the last in a chain. - */ -void -convert_filter_set(Filter *filter, AudioFormat out_audio_format); - -#endif diff --git a/src/filter/FilterConfig.cxx b/src/filter/FilterConfig.cxx new file mode 100644 index 000000000..d8c1fc6c2 --- /dev/null +++ b/src/filter/FilterConfig.cxx @@ -0,0 +1,80 @@ +/* + * 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 "FilterConfig.hxx" +#include "plugins/ChainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +#include <algorithm> + +#include <string.h> + +static bool +filter_chain_append_new(Filter &chain, const char *template_name, Error &error) +{ + const struct config_param *cfg = + config_find_block(CONF_AUDIO_FILTER, "name", template_name); + if (cfg == nullptr) { + error.Format(config_domain, + "filter template not found: %s", + template_name); + return false; + } + + // Instantiate one of those filter plugins with the template name as a hint + Filter *f = filter_configured_new(*cfg, error); + if (f == nullptr) + // The error has already been set, just stop. + return false; + + const char *plugin_name = cfg->GetBlockValue("plugin", + "unknown"); + filter_chain_append(chain, plugin_name, f); + + return true; +} + +bool +filter_chain_parse(Filter &chain, const char *spec, Error &error) +{ + const char *const end = spec + strlen(spec); + + while (true) { + const char *comma = std::find(spec, end, ','); + if (comma > spec) { + const std::string name(spec, comma); + if (!filter_chain_append_new(chain, name.c_str(), + error)) + return false; + } + + if (comma == end) + break; + + spec = comma + 1; + } + + return true; +} diff --git a/src/filter/FilterConfig.hxx b/src/filter/FilterConfig.hxx new file mode 100644 index 000000000..1018eed51 --- /dev/null +++ b/src/filter/FilterConfig.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. + */ + +/** \file + * + * Utility functions for filter configuration + */ + +#ifndef MPD_FILTER_CONFIG_HXX +#define MPD_FILTER_CONFIG_HXX + +class Filter; +class Error; + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return true on success + */ +bool +filter_chain_parse(Filter &chain, const char *spec, Error &error); + +#endif diff --git a/src/filter/FilterInternal.hxx b/src/filter/FilterInternal.hxx new file mode 100644 index 000000000..d2e619540 --- /dev/null +++ b/src/filter/FilterInternal.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Internal stuff for the filter core and filter plugins. + */ + +#ifndef MPD_FILTER_INTERNAL_HXX +#define MPD_FILTER_INTERNAL_HXX + +#include <stddef.h> + +struct AudioFormat; +class Error; +template<typename T> struct ConstBuffer; + +class Filter { +public: + virtual ~Filter() {} + + /** + * Opens the filter, preparing it for FilterPCM(). + * + * @param filter the filter object + * @param af the audio format of incoming data; the + * plugin may modify the object to enforce another input + * format + * @param error location to store the error occurring, or nullptr + * to ignore errors. + * @return the format of outgoing data or + * AudioFormat::Undefined() on error + */ + virtual AudioFormat Open(AudioFormat &af, Error &error) = 0; + + /** + * Closes the filter. After that, you may call Open() again. + */ + virtual void Close() = 0; + + /** + * Filters a block of PCM data. + * + * @param filter the filter object + * @param src the input buffer + * @param error location to store the error occurring, or nullptr + * to ignore errors. + * @return the destination buffer on success (will be + * invalidated by Close() or FilterPCM()), nullptr on + * error + */ + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, Error &error) = 0; +}; + +#endif diff --git a/src/filter/FilterPlugin.cxx b/src/filter/FilterPlugin.cxx new file mode 100644 index 000000000..98314f771 --- /dev/null +++ b/src/filter/FilterPlugin.cxx @@ -0,0 +1,58 @@ +/* + * 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 "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +Filter * +filter_new(const struct filter_plugin *plugin, + const config_param ¶m, Error &error) +{ + assert(plugin != nullptr); + assert(!error.IsDefined()); + + return plugin->init(param, error); +} + +Filter * +filter_configured_new(const config_param ¶m, Error &error) +{ + assert(!error.IsDefined()); + + const char *plugin_name = param.GetBlockValue("plugin"); + if (plugin_name == nullptr) { + error.Set(config_domain, "No filter plugin specified"); + return nullptr; + } + + const filter_plugin *plugin = filter_plugin_by_name(plugin_name); + if (plugin == nullptr) { + error.Format(config_domain, + "No such filter plugin: %s", plugin_name); + return nullptr; + } + + return filter_new(plugin, param, error); +} diff --git a/src/filter/FilterPlugin.hxx b/src/filter/FilterPlugin.hxx new file mode 100644 index 000000000..443d29881 --- /dev/null +++ b/src/filter/FilterPlugin.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the filter_plugin class. It describes a + * plugin API for objects which filter raw PCM data. + */ + +#ifndef MPD_FILTER_PLUGIN_HXX +#define MPD_FILTER_PLUGIN_HXX + +struct config_param; +class Filter; +class Error; + +struct filter_plugin { + const char *name; + + /** + * Allocates and configures a filter. + */ + Filter *(*init)(const config_param ¶m, Error &error); +}; + +/** + * Creates a new instance of the specified filter plugin. + * + * @param plugin the filter plugin + * @param param optional configuration section + * @param error location to store the error occurring, or nullptr to + * ignore errors. + * @return a new filter object, or nullptr on error + */ +Filter * +filter_new(const struct filter_plugin *plugin, + const config_param ¶m, Error &error); + +/** + * Creates a new filter, loads configuration and the plugin name from + * the specified configuration section. + * + * @param param the configuration section + * @param error location to store the error occurring, or nullptr to + * ignore errors. + * @return a new filter object, or nullptr on error + */ +Filter * +filter_configured_new(const config_param ¶m, Error &error); + +#endif diff --git a/src/filter/FilterRegistry.cxx b/src/filter/FilterRegistry.cxx new file mode 100644 index 000000000..286fb8db3 --- /dev/null +++ b/src/filter/FilterRegistry.cxx @@ -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. + */ + +#include "config.h" +#include "FilterRegistry.hxx" +#include "FilterPlugin.hxx" + +#include <string.h> + +const struct filter_plugin *const filter_plugins[] = { + &null_filter_plugin, + &route_filter_plugin, + &normalize_filter_plugin, + &volume_filter_plugin, + &replay_gain_filter_plugin, + nullptr, +}; + +const struct filter_plugin * +filter_plugin_by_name(const char *name) +{ + for (unsigned i = 0; filter_plugins[i] != nullptr; ++i) + if (strcmp(filter_plugins[i]->name, name) == 0) + return filter_plugins[i]; + + return nullptr; +} diff --git a/src/filter/FilterRegistry.hxx b/src/filter/FilterRegistry.hxx new file mode 100644 index 000000000..24618a87a --- /dev/null +++ b/src/filter/FilterRegistry.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. + */ + +/** \file + * + * This library manages all filter plugins which are enabled at + * compile time. + */ + +#ifndef MPD_FILTER_REGISTRY_HXX +#define MPD_FILTER_REGISTRY_HXX + +#include "Compiler.h" + +extern const struct filter_plugin null_filter_plugin; +extern const struct filter_plugin chain_filter_plugin; +extern const struct filter_plugin convert_filter_plugin; +extern const struct filter_plugin route_filter_plugin; +extern const struct filter_plugin normalize_filter_plugin; +extern const struct filter_plugin volume_filter_plugin; +extern const struct filter_plugin replay_gain_filter_plugin; + +gcc_pure +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx deleted file mode 100644 index 6c4f6b0e5..000000000 --- a/src/filter/NormalizeFilterPlugin.cxx +++ /dev/null @@ -1,83 +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 "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "pcm/PcmBuffer.hxx" -#include "AudioFormat.hxx" -#include "AudioCompress/compress.h" - -#include <assert.h> -#include <string.h> - -class NormalizeFilter final : public Filter { - struct Compressor *compressor; - - PcmBuffer buffer; - -public: - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close(); - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error); -}; - -static Filter * -normalize_filter_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new NormalizeFilter(); -} - -AudioFormat -NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) -{ - audio_format.format = SampleFormat::S16; - - compressor = Compressor_new(0); - - return audio_format; -} - -void -NormalizeFilter::Close() -{ - buffer.Clear(); - Compressor_delete(compressor); -} - -const void * -NormalizeFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, gcc_unused Error &error) -{ - int16_t *dest = (int16_t *)buffer.Get(src_size); - memcpy(dest, src, src_size); - - Compressor_Process_int16(compressor, dest, src_size / 2); - - *dest_size_r = src_size; - return dest; -} - -const struct filter_plugin normalize_filter_plugin = { - "normalize", - normalize_filter_init, -}; diff --git a/src/filter/NullFilterPlugin.cxx b/src/filter/NullFilterPlugin.cxx deleted file mode 100644 index c762592f6..000000000 --- a/src/filter/NullFilterPlugin.cxx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This filter plugin does nothing. That is not quite useful, except - * for testing the filter core, or as a template for new filter - * plugins. - */ - -#include "config.h" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "AudioFormat.hxx" -#include "Compiler.h" - -class NullFilter final : public Filter { -public: - virtual AudioFormat Open(AudioFormat &af, - gcc_unused Error &error) override { - return af; - } - - virtual void Close() override {} - - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, - gcc_unused Error &error) override { - *dest_size_r = src_size; - return src; - } -}; - -static Filter * -null_filter_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new NullFilter(); -} - -const struct filter_plugin null_filter_plugin = { - "null", - null_filter_init, -}; diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx deleted file mode 100644 index b2dcde4cc..000000000 --- a/src/filter/ReplayGainFilterPlugin.cxx +++ /dev/null @@ -1,235 +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 "ReplayGainFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "AudioFormat.hxx" -#include "ReplayGainInfo.hxx" -#include "ReplayGainConfig.hxx" -#include "MixerControl.hxx" -#include "pcm/PcmVolume.hxx" -#include "pcm/PcmBuffer.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> - -static constexpr Domain replay_gain_domain("replay_gain"); - -class ReplayGainFilter final : public Filter { - /** - * If set, then this hardware mixer is used for applying - * replay gain, instead of the software volume library. - */ - Mixer *mixer; - - /** - * The base volume level for scale=1.0, between 1 and 100 - * (including). - */ - unsigned base; - - ReplayGainMode mode; - - ReplayGainInfo info; - - /** - * The current volume, between 0 and a value that may or may not exceed - * #PCM_VOLUME_1. - * - * If the default value of true is used for replaygain_limit, the - * application of the volume to the signal will never cause clipping. - * - * On the other hand, if the user has set replaygain_limit to false, - * the chance of clipping is explicitly preferred if that's required to - * maintain a consistent audio level. Whether clipping will actually - * occur depends on what value the user is using for replaygain_preamp. - */ - unsigned volume; - - AudioFormat format; - - PcmBuffer buffer; - -public: - ReplayGainFilter() - :mixer(nullptr), mode(REPLAY_GAIN_OFF), - volume(PCM_VOLUME_1) { - info.Clear(); - } - - void SetMixer(Mixer *_mixer, unsigned _base) { - assert(_mixer == nullptr || (_base > 0 && _base <= 100)); - - mixer = _mixer; - base = _base; - - Update(); - } - - void SetInfo(const ReplayGainInfo *_info) { - if (_info != nullptr) { - info = *_info; - info.Complete(); - } else - info.Clear(); - - Update(); - } - - void SetMode(ReplayGainMode _mode) { - if (_mode == mode) - /* no change */ - return; - - FormatDebug(replay_gain_domain, - "replay gain mode has changed %d->%d\n", - mode, _mode); - - mode = _mode; - Update(); - } - - /** - * Recalculates the new volume after a property was changed. - */ - void Update(); - - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close(); - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error); -}; - -void -ReplayGainFilter::Update() -{ - if (mode != REPLAY_GAIN_OFF) { - const auto &tuple = info.tuples[mode]; - float scale = tuple.CalculateScale(replay_gain_preamp, - replay_gain_missing_preamp, - replay_gain_limit); - FormatDebug(replay_gain_domain, - "scale=%f\n", (double)scale); - - volume = pcm_float_to_volume(scale); - } else - volume = PCM_VOLUME_1; - - if (mixer != nullptr) { - /* update the hardware mixer volume */ - - unsigned _volume = (volume * base) / PCM_VOLUME_1; - if (_volume > 100) - _volume = 100; - - Error error; - if (!mixer_set_volume(mixer, _volume, error)) - LogError(error, "Failed to update hardware mixer"); - } -} - -static Filter * -replay_gain_filter_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new ReplayGainFilter(); -} - -AudioFormat -ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error) -{ - format = af; - - return format; -} - -void -ReplayGainFilter::Close() -{ - buffer.Clear(); -} - -const void * -ReplayGainFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) -{ - - *dest_size_r = src_size; - - if (volume == PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - void *dest = buffer.Get(src_size); - if (volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - bool success = pcm_volume(dest, src_size, - format.format, - volume); - if (!success) { - error.Set(replay_gain_domain, "pcm_volume() has failed"); - return nullptr; - } - - return dest; -} - -const struct filter_plugin replay_gain_filter_plugin = { - "replay_gain", - replay_gain_filter_init, -}; - -void -replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer, - unsigned base) -{ - ReplayGainFilter *filter = (ReplayGainFilter *)_filter; - - filter->SetMixer(mixer, base); -} - -void -replay_gain_filter_set_info(Filter *_filter, const ReplayGainInfo *info) -{ - ReplayGainFilter *filter = (ReplayGainFilter *)_filter; - - filter->SetInfo(info); -} - -void -replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode) -{ - ReplayGainFilter *filter = (ReplayGainFilter *)_filter; - - filter->SetMode(mode); -} diff --git a/src/filter/ReplayGainFilterPlugin.hxx b/src/filter/ReplayGainFilterPlugin.hxx deleted file mode 100644 index fbd1f2712..000000000 --- a/src/filter/ReplayGainFilterPlugin.hxx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX -#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX - -#include "ReplayGainInfo.hxx" - -class Filter; -class Mixer; - -/** - * Enables or disables the hardware mixer for applying replay gain. - * - * @param mixer the hardware mixer, or nullptr to fall back to software - * volume - * @param base the base volume level for scale=1.0, between 1 and 100 - * (including). - */ -void -replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer, - unsigned base); - -/** - * Sets a new #replay_gain_info at the beginning of a new song. - * - * @param info the new #replay_gain_info value, or nullptr if no replay - * gain data is available for the current song - */ -void -replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info); - -void -replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode); - -#endif diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx deleted file mode 100644 index d9042c21f..000000000 --- a/src/filter/RouteFilterPlugin.cxx +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * This filter copies audio data between channels. Useful for - * upmixing mono/stereo audio to surround speaker configurations. - * - * Its configuration consists of a "filter" section with a single - * "routes" entry, formatted as: \\ - * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ - * where each pair of numbers signifies a set of channels. - * Each source>dest pair leads to the data from channel #source - * being copied to channel #dest in the output. - * - * Example: \\ - * routes "0>0, 1>1, 0>2, 1>3"\\ - * upmixes stereo audio to a 4-speaker system, copying the front-left - * (0) to front left (0) and rear left (2), copying front-right (1) to - * front-right (1) and rear-right (3). - * - * If multiple sources are copied to the same destination channel, only - * one of them takes effect. - */ - -#include "config.h" -#include "ConfigError.hxx" -#include "ConfigData.hxx" -#include "AudioFormat.hxx" -#include "CheckAudioFormat.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "pcm/PcmBuffer.hxx" -#include "util/StringUtil.hxx" -#include "util/Error.hxx" - -#include <algorithm> - -#include <assert.h> -#include <string.h> -#include <stdint.h> -#include <stdlib.h> - -class RouteFilter final : public Filter { - /** - * The minimum number of channels we need for output - * to be able to perform all the copies the user has specified - */ - unsigned min_output_channels; - - /** - * The minimum number of input channels we need to - * copy all the data the user has requested. If fewer - * than this many are supplied by the input, undefined - * copy operations are given zeroed sources in stead. - */ - unsigned min_input_channels; - - /** - * The set of copy operations to perform on each sample - * The index is an output channel to use, the value is - * a corresponding input channel from which to take the - * data. A -1 means "no source" - */ - int8_t sources[MAX_CHANNELS]; - - /** - * The actual input format of our signal, once opened - */ - AudioFormat input_format; - - /** - * The decided upon output format, once opened - */ - AudioFormat output_format; - - /** - * The size, in bytes, of each multichannel frame in the - * input buffer - */ - size_t input_frame_size; - - /** - * The size, in bytes, of each multichannel frame in the - * output buffer - */ - size_t output_frame_size; - - /** - * The output buffer used last time around, can be reused if the size doesn't differ. - */ - PcmBuffer output_buffer; - -public: - /** - * Parse the "routes" section, a string on the form - * a>b, c>d, e>f, ... - * where a... are non-unique, non-negative integers - * and input channel a gets copied to output channel b, etc. - * @param param the configuration block to read - * @param filter a route_filter whose min_channels and sources[] to set - * @return true on success, false on error - */ - bool Configure(const config_param ¶m, Error &error); - - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close(); - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error); -}; - -bool -RouteFilter::Configure(const config_param ¶m, Error &error) { - - /* TODO: - * With a more clever way of marking "don't copy to output N", - * This could easily be merged into a single loop with some - * dynamic realloc() instead of one count run and one malloc(). - */ - - std::fill_n(sources, MAX_CHANNELS, -1); - - min_input_channels = 0; - min_output_channels = 0; - - // A cowardly default, just passthrough stereo - const char *routes = param.GetBlockValue("routes", "0>0, 1>1"); - while (true) { - routes = strchug_fast(routes); - - char *endptr; - const unsigned source = strtoul(routes, &endptr, 10); - endptr = strchug_fast(endptr); - if (endptr == routes || *endptr != '>') { - error.Set(config_domain, - "Malformed 'routes' specification"); - return false; - } - - if (source >= MAX_CHANNELS) { - error.Format(config_domain, - "Invalid source channel number: %u", - source); - return false; - } - - if (source >= min_input_channels) - min_input_channels = source + 1; - - routes = strchug_fast(endptr + 1); - - unsigned dest = strtoul(routes, &endptr, 10); - endptr = strchug_fast(endptr); - if (endptr == routes) { - error.Set(config_domain, - "Malformed 'routes' specification"); - return false; - } - - if (dest >= MAX_CHANNELS) { - error.Format(config_domain, - "Invalid destination channel number: %u", - dest); - return false; - } - - if (dest >= min_output_channels) - min_output_channels = dest + 1; - - sources[dest] = source; - - routes = endptr; - - if (*routes == 0) - break; - - if (*routes != ',') { - error.Set(config_domain, - "Malformed 'routes' specification"); - return false; - } - - ++routes; - } - - return true; -} - -static Filter * -route_filter_init(const config_param ¶m, Error &error) -{ - RouteFilter *filter = new RouteFilter(); - if (!filter->Configure(param, error)) { - delete filter; - return nullptr; - } - - return filter; -} - -AudioFormat -RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) -{ - // Copy the input format for later reference - input_format = audio_format; - input_frame_size = input_format.GetFrameSize(); - - // Decide on an output format which has enough channels, - // and is otherwise identical - output_format = audio_format; - output_format.channels = min_output_channels; - - // Precalculate this simple value, to speed up allocation later - output_frame_size = output_format.GetFrameSize(); - - return output_format; -} - -void -RouteFilter::Close() -{ - output_buffer.Clear(); -} - -const void * -RouteFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, gcc_unused Error &error) -{ - size_t number_of_frames = src_size / input_frame_size; - - const size_t bytes_per_frame_per_channel = input_format.GetSampleSize(); - - // A moving pointer that always refers to channel 0 in the input, at the currently handled frame - const uint8_t *base_source = (const uint8_t *)src; - - // Grow our reusable buffer, if needed, and set the moving pointer - *dest_size_r = number_of_frames * output_frame_size; - void *const result = output_buffer.Get(*dest_size_r); - - // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output - uint8_t *chan_destination = (uint8_t *)result; - - // Perform our copy operations, with N input channels and M output channels - for (unsigned int s=0; s<number_of_frames; ++s) { - - // Need to perform one copy per output channel - for (unsigned int c=0; c<min_output_channels; ++c) { - if (sources[c] == -1 || - (unsigned)sources[c] >= input_format.channels) { - // No source for this destination output, - // give it zeroes as input - memset(chan_destination, - 0x00, - bytes_per_frame_per_channel); - } else { - // Get the data from channel sources[c] - // and copy it to the output - const uint8_t *data = base_source + - (sources[c] * bytes_per_frame_per_channel); - memcpy(chan_destination, - data, - bytes_per_frame_per_channel); - } - // Move on to the next output channel - chan_destination += bytes_per_frame_per_channel; - } - - - // Go on to the next N input samples - base_source += input_frame_size; - } - - // Here it is, ladies and gentlemen! Rerouted data! - return result; -} - -const struct filter_plugin route_filter_plugin = { - "route", - route_filter_init, -}; diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx deleted file mode 100644 index 1b663f6eb..000000000 --- a/src/filter/VolumeFilterPlugin.cxx +++ /dev/null @@ -1,143 +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 "VolumeFilterPlugin.hxx" -#include "FilterPlugin.hxx" -#include "FilterInternal.hxx" -#include "FilterRegistry.hxx" -#include "pcm/PcmVolume.hxx" -#include "pcm/PcmBuffer.hxx" -#include "AudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <assert.h> -#include <string.h> - -class VolumeFilter final : public Filter { - /** - * The current volume, from 0 to #PCM_VOLUME_1. - */ - unsigned volume; - - AudioFormat format; - - PcmBuffer buffer; - -public: - VolumeFilter() - :volume(PCM_VOLUME_1) {} - - unsigned GetVolume() const { - assert(volume <= PCM_VOLUME_1); - - return volume; - } - - void SetVolume(unsigned _volume) { - assert(_volume <= PCM_VOLUME_1); - - volume = _volume; - } - - virtual AudioFormat Open(AudioFormat &af, Error &error) override; - virtual void Close(); - virtual const void *FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error); -}; - -static constexpr Domain volume_domain("pcm_volume"); - -static Filter * -volume_filter_init(gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new VolumeFilter(); -} - -AudioFormat -VolumeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) -{ - format = audio_format; - - return format; -} - -void -VolumeFilter::Close() -{ - buffer.Clear(); -} - -const void * -VolumeFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) -{ - *dest_size_r = src_size; - - if (volume >= PCM_VOLUME_1) - /* optimized special case: 100% volume = no-op */ - return src; - - void *dest = buffer.Get(src_size); - - if (volume <= 0) { - /* optimized special case: 0% volume = memset(0) */ - /* XXX is this valid for all sample formats? What - about floating point? */ - memset(dest, 0, src_size); - return dest; - } - - memcpy(dest, src, src_size); - - bool success = pcm_volume(dest, src_size, - format.format, - volume); - if (!success) { - error.Set(volume_domain, "pcm_volume() has failed"); - return NULL; - } - - return dest; -} - -const struct filter_plugin volume_filter_plugin = { - "volume", - volume_filter_init, -}; - -unsigned -volume_filter_get(const Filter *_filter) -{ - const VolumeFilter *filter = - (const VolumeFilter *)_filter; - - return filter->GetVolume(); -} - -void -volume_filter_set(Filter *_filter, unsigned volume) -{ - VolumeFilter *filter = (VolumeFilter *)_filter; - - filter->SetVolume(volume); -} - diff --git a/src/filter/VolumeFilterPlugin.hxx b/src/filter/VolumeFilterPlugin.hxx deleted file mode 100644 index 822b7e93a..000000000 --- a/src/filter/VolumeFilterPlugin.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX -#define MPD_VOLUME_FILTER_PLUGIN_HXX - -class Filter; - -unsigned -volume_filter_get(const Filter *filter); - -void -volume_filter_set(Filter *filter, unsigned volume); - -#endif diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx new file mode 100644 index 000000000..8586cb86e --- /dev/null +++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx @@ -0,0 +1,130 @@ +/* + * 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 "AutoConvertFilterPlugin.hxx" +#include "ConvertFilterPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigData.hxx" +#include "util/ConstBuffer.hxx" + +#include <assert.h> + +class AutoConvertFilter final : public Filter { + /** + * The underlying filter. + */ + Filter *filter; + + /** + * A convert_filter, just in case conversion is needed. nullptr + * if unused. + */ + Filter *convert; + +public: + AutoConvertFilter(Filter *_filter):filter(_filter) {} + ~AutoConvertFilter() { + delete filter; + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error) override; +}; + +AudioFormat +AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error) +{ + assert(in_audio_format.IsValid()); + + /* open the "real" filter */ + + AudioFormat child_audio_format = in_audio_format; + AudioFormat out_audio_format = filter->Open(child_audio_format, error); + if (!out_audio_format.IsDefined()) + return out_audio_format; + + /* need to convert? */ + + if (in_audio_format != child_audio_format) { + /* yes - create a convert_filter */ + + const config_param empty; + convert = filter_new(&convert_filter_plugin, empty, error); + if (convert == nullptr) { + filter->Close(); + return AudioFormat::Undefined(); + } + + AudioFormat audio_format2 = in_audio_format; + AudioFormat audio_format3 = + convert->Open(audio_format2, error); + if (!audio_format3.IsDefined()) { + delete convert; + filter->Close(); + return AudioFormat::Undefined(); + } + + assert(audio_format2 == in_audio_format); + + if (!convert_filter_set(convert, child_audio_format, error)) { + delete convert; + filter->Close(); + return AudioFormat::Undefined(); + } + } else + /* no */ + convert = nullptr; + + return out_audio_format; +} + +void +AutoConvertFilter::Close() +{ + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + filter->Close(); +} + +ConstBuffer<void> +AutoConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error) +{ + if (convert != nullptr) { + src = convert->FilterPCM(src, error); + if (src.IsNull()) + return nullptr; + } + + return filter->FilterPCM(src, error); +} + +Filter * +autoconvert_filter_new(Filter *filter) +{ + return new AutoConvertFilter(filter); +} diff --git a/src/filter/plugins/AutoConvertFilterPlugin.hxx b/src/filter/plugins/AutoConvertFilterPlugin.hxx new file mode 100644 index 000000000..c5dfdd2f6 --- /dev/null +++ b/src/filter/plugins/AutoConvertFilterPlugin.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX +#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX + +class Filter; + +/** + * Creates a new "autoconvert" filter. When opened, it ensures that + * the input audio format isn't changed. If the underlying filter + * requests a different format, it automatically creates a + * convert_filter. + */ +Filter * +autoconvert_filter_new(Filter *filter); + +#endif diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx new file mode 100644 index 000000000..7342beb14 --- /dev/null +++ b/src/filter/plugins/ChainFilterPlugin.cxx @@ -0,0 +1,179 @@ +/* + * 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 "ChainFilterPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/ConstBuffer.hxx" + +#include <list> + +#include <assert.h> + +class ChainFilter final : public Filter { + struct Child { + const char *name; + Filter *filter; + + Child(const char *_name, Filter *_filter) + :name(_name), filter(_filter) {} + ~Child() { + delete filter; + } + + Child(const Child &) = delete; + Child &operator=(const Child &) = delete; + }; + + std::list<Child> children; + +public: + void Append(const char *name, Filter *filter) { + children.emplace_back(name, filter); + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error); + +private: + /** + * Close all filters in the chain until #until is reached. + * #until itself is not closed. + */ + void CloseUntil(const Filter *until); +}; + +static constexpr Domain chain_filter_domain("chain_filter"); + +static Filter * +chain_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new ChainFilter(); +} + +void +ChainFilter::CloseUntil(const Filter *until) +{ + for (auto &child : children) { + if (child.filter == until) + /* don't close this filter */ + return; + + /* close this filter */ + child.filter->Close(); + } + + /* this assertion fails if #until does not exist (anymore) */ + assert(false); + gcc_unreachable(); +} + +static AudioFormat +chain_open_child(const char *name, Filter *filter, + const AudioFormat &prev_audio_format, + Error &error) +{ + AudioFormat conv_audio_format = prev_audio_format; + const AudioFormat next_audio_format = + filter->Open(conv_audio_format, error); + if (!next_audio_format.IsDefined()) + return next_audio_format; + + if (conv_audio_format != prev_audio_format) { + struct audio_format_string s; + + filter->Close(); + + error.Format(chain_filter_domain, + "Audio format not supported by filter '%s': %s", + name, + audio_format_to_string(prev_audio_format, &s)); + return AudioFormat::Undefined(); + } + + return next_audio_format; +} + +AudioFormat +ChainFilter::Open(AudioFormat &in_audio_format, Error &error) +{ + AudioFormat audio_format = in_audio_format; + + for (auto &child : children) { + audio_format = chain_open_child(child.name, child.filter, + audio_format, error); + if (!audio_format.IsDefined()) { + /* rollback, close all children */ + CloseUntil(child.filter); + break; + } + } + + /* return the output format of the last filter */ + return audio_format; +} + +void +ChainFilter::Close() +{ + for (auto &child : children) + child.filter->Close(); +} + +ConstBuffer<void> +ChainFilter::FilterPCM(ConstBuffer<void> src, Error &error) +{ + for (auto &child : children) { + /* feed the output of the previous filter as input + into the current one */ + src = child.filter->FilterPCM(src, error); + if (src.IsNull()) + return nullptr; + } + + /* return the output of the last filter */ + return src; +} + +const struct filter_plugin chain_filter_plugin = { + "chain", + chain_filter_init, +}; + +Filter * +filter_chain_new(void) +{ + return new ChainFilter(); +} + +void +filter_chain_append(Filter &_chain, const char *name, Filter *filter) +{ + ChainFilter &chain = (ChainFilter &)_chain; + + chain.Append(name, filter); +} diff --git a/src/filter/plugins/ChainFilterPlugin.hxx b/src/filter/plugins/ChainFilterPlugin.hxx new file mode 100644 index 000000000..b36aa3322 --- /dev/null +++ b/src/filter/plugins/ChainFilterPlugin.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * A filter chain is a container for several filters. They are + * chained together, i.e. called in a row, one filter passing its + * output to the next one. + */ + +#ifndef MPD_FILTER_CHAIN_HXX +#define MPD_FILTER_CHAIN_HXX + +class Filter; + +/** + * Creates a new filter chain. + */ +Filter * +filter_chain_new(void); + +/** + * Appends a new filter at the end of the filter chain. You must call + * this function before the first filter_open() call. + * + * @param chain the filter chain created with filter_chain_new() + * @param filter the filter to be appended to #chain + */ +void +filter_chain_append(Filter &chain, const char *name, Filter *filter); + +#endif diff --git a/src/filter/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx new file mode 100644 index 000000000..5c6a07ba1 --- /dev/null +++ b/src/filter/plugins/ConvertFilterPlugin.cxx @@ -0,0 +1,145 @@ +/* + * 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 "ConvertFilterPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/PcmConvert.hxx" +#include "util/Manual.hxx" +#include "util/ConstBuffer.hxx" +#include "AudioFormat.hxx" +#include "poison.h" + +#include <assert.h> + +class ConvertFilter final : public Filter { + /** + * The input audio format; PCM data is passed to the filter() + * method in this format. + */ + AudioFormat in_audio_format; + + /** + * The output audio format; the consumer of this plugin + * expects PCM data in this format. + * + * If this is AudioFormat::Undefined(), then the #PcmConvert + * attribute is not open. This can mean that Set() has failed + * or that no conversion is necessary. + */ + AudioFormat out_audio_format; + + Manual<PcmConvert> state; + +public: + bool Set(const AudioFormat &_out_audio_format, Error &error); + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error) override; +}; + +static Filter * +convert_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new ConvertFilter(); +} + +bool +ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error) +{ + assert(in_audio_format.IsValid()); + assert(_out_audio_format.IsValid()); + + if (_out_audio_format == out_audio_format) + /* no change */ + return true; + + if (out_audio_format.IsValid()) { + out_audio_format.Clear(); + state->Close(); + } + + if (_out_audio_format == in_audio_format) + /* optimized special case: no-op */ + return true; + + if (!state->Open(in_audio_format, _out_audio_format, error)) + return false; + + out_audio_format = _out_audio_format; + return true; +} + +AudioFormat +ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + assert(audio_format.IsValid()); + + in_audio_format = audio_format; + out_audio_format.Clear(); + + state.Construct(); + + return in_audio_format; +} + +void +ConvertFilter::Close() +{ + assert(in_audio_format.IsValid()); + + if (out_audio_format.IsValid()) + state->Close(); + + state.Destruct(); + + poison_undefined(&in_audio_format, sizeof(in_audio_format)); + poison_undefined(&out_audio_format, sizeof(out_audio_format)); +} + +ConstBuffer<void> +ConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error) +{ + assert(in_audio_format.IsValid()); + + if (!out_audio_format.IsValid()) + /* optimized special case: no-op */ + return src; + + return state->Convert(src, error); +} + +const struct filter_plugin convert_filter_plugin = { + "convert", + convert_filter_init, +}; + +bool +convert_filter_set(Filter *_filter, AudioFormat out_audio_format, + Error &error) +{ + ConvertFilter *filter = (ConvertFilter *)_filter; + + return filter->Set(out_audio_format, error); +} diff --git a/src/filter/plugins/ConvertFilterPlugin.hxx b/src/filter/plugins/ConvertFilterPlugin.hxx new file mode 100644 index 000000000..bb4673651 --- /dev/null +++ b/src/filter/plugins/ConvertFilterPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX +#define MPD_CONVERT_FILTER_PLUGIN_HXX + +class Filter; +class Error; +struct AudioFormat; + +/** + * Sets the output audio format for the specified filter. You must + * call this after the filter has been opened. Since this audio + * format switch is a violation of the filter API, this filter must be + * the last in a chain. + */ +bool +convert_filter_set(Filter *filter, AudioFormat out_audio_format, + Error &error); + +#endif diff --git a/src/filter/plugins/NormalizeFilterPlugin.cxx b/src/filter/plugins/NormalizeFilterPlugin.cxx new file mode 100644 index 000000000..a69df2b81 --- /dev/null +++ b/src/filter/plugins/NormalizeFilterPlugin.cxx @@ -0,0 +1,80 @@ +/* + * 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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/PcmBuffer.hxx" +#include "AudioFormat.hxx" +#include "AudioCompress/compress.h" +#include "util/ConstBuffer.hxx" + +#include <string.h> + +class NormalizeFilter final : public Filter { + struct Compressor *compressor; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error) override; +}; + +static Filter * +normalize_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new NormalizeFilter(); +} + +AudioFormat +NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + audio_format.format = SampleFormat::S16; + + compressor = Compressor_new(0); + + return audio_format; +} + +void +NormalizeFilter::Close() +{ + buffer.Clear(); + Compressor_delete(compressor); +} + +ConstBuffer<void> +NormalizeFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error) +{ + int16_t *dest = (int16_t *)buffer.Get(src.size); + memcpy(dest, src.data, src.size); + + Compressor_Process_int16(compressor, dest, src.size / 2); + return { (const void *)dest, src.size }; +} + +const struct filter_plugin normalize_filter_plugin = { + "normalize", + normalize_filter_init, +}; diff --git a/src/filter/plugins/NullFilterPlugin.cxx b/src/filter/plugins/NullFilterPlugin.cxx new file mode 100644 index 000000000..ebd8e4ec5 --- /dev/null +++ b/src/filter/plugins/NullFilterPlugin.cxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter plugin does nothing. That is not quite useful, except + * for testing the filter core, or as a template for new filter + * plugins. + */ + +#include "config.h" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "Compiler.h" +#include "util/ConstBuffer.hxx" + +class NullFilter final : public Filter { +public: + virtual AudioFormat Open(AudioFormat &af, + gcc_unused Error &error) override { + return af; + } + + virtual void Close() override {} + + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + gcc_unused Error &error) override { + return src; + } +}; + +static Filter * +null_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new NullFilter(); +} + +const struct filter_plugin null_filter_plugin = { + "null", + null_filter_init, +}; diff --git a/src/filter/plugins/ReplayGainFilterPlugin.cxx b/src/filter/plugins/ReplayGainFilterPlugin.cxx new file mode 100644 index 000000000..651352ac9 --- /dev/null +++ b/src/filter/plugins/ReplayGainFilterPlugin.cxx @@ -0,0 +1,207 @@ +/* + * 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 "ReplayGainFilterPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "ReplayGainInfo.hxx" +#include "ReplayGainConfig.hxx" +#include "mixer/MixerControl.hxx" +#include "pcm/Volume.hxx" +#include "pcm/PcmBuffer.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +static constexpr Domain replay_gain_domain("replay_gain"); + +class ReplayGainFilter final : public Filter { + /** + * If set, then this hardware mixer is used for applying + * replay gain, instead of the software volume library. + */ + Mixer *mixer; + + /** + * The base volume level for scale=1.0, between 1 and 100 + * (including). + */ + unsigned base; + + ReplayGainMode mode; + + ReplayGainInfo info; + + /** + * About the current volume: it is between 0 and a value that + * may or may not exceed #PCM_VOLUME_1. + * + * If the default value of true is used for replaygain_limit, the + * application of the volume to the signal will never cause clipping. + * + * On the other hand, if the user has set replaygain_limit to false, + * the chance of clipping is explicitly preferred if that's required to + * maintain a consistent audio level. Whether clipping will actually + * occur depends on what value the user is using for replaygain_preamp. + */ + PcmVolume pv; + +public: + ReplayGainFilter() + :mixer(nullptr), mode(REPLAY_GAIN_OFF) { + info.Clear(); + } + + void SetMixer(Mixer *_mixer, unsigned _base) { + assert(_mixer == nullptr || (_base > 0 && _base <= 100)); + + mixer = _mixer; + base = _base; + + Update(); + } + + void SetInfo(const ReplayGainInfo *_info) { + if (_info != nullptr) { + info = *_info; + info.Complete(); + } else + info.Clear(); + + Update(); + } + + void SetMode(ReplayGainMode _mode) { + if (_mode == mode) + /* no change */ + return; + + FormatDebug(replay_gain_domain, + "replay gain mode has changed %d->%d\n", + mode, _mode); + + mode = _mode; + Update(); + } + + /** + * Recalculates the new volume after a property was changed. + */ + void Update(); + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error) override; +}; + +void +ReplayGainFilter::Update() +{ + unsigned volume = PCM_VOLUME_1; + if (mode != REPLAY_GAIN_OFF) { + const auto &tuple = info.tuples[mode]; + float scale = tuple.CalculateScale(replay_gain_preamp, + replay_gain_missing_preamp, + replay_gain_limit); + FormatDebug(replay_gain_domain, + "scale=%f\n", (double)scale); + + volume = pcm_float_to_volume(scale); + } + + pv.SetVolume(volume); + + if (mixer != nullptr) { + /* update the hardware mixer volume */ + + unsigned _volume = (volume * base) / PCM_VOLUME_1; + if (_volume > 100) + _volume = 100; + + Error error; + if (!mixer_set_volume(mixer, _volume, error)) + LogError(error, "Failed to update hardware mixer"); + } +} + +static Filter * +replay_gain_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new ReplayGainFilter(); +} + +AudioFormat +ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error) +{ + if (!pv.Open(af.format, error)) + return AudioFormat::Undefined(); + + return af; +} + +void +ReplayGainFilter::Close() +{ + pv.Close(); +} + +ConstBuffer<void> +ReplayGainFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error) +{ + return pv.Apply(src); +} + +const struct filter_plugin replay_gain_filter_plugin = { + "replay_gain", + replay_gain_filter_init, +}; + +void +replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer, + unsigned base) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMixer(mixer, base); +} + +void +replay_gain_filter_set_info(Filter *_filter, const ReplayGainInfo *info) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetInfo(info); +} + +void +replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMode(mode); +} diff --git a/src/filter/plugins/ReplayGainFilterPlugin.hxx b/src/filter/plugins/ReplayGainFilterPlugin.hxx new file mode 100644 index 000000000..346541b97 --- /dev/null +++ b/src/filter/plugins/ReplayGainFilterPlugin.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX +#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX + +#include "ReplayGainInfo.hxx" + +class Filter; +class Mixer; + +/** + * Enables or disables the hardware mixer for applying replay gain. + * + * @param mixer the hardware mixer, or nullptr to fall back to software + * volume + * @param base the base volume level for scale=1.0, between 1 and 100 + * (including). + */ +void +replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer, + unsigned base); + +/** + * Sets a new #replay_gain_info at the beginning of a new song. + * + * @param info the new #replay_gain_info value, or nullptr if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info); + +void +replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode); + +#endif diff --git a/src/filter/plugins/RouteFilterPlugin.cxx b/src/filter/plugins/RouteFilterPlugin.cxx new file mode 100644 index 000000000..a252af97d --- /dev/null +++ b/src/filter/plugins/RouteFilterPlugin.cxx @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter copies audio data between channels. Useful for + * upmixing mono/stereo audio to surround speaker configurations. + * + * Its configuration consists of a "filter" section with a single + * "routes" entry, formatted as: \\ + * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ + * where each pair of numbers signifies a set of channels. + * Each source>dest pair leads to the data from channel #source + * being copied to channel #dest in the output. + * + * Example: \\ + * routes "0>0, 1>1, 0>2, 1>3"\\ + * upmixes stereo audio to a 4-speaker system, copying the front-left + * (0) to front left (0) and rear left (2), copying front-right (1) to + * front-right (1) and rear-right (3). + * + * If multiple sources are copied to the same destination channel, only + * one of them takes effect. + */ + +#include "config.h" +#include "config/ConfigError.hxx" +#include "config/ConfigData.hxx" +#include "AudioFormat.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/PcmBuffer.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/ConstBuffer.hxx" + +#include <algorithm> + +#include <assert.h> +#include <string.h> +#include <stdint.h> +#include <stdlib.h> + +class RouteFilter final : public Filter { + /** + * The minimum number of channels we need for output + * to be able to perform all the copies the user has specified + */ + unsigned min_output_channels; + + /** + * The minimum number of input channels we need to + * copy all the data the user has requested. If fewer + * than this many are supplied by the input, undefined + * copy operations are given zeroed sources in stead. + */ + unsigned min_input_channels; + + /** + * The set of copy operations to perform on each sample + * The index is an output channel to use, the value is + * a corresponding input channel from which to take the + * data. A -1 means "no source" + */ + int8_t sources[MAX_CHANNELS]; + + /** + * The actual input format of our signal, once opened + */ + AudioFormat input_format; + + /** + * The decided upon output format, once opened + */ + AudioFormat output_format; + + /** + * The size, in bytes, of each multichannel frame in the + * input buffer + */ + size_t input_frame_size; + + /** + * The size, in bytes, of each multichannel frame in the + * output buffer + */ + size_t output_frame_size; + + /** + * The output buffer used last time around, can be reused if the size doesn't differ. + */ + PcmBuffer output_buffer; + +public: + /** + * Parse the "routes" section, a string on the form + * a>b, c>d, e>f, ... + * where a... are non-unique, non-negative integers + * and input channel a gets copied to output channel b, etc. + * @param param the configuration block to read + * @param filter a route_filter whose min_channels and sources[] to set + * @return true on success, false on error + */ + bool Configure(const config_param ¶m, Error &error); + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error) override; +}; + +bool +RouteFilter::Configure(const config_param ¶m, Error &error) { + + /* TODO: + * With a more clever way of marking "don't copy to output N", + * This could easily be merged into a single loop with some + * dynamic realloc() instead of one count run and one malloc(). + */ + + std::fill_n(sources, MAX_CHANNELS, -1); + + min_input_channels = 0; + min_output_channels = 0; + + // A cowardly default, just passthrough stereo + const char *routes = param.GetBlockValue("routes", "0>0, 1>1"); + while (true) { + routes = StripLeft(routes); + + char *endptr; + const unsigned source = strtoul(routes, &endptr, 10); + endptr = StripLeft(endptr); + if (endptr == routes || *endptr != '>') { + error.Set(config_domain, + "Malformed 'routes' specification"); + return false; + } + + if (source >= MAX_CHANNELS) { + error.Format(config_domain, + "Invalid source channel number: %u", + source); + return false; + } + + if (source >= min_input_channels) + min_input_channels = source + 1; + + routes = StripLeft(endptr + 1); + + unsigned dest = strtoul(routes, &endptr, 10); + endptr = StripLeft(endptr); + if (endptr == routes) { + error.Set(config_domain, + "Malformed 'routes' specification"); + return false; + } + + if (dest >= MAX_CHANNELS) { + error.Format(config_domain, + "Invalid destination channel number: %u", + dest); + return false; + } + + if (dest >= min_output_channels) + min_output_channels = dest + 1; + + sources[dest] = source; + + routes = endptr; + + if (*routes == 0) + break; + + if (*routes != ',') { + error.Set(config_domain, + "Malformed 'routes' specification"); + return false; + } + + ++routes; + } + + return true; +} + +static Filter * +route_filter_init(const config_param ¶m, Error &error) +{ + RouteFilter *filter = new RouteFilter(); + if (!filter->Configure(param, error)) { + delete filter; + return nullptr; + } + + return filter; +} + +AudioFormat +RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + // Copy the input format for later reference + input_format = audio_format; + input_frame_size = input_format.GetFrameSize(); + + // Decide on an output format which has enough channels, + // and is otherwise identical + output_format = audio_format; + output_format.channels = min_output_channels; + + // Precalculate this simple value, to speed up allocation later + output_frame_size = output_format.GetFrameSize(); + + return output_format; +} + +void +RouteFilter::Close() +{ + output_buffer.Clear(); +} + +ConstBuffer<void> +RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error) +{ + size_t number_of_frames = src.size / input_frame_size; + + const size_t bytes_per_frame_per_channel = input_format.GetSampleSize(); + + // A moving pointer that always refers to channel 0 in the input, at the currently handled frame + const uint8_t *base_source = (const uint8_t *)src.data; + + // Grow our reusable buffer, if needed, and set the moving pointer + const size_t result_size = number_of_frames * output_frame_size; + void *const result = output_buffer.Get(result_size); + + // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output + uint8_t *chan_destination = (uint8_t *)result; + + // Perform our copy operations, with N input channels and M output channels + for (unsigned int s=0; s<number_of_frames; ++s) { + + // Need to perform one copy per output channel + for (unsigned int c=0; c<min_output_channels; ++c) { + if (sources[c] == -1 || + (unsigned)sources[c] >= input_format.channels) { + // No source for this destination output, + // give it zeroes as input + memset(chan_destination, + 0x00, + bytes_per_frame_per_channel); + } else { + // Get the data from channel sources[c] + // and copy it to the output + const uint8_t *data = base_source + + (sources[c] * bytes_per_frame_per_channel); + memcpy(chan_destination, + data, + bytes_per_frame_per_channel); + } + // Move on to the next output channel + chan_destination += bytes_per_frame_per_channel; + } + + + // Go on to the next N input samples + base_source += input_frame_size; + } + + // Here it is, ladies and gentlemen! Rerouted data! + return { result, result_size }; +} + +const struct filter_plugin route_filter_plugin = { + "route", + route_filter_init, +}; diff --git a/src/filter/plugins/VolumeFilterPlugin.cxx b/src/filter/plugins/VolumeFilterPlugin.cxx new file mode 100644 index 000000000..7b6ccc51e --- /dev/null +++ b/src/filter/plugins/VolumeFilterPlugin.cxx @@ -0,0 +1,103 @@ +/* + * 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 "VolumeFilterPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/Volume.hxx" +#include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> +#include <string.h> + +class VolumeFilter final : public Filter { + PcmVolume pv; + +public: + unsigned GetVolume() const { + return pv.GetVolume(); + } + + void SetVolume(unsigned _volume) { + pv.SetVolume(_volume); + } + + virtual AudioFormat Open(AudioFormat &af, Error &error) override; + virtual void Close(); + virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, + Error &error) override; +}; + +static constexpr Domain volume_domain("pcm_volume"); + +static Filter * +volume_filter_init(gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new VolumeFilter(); +} + +AudioFormat +VolumeFilter::Open(AudioFormat &audio_format, Error &error) +{ + if (!pv.Open(audio_format.format, error)) + return AudioFormat::Undefined(); + + return audio_format; +} + +void +VolumeFilter::Close() +{ + pv.Close(); +} + +ConstBuffer<void> +VolumeFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error) +{ + return pv.Apply(src); +} + +const struct filter_plugin volume_filter_plugin = { + "volume", + volume_filter_init, +}; + +unsigned +volume_filter_get(const Filter *_filter) +{ + const VolumeFilter *filter = + (const VolumeFilter *)_filter; + + return filter->GetVolume(); +} + +void +volume_filter_set(Filter *_filter, unsigned volume) +{ + VolumeFilter *filter = (VolumeFilter *)_filter; + + filter->SetVolume(volume); +} + diff --git a/src/filter/plugins/VolumeFilterPlugin.hxx b/src/filter/plugins/VolumeFilterPlugin.hxx new file mode 100644 index 000000000..b5317dc6f --- /dev/null +++ b/src/filter/plugins/VolumeFilterPlugin.hxx @@ -0,0 +1,31 @@ +/* + * 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_VOLUME_FILTER_PLUGIN_HXX +#define MPD_VOLUME_FILTER_PLUGIN_HXX + +class Filter; + +unsigned +volume_filter_get(const Filter *filter); + +void +volume_filter_set(Filter *filter, unsigned volume); + +#endif diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx index 37b79a685..30ce7e3a9 100644 --- a/src/fs/AllocatedPath.cxx +++ b/src/fs/AllocatedPath.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 @@ -24,29 +24,32 @@ #include "util/Error.hxx" #include "Compiler.h" +#ifdef HAVE_GLIB #include <glib.h> +#endif -#include <assert.h> #include <string.h> +#ifdef HAVE_GLIB + inline AllocatedPath::AllocatedPath(Donate, pointer _value) :value(_value) { g_free(_value); } +#endif + /* no inlining, please */ AllocatedPath::~AllocatedPath() {} AllocatedPath -AllocatedPath::Build(const_pointer a, const_pointer b) -{ - return AllocatedPath(Donate(), g_build_filename(a, b, nullptr)); -} - -AllocatedPath AllocatedPath::FromUTF8(const char *path_utf8) { +#ifdef HAVE_GLIB return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8)); +#else + return FromFS(path_utf8); +#endif } AllocatedPath @@ -64,7 +67,7 @@ AllocatedPath::FromUTF8(const char *path_utf8, Error &error) AllocatedPath AllocatedPath::GetDirectoryName() const { - return AllocatedPath(Donate(), g_path_get_dirname(c_str())); + return FromFS(PathTraitsFS::GetParent(c_str())); } std::string @@ -82,14 +85,14 @@ AllocatedPath::RelativeFS(const char *other_fs) const other_fs += l; if (*other_fs != 0) { - if (!PathTraits::IsSeparatorFS(*other_fs)) + if (!PathTraitsFS::IsSeparator(*other_fs)) /* mismatch */ return nullptr; /* skip remaining path separators */ do { ++other_fs; - } while (PathTraits::IsSeparatorFS(*other_fs)); + } while (PathTraitsFS::IsSeparator(*other_fs)); } return other_fs; @@ -101,7 +104,7 @@ AllocatedPath::ChopSeparators() size_t l = length(); const char *p = data(); - while (l >= 2 && PathTraits::IsSeparatorFS(p[l - 1])) { + while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) { --l; #if GCC_CHECK_VERSION(4,7) && !defined(__clang__) diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx index 36d8a1598..4fb217547 100644 --- a/src/fs/AllocatedPath.hxx +++ b/src/fs/AllocatedPath.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 @@ -28,8 +28,6 @@ #include <utility> #include <string> -#include <assert.h> - class Error; /** @@ -39,11 +37,10 @@ class Error; * stored. */ class AllocatedPath { - typedef std::string string; - - typedef PathTraits::value_type value_type; - typedef PathTraits::pointer pointer; - typedef PathTraits::const_pointer const_pointer; + typedef PathTraitsFS::string string; + typedef PathTraitsFS::value_type value_type; + typedef PathTraitsFS::pointer pointer; + typedef PathTraitsFS::const_pointer const_pointer; string value; @@ -56,6 +53,12 @@ class AllocatedPath { AllocatedPath(const_pointer _value):value(_value) {} + AllocatedPath(string &&_value):value(std::move(_value)) {} + + static AllocatedPath Build(const_pointer a, size_t a_size, + const_pointer b, size_t b_size) { + return AllocatedPath(PathTraitsFS::Build(a, a_size, b, b_size)); + } public: /** * Copy a #AllocatedPath object. @@ -67,6 +70,8 @@ public: */ AllocatedPath(AllocatedPath &&other):value(std::move(other.value)) {} + explicit AllocatedPath(Path other):value(other.c_str()) {} + ~AllocatedPath(); /** @@ -89,22 +94,38 @@ public: * Join two path components with the path separator. */ gcc_pure gcc_nonnull_all - static AllocatedPath Build(const_pointer a, const_pointer b); + static AllocatedPath Build(const_pointer a, const_pointer b) { + return Build(a, PathTraitsFS::GetLength(a), + b, PathTraitsFS::GetLength(b)); + } gcc_pure gcc_nonnull_all - static AllocatedPath Build(const_pointer a, const AllocatedPath &b) { + static AllocatedPath Build(Path a, const_pointer b) { + return Build(a.c_str(), b); + } + + gcc_pure gcc_nonnull_all + static AllocatedPath Build(Path a, Path b) { return Build(a, b.c_str()); } gcc_pure gcc_nonnull_all + static AllocatedPath Build(const_pointer a, const AllocatedPath &b) { + return Build(a, PathTraitsFS::GetLength(a), + b.value.c_str(), b.value.size()); + } + + gcc_pure gcc_nonnull_all static AllocatedPath Build(const AllocatedPath &a, const_pointer b) { - return Build(a.c_str(), b); + return Build(a.value.c_str(), a.value.size(), + b, PathTraitsFS::GetLength(b)); } gcc_pure static AllocatedPath Build(const AllocatedPath &a, const AllocatedPath &b) { - return Build(a.c_str(), b.c_str()); + return Build(a.value.c_str(), a.value.size(), + b.value.c_str(), b.value.size()); } /** @@ -117,6 +138,15 @@ public: } /** + * Convert a C++ string that is already in the filesystem + * character set to a #Path instance. + */ + gcc_pure + static AllocatedPath FromFS(string &&fs) { + return AllocatedPath(std::move(fs)); + } + + /** * Convert a UTF-8 C string to a #AllocatedPath instance. * Returns return a "nulled" instance on error. */ @@ -215,7 +245,7 @@ public: gcc_pure bool IsAbsolute() { - return PathTraits::IsAbsoluteFS(c_str()); + return PathTraitsFS::IsAbsolute(c_str()); } }; diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx index dad5779f9..2d289c3b8 100644 --- a/src/fs/Charset.cxx +++ b/src/fs/Charset.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,11 +22,14 @@ #include "Domain.hxx" #include "Limits.hxx" #include "system/FatalError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" #include "Log.hxx" +#include "Traits.hxx" +#ifdef HAVE_GLIB #include <glib.h> +#endif + +#include <algorithm> #include <assert.h> #include <string.h> @@ -41,6 +44,7 @@ */ static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1; +#ifdef HAVE_GLIB static std::string fs_charset; gcc_pure @@ -70,10 +74,29 @@ SetFSCharset(const char *charset) "SetFSCharset: fs charset is: %s", fs_charset.c_str()); } +#endif + const char * GetFSCharset() { - return fs_charset.empty() ? "utf-8" : fs_charset.c_str(); +#ifdef HAVE_GLIB + return fs_charset.empty() ? "UTF-8" : fs_charset.c_str(); +#else + return "UTF-8"; +#endif +} + +static inline void FixSeparators(std::string &s) +{ +#ifdef WIN32 + // For whatever reason GCC can't convert constexpr to value reference. + // This leads to link errors when passing separators directly. + auto from = PathTraitsFS::SEPARATOR; + auto to = PathTraitsUTF8::SEPARATOR; + std::replace(s.begin(), s.end(), from, to); +#else + (void)s; +#endif } std::string @@ -81,8 +104,14 @@ PathToUTF8(const char *path_fs) { assert(path_fs != nullptr); - if (fs_charset.empty()) - return std::string(path_fs); +#ifdef HAVE_GLIB + if (fs_charset.empty()) { +#endif + auto result = std::string(path_fs); + FixSeparators(result); + return result; +#ifdef HAVE_GLIB + } GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); if (conv == reinterpret_cast<GIConv>(-1)) @@ -103,9 +132,14 @@ PathToUTF8(const char *path_fs) if (ret == static_cast<size_t>(-1) || in_left > 0) return std::string(); - return std::string(path_utf8, sizeof(path_utf8) - out_left); + auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left); + FixSeparators(result_path); + return result_path; +#endif } +#ifdef HAVE_GLIB + char * PathFromUTF8(const char *path_utf8) { @@ -118,3 +152,5 @@ PathFromUTF8(const char *path_utf8) fs_charset.c_str(), "utf-8", nullptr, nullptr, nullptr); } + +#endif diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx index a89cb0459..0a71d7c58 100644 --- a/src/fs/Charset.hxx +++ b/src/fs/Charset.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/fs/CheckFile.cxx b/src/fs/CheckFile.cxx new file mode 100644 index 000000000..a35443674 --- /dev/null +++ b/src/fs/CheckFile.cxx @@ -0,0 +1,62 @@ +/* + * 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 "CheckFile.hxx" +#include "Log.hxx" +#include "config/ConfigError.hxx" +#include "FileSystem.hxx" +#include "Path.hxx" +#include "AllocatedPath.hxx" +#include "DirectoryReader.hxx" + +#include <errno.h> +#include <sys/stat.h> + +void +CheckDirectoryReadable(Path path_fs) +{ + struct stat st; + if (!StatFile(path_fs, st)) { + FormatErrno(config_domain, + "Failed to stat directory \"%s\"", + path_fs.c_str()); + return; + } + + if (!S_ISDIR(st.st_mode)) { + FormatError(config_domain, + "Not a directory: %s", path_fs.c_str()); + return; + } + +#ifndef WIN32 + const auto x = AllocatedPath::Build(path_fs, "."); + if (!StatFile(x, st) && errno == EACCES) + FormatError(config_domain, + "No permission to traverse (\"execute\") directory: %s", + path_fs.c_str()); +#endif + + const DirectoryReader reader(path_fs); + if (reader.HasFailed() && errno == EACCES) + FormatError(config_domain, + "No permission to read directory: %s", path_fs.c_str()); + +} diff --git a/src/fs/CheckFile.hxx b/src/fs/CheckFile.hxx new file mode 100644 index 000000000..00559647d --- /dev/null +++ b/src/fs/CheckFile.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FS_CHECK_FILE_HXX +#define MPD_FS_CHECK_FILE_HXX + +#include "check.h" + +class Path; + +/** + * Check whether the directory is readable and usable. Logs a warning + * if there is a problem. + */ +void +CheckDirectoryReadable(Path path_fs); + +#endif diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx index 63e64ef99..6aa23005c 100644 --- a/src/fs/Config.cxx +++ b/src/fs/Config.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 @@ -20,24 +20,19 @@ #include "config.h" #include "Config.hxx" #include "Charset.hxx" -#include "Domain.hxx" -#include "ConfigGlobal.hxx" -#include "Log.hxx" -#include "Compiler.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> +#include "config/ConfigGlobal.hxx" #ifdef WIN32 #include <windows.h> // for GetACP() #include <stdio.h> // for sprintf() +#elif defined(HAVE_GLIB) +#include <glib.h> #endif void ConfigureFS() { +#if defined(HAVE_GLIB) || defined(WIN32) const char *charset = nullptr; charset = config_get_string(CONF_FS_CHARSET, nullptr); @@ -62,4 +57,5 @@ ConfigureFS() if (charset != nullptr) SetFSCharset(charset); +#endif } diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx index 9d7035706..d4f1709f5 100644 --- a/src/fs/Config.hxx +++ b/src/fs/Config.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/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx index c9d2c04b8..f77c0629f 100644 --- a/src/fs/DirectoryReader.hxx +++ b/src/fs/DirectoryReader.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 @@ -21,7 +21,7 @@ #define MPD_FS_DIRECTORY_READER_HXX #include "check.h" -#include "AllocatedPath.hxx" +#include "Path.hxx" #include <dirent.h> @@ -78,9 +78,9 @@ public: /** * Extracts directory entry that was previously read by #ReadEntry. */ - AllocatedPath GetEntry() const { + Path GetEntry() const { assert(HasEntry()); - return AllocatedPath::FromFS(ent->d_name); + return Path::FromFS(ent->d_name); } }; diff --git a/src/fs/Domain.cxx b/src/fs/Domain.cxx index 0877bca4c..4f3129219 100644 --- a/src/fs/Domain.cxx +++ b/src/fs/Domain.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 diff --git a/src/fs/Domain.hxx b/src/fs/Domain.hxx index b303570fc..1fd17b37f 100644 --- a/src/fs/Domain.hxx +++ b/src/fs/Domain.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/fs/FileSystem.cxx b/src/fs/FileSystem.cxx index 4cd9f33b2..4e7c87415 100644 --- a/src/fs/FileSystem.cxx +++ b/src/fs/FileSystem.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 diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx index cb2f82d22..4dbb064cb 100644 --- a/src/fs/FileSystem.hxx +++ b/src/fs/FileSystem.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 @@ -28,7 +28,6 @@ #include <sys/stat.h> #include <unistd.h> -#include <assert.h> #include <stdio.h> class AllocatedPath; @@ -37,39 +36,39 @@ namespace FOpenMode { /** * Open mode for reading text files. */ - constexpr PathTraits::const_pointer ReadText = "r"; + constexpr PathTraitsFS::const_pointer ReadText = "r"; /** * Open mode for reading binary files. */ - constexpr PathTraits::const_pointer ReadBinary = "rb"; + constexpr PathTraitsFS::const_pointer ReadBinary = "rb"; /** * Open mode for writing text files. */ - constexpr PathTraits::const_pointer WriteText = "w"; + constexpr PathTraitsFS::const_pointer WriteText = "w"; /** * Open mode for writing binary files. */ - constexpr PathTraits::const_pointer WriteBinary = "wb"; + constexpr PathTraitsFS::const_pointer WriteBinary = "wb"; /** * Open mode for appending text files. */ - constexpr PathTraits::const_pointer AppendText = "a"; + constexpr PathTraitsFS::const_pointer AppendText = "a"; /** * Open mode for appending binary files. */ - constexpr PathTraits::const_pointer AppendBinary = "ab"; + constexpr PathTraitsFS::const_pointer AppendBinary = "ab"; } /** * Wrapper for fopen() that uses #Path names. */ static inline FILE * -FOpen(Path file, PathTraits::const_pointer mode) +FOpen(Path file, PathTraitsFS::const_pointer mode) { return fopen(file.c_str(), mode); } @@ -132,20 +131,28 @@ MakeFifo(Path path, mode_t mode) return mkfifo(path.c_str(), mode) == 0; } -#endif - /** * Wrapper for access() that uses #Path names. */ static inline bool CheckAccess(Path path, int mode) { + return access(path.c_str(), mode) == 0; +} + +#endif + +/** + * Checks is specified path exists and accessible. + */ +static inline bool +CheckAccess(Path path) +{ #ifdef WIN32 - (void)path; - (void)mode; - return true; + struct stat buf; + return StatFile(path, buf); #else - return access(path.c_str(), mode) == 0; + return CheckAccess(path, F_OK); #endif } diff --git a/src/fs/Limits.hxx b/src/fs/Limits.hxx index 480b08851..432897a69 100644 --- a/src/fs/Limits.hxx +++ b/src/fs/Limits.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/fs/Path.cxx b/src/fs/Path.cxx index 0ff0591fb..8288a4fec 100644 --- a/src/fs/Path.cxx +++ b/src/fs/Path.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 @@ -26,25 +26,3 @@ Path::ToUTF8() const { return ::PathToUTF8(c_str()); } - -const char * -Path::RelativeFS(const char *other_fs) const -{ - const size_t l = length(); - if (memcmp(data(), other_fs, l) != 0) - return nullptr; - - other_fs += l; - if (*other_fs != 0) { - if (!PathTraits::IsSeparatorFS(*other_fs)) - /* mismatch */ - return nullptr; - - /* skip remaining path separators */ - do { - ++other_fs; - } while (PathTraits::IsSeparatorFS(*other_fs)); - } - - return other_fs; -} diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx index 6ea954577..9e0fa5aeb 100644 --- a/src/fs/Path.hxx +++ b/src/fs/Path.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 @@ -29,8 +29,6 @@ #include <assert.h> #include <string.h> -class Error; - /** * A path name in the native file system character set. * @@ -38,9 +36,9 @@ class Error; * instance lives, the string must not be invalidated. */ class Path { - typedef PathTraits::value_type value_type; - typedef PathTraits::pointer pointer; - typedef PathTraits::const_pointer const_pointer; + typedef PathTraitsFS::value_type value_type; + typedef PathTraitsFS::pointer pointer; + typedef PathTraitsFS::const_pointer const_pointer; const char *value; @@ -137,11 +135,13 @@ public: * nullptr on mismatch. */ gcc_pure - const char *RelativeFS(const char *other_fs) const; + const char *RelativeFS(const char *other_fs) const { + return PathTraitsFS::Relative(value, other_fs); + } gcc_pure bool IsAbsolute() { - return PathTraits::IsAbsoluteFS(c_str()); + return PathTraitsFS::IsAbsolute(c_str()); } }; diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx new file mode 100644 index 000000000..7a836f906 --- /dev/null +++ b/src/fs/StandardDirectory.cxx @@ -0,0 +1,327 @@ +/* + * 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" + +// Use X Desktop guidelines where applicable +#if !defined(__APPLE__) && !defined(WIN32) && !defined(ANDROID) +#define USE_XDG +#endif + +#include "StandardDirectory.hxx" +#include "FileSystem.hxx" + +#include <array> + +#ifdef WIN32 +#include <windows.h> +#include <shlobj.h> +#else +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <pwd.h> +#endif + +#ifdef USE_XDG +#include "util/Error.hxx" +#include "util/StringUtil.hxx" +#include "io/TextFile.hxx" +#include <string.h> +#include <utility> +#endif + +#ifdef ANDROID +#include "java/Global.hxx" +#include "android/Environment.hxx" +#include "android/Context.hxx" +#include "Main.hxx" +#endif + +#ifndef WIN32 +class PasswdEntry +{ +#if defined(HAVE_GETPWNAM_R) || defined(HAVE_GETPWUID_R) + std::array<char, 16 * 1024> buf; + passwd pw; +#endif + + passwd *result; +public: + PasswdEntry() : result(nullptr) { } + + bool ReadByName(const char *name) { +#ifdef HAVE_GETPWNAM_R + getpwnam_r(name, &pw, buf.data(), buf.size(), &result); +#else + result = getpwnam(name); +#endif + return result != nullptr; + } + + bool ReadByUid(uid_t uid) { +#ifdef HAVE_GETPWUID_R + getpwuid_r(uid, &pw, buf.data(), buf.size(), &result); +#else + result = getpwuid(uid); +#endif + return result != nullptr; + } + + const passwd *operator->() { + assert(result != nullptr); + return result; + } +}; +#endif + +static inline bool IsValidPathString(PathTraitsFS::const_pointer path) +{ + return path != nullptr && *path != '\0'; +} + +static inline bool IsValidDir(PathTraitsFS::const_pointer dir) +{ + return PathTraitsFS::IsAbsolute(dir) && + DirectoryExists(Path::FromFS(dir)); +} + +static inline AllocatedPath SafePathFromFS(PathTraitsFS::const_pointer dir) +{ + if (IsValidPathString(dir) && IsValidDir(dir)) + return AllocatedPath::FromFS(dir); + return AllocatedPath::Null(); +} + +#ifdef WIN32 +static AllocatedPath GetStandardDir(int folder_id) +{ + std::array<char, MAX_PATH> dir; + auto ret = SHGetFolderPath(nullptr, folder_id | CSIDL_FLAG_DONT_VERIFY, + nullptr, SHGFP_TYPE_CURRENT, dir.data()); + if (FAILED(ret)) + return AllocatedPath::Null(); + return SafePathFromFS(dir.data()); +} +#endif + +#ifdef USE_XDG + +static const char home_prefix[] = "$HOME/"; + +static bool +ParseConfigLine(char *line, const char *dir_name, AllocatedPath &result_dir) +{ + // strip leading white space + line = StripLeft(line); + + // check for end-of-line or comment + if (*line == '\0' || *line == '#') + return false; + + // check if current setting is for requested dir + if (!StringStartsWith(line, dir_name)) + return false; + line += strlen(dir_name); + + // strip equals sign and spaces around it + line = StripLeft(line); + if (*line != '=') + return false; + ++line; + line = StripLeft(line); + + // check if path is quoted + bool quoted = false; + if (*line == '"') { + ++line; + quoted = true; + } + + // check if path is relative to $HOME + bool home_relative = false; + if (StringStartsWith(line, home_prefix)) { + line += strlen(home_prefix); + home_relative = true; + } + + + char *line_end; + // find end of the string + if (quoted) { + line_end = strrchr(line, '"'); + if (line_end == nullptr) + return true; + } else { + line_end = StripRight(line, line + strlen(line)); + } + + // check for empty result + if (line == line_end) + return true; + + *line_end = 0; + + // build the result path + const char *path = line; + + auto result = AllocatedPath::Null(); + if (home_relative) { + auto home = GetHomeDir(); + if (home.IsNull()) + return true; + result = AllocatedPath::Build(home, path); + } else { + result = AllocatedPath::FromFS(path); + } + + if (IsValidDir(result.c_str())) { + result_dir = std::move(result); + return true; + } + return true; +} + +static AllocatedPath GetUserDir(const char *name) +{ + auto result = AllocatedPath::Null(); + auto config_dir = GetUserConfigDir(); + if (config_dir.IsNull()) + return result; + auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs"); + TextFile input(dirs_file, IgnoreError()); + if (input.HasFailed()) + return result; + char *line; + while ((line = input.ReadLine()) != nullptr) + if (ParseConfigLine(line, name, result)) + return result; + return result; +} + +#endif + +AllocatedPath GetUserConfigDir() +{ +#if defined(WIN32) + return GetStandardDir(CSIDL_LOCAL_APPDATA); +#elif defined(USE_XDG) + // Check for $XDG_CONFIG_HOME + auto config_home = getenv("XDG_CONFIG_HOME"); + if (IsValidPathString(config_home) && IsValidDir(config_home)) + return AllocatedPath::FromFS(config_home); + + // Check for $HOME/.config + auto home = GetHomeDir(); + if (!home.IsNull()) { + AllocatedPath fallback = AllocatedPath::Build(home, ".config"); + if (IsValidDir(fallback.c_str())) + return fallback; + } + + return AllocatedPath::Null(); +#else + return AllocatedPath::Null(); +#endif +} + +AllocatedPath GetUserMusicDir() +{ +#if defined(WIN32) + return GetStandardDir(CSIDL_MYMUSIC); +#elif defined(USE_XDG) + return GetUserDir("XDG_MUSIC_DIR"); +#elif defined(ANDROID) + return Environment::getExternalStoragePublicDirectory("Music"); +#else + return AllocatedPath::Null(); +#endif +} + +AllocatedPath GetUserCacheDir() +{ +#ifdef USE_XDG + // Check for $XDG_CACHE_HOME + auto cache_home = getenv("XDG_CACHE_HOME"); + if (IsValidPathString(cache_home) && IsValidDir(cache_home)) + return AllocatedPath::FromFS(cache_home); + + // Check for $HOME/.cache + auto home = GetHomeDir(); + if (!home.IsNull()) { + AllocatedPath fallback = AllocatedPath::Build(home, ".cache"); + if (IsValidDir(fallback.c_str())) + return fallback; + } + + return AllocatedPath::Null(); +#elif defined(ANDROID) + return context->GetCacheDir(Java::GetEnv()); +#else + return AllocatedPath::Null(); +#endif +} + +#ifdef WIN32 + +AllocatedPath GetSystemConfigDir() +{ + return GetStandardDir(CSIDL_COMMON_APPDATA); +} + +AllocatedPath GetAppBaseDir() +{ + std::array<char, MAX_PATH> app; + auto ret = GetModuleFileName(nullptr, app.data(), app.size()); + + // Check for error + if (ret == 0) + return AllocatedPath::Null(); + + // Check for truncation + if (ret == app.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + return AllocatedPath::Null(); + + auto app_path = AllocatedPath::FromFS(app.data()); + return app_path.GetDirectoryName().GetDirectoryName(); +} + +#else + +AllocatedPath GetHomeDir() +{ + auto home = getenv("HOME"); + if (IsValidPathString(home) && IsValidDir(home)) + return AllocatedPath::FromFS(home); + PasswdEntry pw; + if (pw.ReadByUid(getuid())) + return SafePathFromFS(pw->pw_dir); + return AllocatedPath::Null(); +} + +AllocatedPath GetHomeDir(const char *user_name) +{ + assert(user_name != nullptr); + PasswdEntry pw; + if (pw.ReadByName(user_name)) + return SafePathFromFS(pw->pw_dir); + return AllocatedPath::Null(); +} + +#endif diff --git a/src/fs/StandardDirectory.hxx b/src/fs/StandardDirectory.hxx new file mode 100644 index 000000000..e3fba375a --- /dev/null +++ b/src/fs/StandardDirectory.hxx @@ -0,0 +1,71 @@ +/* + * 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_FS_STANDARD_DIRECTORY_HXX +#define MPD_FS_STANDARD_DIRECTORY_HXX + +#include "check.h" +#include "AllocatedPath.hxx" + +/** + * Obtains configuration directory for the current user. + */ +AllocatedPath GetUserConfigDir(); + +/** + * Obtains music directory for the current user. + */ +AllocatedPath GetUserMusicDir(); + +/** + * Obtains cache directory for the current user. + */ +gcc_pure +AllocatedPath +GetUserCacheDir(); + +#ifdef WIN32 + +/** + * Obtains system configuration directory. + */ +AllocatedPath GetSystemConfigDir(); + +/** + * Obtains application application base directory. + * Application base directory is a directory that contains 'bin' folder + * for current executable. + */ +AllocatedPath GetAppBaseDir(); + +#else + +/** + * Obtains home directory for the current user. + */ +AllocatedPath GetHomeDir(); + +/** + * Obtains home directory for the specified user. + */ +AllocatedPath GetHomeDir(const char *user_name); + +#endif + +#endif diff --git a/src/fs/Traits.cxx b/src/fs/Traits.cxx index 2c3ce075b..d62987087 100644 --- a/src/fs/Traits.cxx +++ b/src/fs/Traits.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,24 +22,136 @@ #include <string.h> -const char * -PathTraits::GetBaseUTF8(const char *p) +template<typename Traits> +typename Traits::string +BuildPathImpl(typename Traits::const_pointer a, size_t a_size, + typename Traits::const_pointer b, size_t b_size) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a_size == 0) + return typename Traits::string(b, b_size); + if (b_size == 0) + return typename Traits::string(a, a_size); + + typename Traits::string result(a, a_size); + + if (!Traits::IsSeparator(a[a_size - 1])) + result.push_back(Traits::SEPARATOR); + + if (Traits::IsSeparator(b[0])) + result.append(b + 1, b_size - 1); + else + result.append(b, b_size); + + return result; +} + +template<typename Traits> +typename Traits::const_pointer +GetBasePathImpl(typename Traits::const_pointer p) { assert(p != nullptr); - const char *slash = strrchr(p, SEPARATOR_UTF8); - return slash != nullptr - ? slash + 1 + typename Traits::const_pointer sep = Traits::FindLastSeparator(p); + return sep != nullptr + ? sep + 1 : p; } -std::string -PathTraits::GetParentUTF8(const char *p) +template<typename Traits> +typename Traits::string +GetParentPathImpl(typename Traits::const_pointer p) { assert(p != nullptr); - const char *slash = strrchr(p, SEPARATOR_UTF8); - return slash != nullptr - ? std::string(p, slash) - : std::string("."); + typename Traits::const_pointer sep = Traits::FindLastSeparator(p); + if (sep == nullptr) + return typename Traits::string("."); + if (sep == p) + return typename Traits::string(p, p + 1); +#ifdef WIN32 + if (Traits::IsDrive(p) && sep == p + 2) + return typename Traits::string(p, p + 3); +#endif + return typename Traits::string(p, sep); +} + +template<typename Traits> +typename Traits::const_pointer +RelativePathImpl(typename Traits::const_pointer base, + typename Traits::const_pointer other) +{ + assert(base != nullptr); + assert(other != nullptr); + + const auto base_length = Traits::GetLength(base); + if (memcmp(base, other, base_length * sizeof(*base)) != 0) + /* mismatch */ + return nullptr; + + other += base_length; + if (other != 0) { + if (!Traits::IsSeparator(*other)) + /* mismatch */ + return nullptr; + + /* skip remaining path separators */ + do { + ++other; + } while (Traits::IsSeparator(*other)); + } + + return other; +} + +PathTraitsFS::string +PathTraitsFS::Build(PathTraitsFS::const_pointer a, size_t a_size, + PathTraitsFS::const_pointer b, size_t b_size) +{ + return BuildPathImpl<PathTraitsFS>(a, a_size, b, b_size); +} + +PathTraitsFS::const_pointer +PathTraitsFS::GetBase(PathTraitsFS::const_pointer p) +{ + return GetBasePathImpl<PathTraitsFS>(p); +} + +PathTraitsFS::string +PathTraitsFS::GetParent(PathTraitsFS::const_pointer p) +{ + return GetParentPathImpl<PathTraitsFS>(p); +} + +PathTraitsFS::const_pointer +PathTraitsFS::Relative(const_pointer base, const_pointer other) +{ + return RelativePathImpl<PathTraitsFS>(base, other); +} + +PathTraitsUTF8::string +PathTraitsUTF8::Build(PathTraitsUTF8::const_pointer a, size_t a_size, + PathTraitsUTF8::const_pointer b, size_t b_size) +{ + return BuildPathImpl<PathTraitsUTF8>(a, a_size, b, b_size); +} + +PathTraitsUTF8::const_pointer +PathTraitsUTF8::GetBase(PathTraitsUTF8::const_pointer p) +{ + return GetBasePathImpl<PathTraitsUTF8>(p); +} + +PathTraitsUTF8::string +PathTraitsUTF8::GetParent(PathTraitsUTF8::const_pointer p) +{ + return GetParentPathImpl<PathTraitsUTF8>(p); +} + +PathTraitsUTF8::const_pointer +PathTraitsUTF8::Relative(const_pointer base, const_pointer other) +{ + return RelativePathImpl<PathTraitsUTF8>(base, other); } diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx index 244ab8b5c..88715c3e8 100644 --- a/src/fs/Traits.hxx +++ b/src/fs/Traits.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 @@ -24,67 +24,153 @@ #include "Compiler.h" #ifdef WIN32 -#include <glib.h> +#include "util/CharUtil.hxx" #endif #include <string> +#include <string.h> #include <assert.h> -class Error; - /** - * This class describes the nature of a filesystem path. + * This class describes the nature of a native filesystem path. */ -struct PathTraits { +struct PathTraitsFS { + typedef std::string string; typedef char value_type; typedef char *pointer; typedef const char *const_pointer; #ifdef WIN32 - static constexpr value_type SEPARATOR_FS = '\\'; - static constexpr char SEPARATOR_UTF8 = '/'; + static constexpr value_type SEPARATOR = '\\'; #else - static constexpr value_type SEPARATOR_FS = '/'; - static constexpr char SEPARATOR_UTF8 = '/'; + static constexpr value_type SEPARATOR = '/'; #endif - static constexpr bool IsSeparatorFS(value_type ch) { + static constexpr bool IsSeparator(value_type ch) { return #ifdef WIN32 ch == '/' || #endif - ch == SEPARATOR_FS; + ch == SEPARATOR; } - static constexpr bool IsSeparatorUTF8(char ch) { - return + gcc_pure gcc_nonnull_all + static const_pointer FindLastSeparator(const_pointer p) { + assert(p != nullptr); #ifdef WIN32 - ch == '/' || + const_pointer pos = p + GetLength(p); + while (p != pos && !IsSeparator(*pos)) + --pos; + return IsSeparator(*pos) ? pos : nullptr; +#else + return strrchr(p, SEPARATOR); #endif - ch == SEPARATOR_UTF8; } - gcc_pure - static bool IsAbsoluteFS(const_pointer p) { - assert(p != nullptr); +#ifdef WIN32 + gcc_pure gcc_nonnull_all + static constexpr bool IsDrive(const_pointer p) { + return IsAlphaASCII(p[0]) && p[1] == ':'; + } +#endif + gcc_pure gcc_nonnull_all + static bool IsAbsolute(const_pointer p) { + assert(p != nullptr); #ifdef WIN32 - return g_path_is_absolute(p); -#else - return IsSeparatorFS(*p); + if (IsDrive(p) && IsSeparator(p[2])) + return true; #endif + return IsSeparator(*p); } - gcc_pure - static bool IsAbsoluteUTF8(const char *p) { + gcc_pure gcc_nonnull_all + static size_t GetLength(const_pointer p) { + return strlen(p); + } + + /** + * Determine the "base" file name of the given native path. + * The return value points inside the given string. + */ + gcc_pure gcc_nonnull_all + static const_pointer GetBase(const_pointer p); + + /** + * Determine the "parent" file name of the given native path. + * As a special case, returns the string "." if there is no + * separator in the given input string. + */ + gcc_pure gcc_nonnull_all + static string GetParent(const_pointer p); + + /** + * Determine the relative part of the given path to this + * object, not including the directory separator. Returns an + * empty string if the given path equals this object or + * nullptr on mismatch. + */ + gcc_pure gcc_nonnull_all + static const_pointer Relative(const_pointer base, const_pointer other); + + /** + * Constructs the path from the given components. + * If either of the components is empty string, + * remaining component is returned unchanged. + * If both components are empty strings, empty string is returned. + */ + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, size_t a_size, + const_pointer b, size_t b_size); + + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, const_pointer b) { + return Build(a, GetLength(a), b, GetLength(b)); + } +}; + +/** + * This class describes the nature of a MPD internal filesystem path. + */ +struct PathTraitsUTF8 { + typedef std::string string; + typedef char value_type; + typedef char *pointer; + typedef const char *const_pointer; + + static constexpr value_type SEPARATOR = '/'; + + static constexpr bool IsSeparator(value_type ch) { + return ch == SEPARATOR; + } + + gcc_pure gcc_nonnull_all + static const_pointer FindLastSeparator(const_pointer p) { assert(p != nullptr); + return strrchr(p, SEPARATOR); + } #ifdef WIN32 - return g_path_is_absolute(p); -#else - return IsSeparatorUTF8(*p); + gcc_pure gcc_nonnull_all + static constexpr bool IsDrive(const_pointer p) { + return IsAlphaASCII(p[0]) && p[1] == ':'; + } +#endif + + gcc_pure gcc_nonnull_all + static bool IsAbsolute(const_pointer p) { + assert(p != nullptr); +#ifdef WIN32 + if (IsDrive(p) && IsSeparator(p[2])) + return true; #endif + return IsSeparator(*p); + } + + gcc_pure gcc_nonnull_all + static size_t GetLength(const_pointer p) { + return strlen(p); } /** @@ -92,7 +178,7 @@ struct PathTraits { * The return value points inside the given string. */ gcc_pure gcc_nonnull_all - static const char *GetBaseUTF8(const char *p); + static const_pointer GetBase(const_pointer p); /** * Determine the "parent" file name of the given UTF-8 path. @@ -100,7 +186,31 @@ struct PathTraits { * separator in the given input string. */ gcc_pure gcc_nonnull_all - static std::string GetParentUTF8(const char *p); + static string GetParent(const_pointer p); + + /** + * Determine the relative part of the given path to this + * object, not including the directory separator. Returns an + * empty string if the given path equals this object or + * nullptr on mismatch. + */ + gcc_pure gcc_nonnull_all + static const_pointer Relative(const_pointer base, const_pointer other); + + /** + * Constructs the path from the given components. + * If either of the components is empty string, + * remaining component is returned unchanged. + * If both components are empty strings, empty string is returned. + */ + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, size_t a_size, + const_pointer b, size_t b_size); + + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, const_pointer b) { + return Build(a, GetLength(a), b, GetLength(b)); + } }; #endif diff --git a/src/fs/io/AutoGunzipReader.cxx b/src/fs/io/AutoGunzipReader.cxx new file mode 100644 index 000000000..2552f7b99 --- /dev/null +++ b/src/fs/io/AutoGunzipReader.cxx @@ -0,0 +1,70 @@ +/* + * 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 "AutoGunzipReader.hxx" +#include "GunzipReader.hxx" +#include "util/Error.hxx" + +AutoGunzipReader::~AutoGunzipReader() +{ + delete gunzip; +} + +gcc_pure +static bool +IsGzip(const uint8_t data[4]) +{ + return data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08 && + (data[3] & 0xe0) == 0; +} + +inline bool +AutoGunzipReader::Detect(Error &error) +{ + const uint8_t *data = (const uint8_t *)peek.Peek(4, error); + if (data == nullptr) { + if (error.IsDefined()) + return false; + + next = &peek; + return true; + } + + if (IsGzip(data)) { + gunzip = new GunzipReader(peek, error); + if (!gunzip->IsDefined()) + return false; + + + next = gunzip; + } else + next = &peek; + + return true; +} + +size_t +AutoGunzipReader::Read(void *data, size_t size, Error &error) +{ + if (next == nullptr && !Detect(error)) + return false; + + return next->Read(data, size, error); +} diff --git a/src/fs/io/AutoGunzipReader.hxx b/src/fs/io/AutoGunzipReader.hxx new file mode 100644 index 000000000..0efd9d56c --- /dev/null +++ b/src/fs/io/AutoGunzipReader.hxx @@ -0,0 +1,51 @@ +/* + * 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_AUTO_GUNZIP_READER_HXX +#define MPD_AUTO_GUNZIP_READER_HXX + +#include "check.h" +#include "PeekReader.hxx" + +#include <stdint.h> + +class GunzipReader; + +/** + * A filter that detects gzip compression and optionally inserts a + * #GunzipReader. + */ +class AutoGunzipReader final : public Reader { + Reader *next; + PeekReader peek; + GunzipReader *gunzip; + +public: + AutoGunzipReader(Reader &_next) + :next(nullptr), peek(_next), gunzip(nullptr) {} + ~AutoGunzipReader(); + + /* virtual methods from class Reader */ + virtual size_t Read(void *data, size_t size, Error &error) override; + +private: + bool Detect(Error &error); +}; + +#endif diff --git a/src/fs/io/BufferedOutputStream.cxx b/src/fs/io/BufferedOutputStream.cxx new file mode 100644 index 000000000..088a3e279 --- /dev/null +++ b/src/fs/io/BufferedOutputStream.cxx @@ -0,0 +1,143 @@ +/* + * 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 "BufferedOutputStream.hxx" +#include "OutputStream.hxx" + +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +bool +BufferedOutputStream::AppendToBuffer(const void *data, size_t size) +{ + auto r = buffer.Write(); + if (r.size < size) + return false; + + memcpy(r.data, data, size); + buffer.Append(size); + return true; +} + +bool +BufferedOutputStream::Write(const void *data, size_t size) +{ + if (gcc_unlikely(last_error.IsDefined())) + return false; + + if (AppendToBuffer(data, size)) + return true; + + if (!Flush()) + return false; + + if (AppendToBuffer(data, size)) + return true; + + return os.Write(data, size, last_error); +} + +bool +BufferedOutputStream::Write(const char *p) +{ + return Write(p, strlen(p)); +} + +bool +BufferedOutputStream::Format(const char *fmt, ...) +{ + if (gcc_unlikely(last_error.IsDefined())) + return false; + + auto r = buffer.Write(); + if (r.IsEmpty()) { + if (!Flush()) + return false; + + r = buffer.Write(); + } + + /* format into the buffer */ + va_list ap; + va_start(ap, fmt); + size_t size = vsnprintf(r.data, r.size, fmt, ap); + va_end(ap); + + if (gcc_unlikely(size >= r.size)) { + /* buffer was not large enough; flush it and try + again */ + + if (!Flush()) + return false; + + r = buffer.Write(); + + if (gcc_unlikely(size >= r.size)) { + /* still not enough space: grow the buffer and + try again */ + r.size = size + 1; + r.data = buffer.Write(r.size); + } + + /* format into the new buffer */ + va_start(ap, fmt); + size = vsnprintf(r.data, r.size, fmt, ap); + va_end(ap); + + /* this time, it must fit */ + assert(size < r.size); + } + + buffer.Append(size); + return true; +} + +bool +BufferedOutputStream::Flush() +{ + if (!Check()) + return false; + + auto r = buffer.Read(); + if (r.IsEmpty()) + return true; + + bool success = os.Write(r.data, r.size, last_error); + if (gcc_likely(success)) + buffer.Consume(r.size); + return success; +} + +bool +BufferedOutputStream::Flush(Error &error) +{ + if (!Check(error)) + return false; + + auto r = buffer.Read(); + if (r.IsEmpty()) + return true; + + bool success = os.Write(r.data, r.size, error); + if (gcc_likely(success)) + buffer.Consume(r.size); + return success; +} diff --git a/src/fs/io/BufferedOutputStream.hxx b/src/fs/io/BufferedOutputStream.hxx new file mode 100644 index 000000000..f2de758a2 --- /dev/null +++ b/src/fs/io/BufferedOutputStream.hxx @@ -0,0 +1,71 @@ +/* + * 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_BUFFERED_OUTPUT_STREAM_HXX +#define MPD_BUFFERED_OUTPUT_STREAM_HXX + +#include "check.h" +#include "Compiler.h" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" + +#include <stddef.h> + +class OutputStream; +class Error; + +class BufferedOutputStream { + OutputStream &os; + + DynamicFifoBuffer<char> buffer; + + Error last_error; + +public: + BufferedOutputStream(OutputStream &_os) + :os(_os), buffer(32768) {} + + bool Write(const void *data, size_t size); + bool Write(const char *p); + + gcc_printf(2,3) + bool Format(const char *fmt, ...); + + gcc_pure + bool Check() const { + return !last_error.IsDefined(); + } + + bool Check(Error &error) const { + if (last_error.IsDefined()) { + error.Set(last_error); + return false; + } else + return true; + } + + bool Flush(); + + bool Flush(Error &error); + +private: + bool AppendToBuffer(const void *data, size_t size); +}; + +#endif diff --git a/src/fs/io/BufferedReader.cxx b/src/fs/io/BufferedReader.cxx new file mode 100644 index 000000000..ba2f17dcf --- /dev/null +++ b/src/fs/io/BufferedReader.cxx @@ -0,0 +1,82 @@ +/* + * 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 "BufferedReader.hxx" +#include "Reader.hxx" +#include "util/TextFile.hxx" + +bool +BufferedReader::Fill(bool need_more) +{ + if (gcc_unlikely(last_error.IsDefined())) + return false; + + if (eof) + return !need_more; + + auto w = buffer.Write(); + if (w.IsEmpty()) { + if (buffer.GetCapacity() >= MAX_SIZE) + return !need_more; + + buffer.Grow(buffer.GetCapacity() * 2); + w = buffer.Write(); + assert(!w.IsEmpty()); + } + + size_t nbytes = reader.Read(w.data, w.size, last_error); + if (nbytes == 0) { + if (gcc_unlikely(last_error.IsDefined())) + return false; + + eof = true; + return !need_more; + } + + buffer.Append(nbytes); + return true; +} + +char * +BufferedReader::ReadLine() +{ + do { + char *line = ReadBufferedLine(buffer); + if (line != nullptr) + return line; + } while (Fill(true)); + + if (last_error.IsDefined() || !eof || buffer.IsEmpty()) + return nullptr; + + auto w = buffer.Write(); + if (w.IsEmpty()) { + buffer.Grow(buffer.GetCapacity() + 1); + w = buffer.Write(); + assert(!w.IsEmpty()); + } + + /* terminate the last line */ + w[0] = 0; + + char *line = buffer.Read().data; + buffer.Clear(); + return line; +} diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx new file mode 100644 index 000000000..61cc8df83 --- /dev/null +++ b/src/fs/io/BufferedReader.hxx @@ -0,0 +1,75 @@ +/* + * 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_BUFFERED_READER_HXX +#define MPD_BUFFERED_READER_HXX + +#include "check.h" +#include "Compiler.h" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" + +#include <stddef.h> + +class Reader; +class Error; + +class BufferedReader { + static constexpr size_t MAX_SIZE = 512 * 1024; + + Reader &reader; + + DynamicFifoBuffer<char> buffer; + + Error last_error; + + bool eof; + +public: + BufferedReader(Reader &_reader) + :reader(_reader), buffer(4096), eof(false) {} + + gcc_pure + bool Check() const { + return !last_error.IsDefined(); + } + + bool Check(Error &error) const { + if (last_error.IsDefined()) { + error.Set(last_error); + return false; + } else + return true; + } + + bool Fill(bool need_more); + + gcc_pure + WritableBuffer<void> Read() const { + return buffer.Read().ToVoid(); + } + + void Consume(size_t n) { + buffer.Consume(n); + } + + char *ReadLine(); +}; + +#endif diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx new file mode 100644 index 000000000..dc4456d1f --- /dev/null +++ b/src/fs/io/FileOutputStream.cxx @@ -0,0 +1,135 @@ +/* + * 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 "FileOutputStream.hxx" +#include "fs/FileSystem.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" + +#ifdef WIN32 + +FileOutputStream::FileOutputStream(Path _path, Error &error) + :path(_path), + handle(CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, + TRUNCATE_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr)) +{ + if (handle == INVALID_HANDLE_VALUE) + error.FormatLastError("Failed to create %s", path.c_str()); +} + +bool +FileOutputStream::Write(const void *data, size_t size, Error &error) +{ + assert(IsDefined()); + + DWORD nbytes; + if (!WriteFile(handle, data, size, &nbytes, nullptr)) { + error.FormatLastError("Failed to write to %s", path.c_str()); + return false; + } + + if (size_t(nbytes) != size) { + error.FormatLastError(ERROR_DISK_FULL, + "Failed to write to %s", path.c_str()); + return false; + } + + return true; +} + +bool +FileOutputStream::Commit(gcc_unused Error &error) +{ + assert(IsDefined()); + + CloseHandle(handle); + return true; +} + +void +FileOutputStream::Cancel() +{ + assert(IsDefined()); + + CloseHandle(handle); + RemoveFile(path); +} + +#else + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +FileOutputStream::FileOutputStream(Path _path, Error &error) + :path(_path), + fd(open_cloexec(path.c_str(), + O_WRONLY|O_CREAT|O_TRUNC, + 0666)) +{ + if (fd < 0) + error.FormatErrno("Failed to create %s", path.c_str()); +} + +bool +FileOutputStream::Write(const void *data, size_t size, Error &error) +{ + assert(IsDefined()); + + ssize_t nbytes = write(fd, data, size); + if (nbytes < 0) { + error.FormatErrno("Failed to write to %s", path.c_str()); + return false; + } else if ((size_t)nbytes < size) { + error.FormatErrno(ENOSPC, + "Failed to write to %s", path.c_str()); + return false; + } + + return true; +} + +bool +FileOutputStream::Commit(Error &error) +{ + assert(IsDefined()); + + bool success = close(fd) == 0; + fd = -1; + if (!success) + error.FormatErrno("Failed to commit %s", path.c_str()); + + return success; +} + +void +FileOutputStream::Cancel() +{ + assert(IsDefined()); + + close(fd); + fd = -1; + + RemoveFile(path); +} + +#endif diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx new file mode 100644 index 000000000..68174ec83 --- /dev/null +++ b/src/fs/io/FileOutputStream.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FILE_OUTPUT_STREAM_HXX +#define MPD_FILE_OUTPUT_STREAM_HXX + +#include "check.h" +#include "OutputStream.hxx" +#include "fs/AllocatedPath.hxx" + +#include <assert.h> + +#ifdef WIN32 +#include <windows.h> +#endif + +class Path; + +class FileOutputStream final : public OutputStream { + AllocatedPath path; + +#ifdef WIN32 + HANDLE handle; +#else + int fd; +#endif + +public: + FileOutputStream(Path _path, Error &error); + + ~FileOutputStream() { + if (IsDefined()) + Cancel(); + } + + + bool IsDefined() const { +#ifdef WIN32 + return handle != INVALID_HANDLE_VALUE; +#else + return fd >= 0; +#endif + } + + bool Commit(Error &error); + void Cancel(); + + /* virtual methods from class OutputStream */ + bool Write(const void *data, size_t size, Error &error) override; +}; + +#endif diff --git a/src/fs/io/FileReader.cxx b/src/fs/io/FileReader.cxx new file mode 100644 index 000000000..d63cd8ab0 --- /dev/null +++ b/src/fs/io/FileReader.cxx @@ -0,0 +1,98 @@ +/* + * 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 "FileReader.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" + +#ifdef WIN32 + +FileReader::FileReader(Path _path, Error &error) + :path(_path), + handle(CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr)) +{ + if (handle == INVALID_HANDLE_VALUE) + error.FormatLastError("Failed to open %s", path.c_str()); +} + +size_t +FileReader::Read(void *data, size_t size, Error &error) +{ + assert(IsDefined()); + + DWORD nbytes; + if (!ReadFile(handle, data, size, &nbytes, nullptr)) { + error.FormatLastError("Failed to read from %s", path.c_str()); + nbytes = 0; + } + + return nbytes; +} + +void +FileReader::Close() +{ + assert(IsDefined()); + + CloseHandle(handle); +} + +#else + +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +FileReader::FileReader(Path _path, Error &error) + :path(_path), + fd(open_cloexec(path.c_str(), + O_RDONLY, + 0)) +{ + if (fd < 0) + error.FormatErrno("Failed to open %s", path.c_str()); +} + +size_t +FileReader::Read(void *data, size_t size, Error &error) +{ + assert(IsDefined()); + + ssize_t nbytes = read(fd, data, size); + if (nbytes < 0) { + error.FormatErrno("Failed to read from %s", path.c_str()); + nbytes = 0; + } + + return nbytes; +} + +void +FileReader::Close() +{ + assert(IsDefined()); + + close(fd); + fd = -1; +} + +#endif diff --git a/src/fs/io/FileReader.hxx b/src/fs/io/FileReader.hxx new file mode 100644 index 000000000..34b43943c --- /dev/null +++ b/src/fs/io/FileReader.hxx @@ -0,0 +1,67 @@ +/* + * 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_FILE_READER_HXX +#define MPD_FILE_READER_HXX + +#include "check.h" +#include "Reader.hxx" +#include "fs/AllocatedPath.hxx" + +#include <assert.h> + +#ifdef WIN32 +#include <windows.h> +#endif + +class Path; + +class FileReader final : public Reader { + AllocatedPath path; + +#ifdef WIN32 + HANDLE handle; +#else + int fd; +#endif + +public: + FileReader(Path _path, Error &error); + + ~FileReader() { + if (IsDefined()) + Close(); + } + + + bool IsDefined() const { +#ifdef WIN32 + return handle != INVALID_HANDLE_VALUE; +#else + return fd >= 0; +#endif + } + + void Close(); + + /* virtual methods from class Reader */ + size_t Read(void *data, size_t size, Error &error) override; +}; + +#endif diff --git a/src/fs/io/GunzipReader.cxx b/src/fs/io/GunzipReader.cxx new file mode 100644 index 000000000..ad5e41784 --- /dev/null +++ b/src/fs/io/GunzipReader.cxx @@ -0,0 +1,101 @@ +/* + * 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 "GunzipReader.hxx" +#include "lib/zlib/Domain.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +GunzipReader::GunzipReader(Reader &_next, Error &error) + :next(_next), eof(false) +{ + z.next_in = nullptr; + z.avail_in = 0; + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + + int result = inflateInit2(&z, 16 + MAX_WBITS); + if (result != Z_OK) { + z.opaque = this; + error.Set(zlib_domain, result, zError(result)); + } +} + +GunzipReader::~GunzipReader() +{ + if (IsDefined()) + inflateEnd(&z); +} + +inline bool +GunzipReader::FillBuffer(Error &error) +{ + auto w = buffer.Write(); + assert(!w.IsEmpty()); + + size_t nbytes = next.Read(w.data, w.size, error); + if (nbytes == 0) + return false; + + buffer.Append(nbytes); + return true; +} + +size_t +GunzipReader::Read(void *data, size_t size, Error &error) +{ + if (eof) + return 0; + + z.next_out = (Bytef *)data; + z.avail_out = size; + + while (true) { + int flush = Z_NO_FLUSH; + + auto r = buffer.Read(); + if (r.IsEmpty()) { + if (FillBuffer(error)) + r = buffer.Read(); + else if (error.IsDefined()) + return 0; + else + flush = Z_FINISH; + } + + z.next_in = r.data; + z.avail_in = r.size; + + int result = inflate(&z, flush); + if (result == Z_STREAM_END) { + eof = true; + return size - z.avail_out; + } else if (result != Z_OK) { + error.Set(zlib_domain, result, zError(result)); + return 0; + } + + buffer.Consume(r.size - z.avail_in); + + if (z.avail_out < size) + return size - z.avail_out; + } +} diff --git a/src/fs/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx new file mode 100644 index 000000000..34fc653fa --- /dev/null +++ b/src/fs/io/GunzipReader.hxx @@ -0,0 +1,67 @@ +/* + * 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_GUNZIP_READER_HXX +#define MPD_GUNZIP_READER_HXX + +#include "check.h" +#include "Reader.hxx" +#include "util/StaticFifoBuffer.hxx" + +#include <zlib.h> + +class Error; +class Domain; + +/** + * A filter that decompresses data using zlib. + */ +class GunzipReader final : public Reader { + Reader &next; + + bool eof; + + z_stream z; + + StaticFifoBuffer<Bytef, 4096> buffer; + +public: + /** + * Construct the filter. Call IsDefined() to check whether + * the constructor has succeeded. If not, #error will hold + * information about the failure. + */ + GunzipReader(Reader &_next, Error &error); + ~GunzipReader(); + + /** + * Check whether the constructor has succeeded. + */ + bool IsDefined() const { + return z.opaque == nullptr; + } + + /* virtual methods from class Reader */ + virtual size_t Read(void *data, size_t size, Error &error) override; + +private: + bool FillBuffer(Error &error); +}; + +#endif diff --git a/src/fs/io/GzipOutputStream.cxx b/src/fs/io/GzipOutputStream.cxx new file mode 100644 index 000000000..27ae6b2ad --- /dev/null +++ b/src/fs/io/GzipOutputStream.cxx @@ -0,0 +1,105 @@ +/* + * 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 "GzipOutputStream.hxx" +#include "lib/zlib/Domain.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +GzipOutputStream::GzipOutputStream(OutputStream &_next, Error &error) + :next(_next) +{ + z.next_in = nullptr; + z.avail_in = 0; + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + + constexpr int windowBits = 15; + constexpr int gzip_encoding = 16; + + int result = deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + windowBits | gzip_encoding, + 8, Z_DEFAULT_STRATEGY); + if (result != Z_OK) { + z.opaque = this; + error.Set(zlib_domain, result, zError(result)); + } +} + +GzipOutputStream::~GzipOutputStream() +{ + if (IsDefined()) + deflateEnd(&z); +} + +bool +GzipOutputStream::Flush(Error &error) +{ + /* no more input */ + z.next_in = nullptr; + z.avail_in = 0; + + while (true) { + Bytef output[4096]; + z.next_out = output; + z.avail_out = sizeof(output); + + int result = deflate(&z, Z_FINISH); + if (z.next_out > output && + !next.Write(output, z.next_out - output, error)) + return false; + + if (result == Z_STREAM_END) + return true; + else if (result != Z_OK) { + error.Set(zlib_domain, result, zError(result)); + return false; + } + } +} + +bool +GzipOutputStream::Write(const void *_data, size_t size, Error &error) +{ + /* zlib's API requires non-const input pointer */ + void *data = const_cast<void *>(_data); + + z.next_in = reinterpret_cast<Bytef *>(data); + z.avail_in = size; + + while (z.avail_in > 0) { + Bytef output[4096]; + z.next_out = output; + z.avail_out = sizeof(output); + + int result = deflate(&z, Z_NO_FLUSH); + if (result != Z_OK) { + error.Set(zlib_domain, result, zError(result)); + return false; + } + + if (z.next_out > output && + !next.Write(output, z.next_out - output, error)) + return false; + } + + return true; +} diff --git a/src/fs/io/GzipOutputStream.hxx b/src/fs/io/GzipOutputStream.hxx new file mode 100644 index 000000000..e23835de8 --- /dev/null +++ b/src/fs/io/GzipOutputStream.hxx @@ -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. + */ + +#ifndef MPD_GZIP_OUTPUT_STREAM_HXX +#define MPD_GZIP_OUTPUT_STREAM_HXX + +#include "check.h" +#include "OutputStream.hxx" + +#include <assert.h> +#include <zlib.h> + +class Error; +class Domain; + +/** + * A filter that compresses data written to it using zlib, forwarding + * compressed data in the "gzip" format. + * + * Don't forget to call Flush() before destructing this object. + */ +class GzipOutputStream final : public OutputStream { + OutputStream &next; + + z_stream z; + +public: + /** + * Construct the filter. Call IsDefined() to check whether + * the constructor has succeeded. If not, #error will hold + * information about the failure. + */ + GzipOutputStream(OutputStream &_next, Error &error); + ~GzipOutputStream(); + + /** + * Check whether the constructor has succeeded. + */ + bool IsDefined() const { + return z.opaque == nullptr; + } + + /** + * Finish the file and write all data remaining in zlib's + * output buffer. + */ + bool Flush(Error &error); + + /* virtual methods from class OutputStream */ + bool Write(const void *data, size_t size, Error &error) override; +}; + +#endif diff --git a/src/fs/io/OutputStream.hxx b/src/fs/io/OutputStream.hxx new file mode 100644 index 000000000..71311c71f --- /dev/null +++ b/src/fs/io/OutputStream.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_STREAM_HXX +#define MPD_OUTPUT_STREAM_HXX + +#include "check.h" +#include "Compiler.h" + +#include <stddef.h> + +class Error; + +class OutputStream { +public: + OutputStream() = default; + OutputStream(const OutputStream &) = delete; + + virtual bool Write(const void *data, size_t size, Error &error) = 0; +}; + +#endif diff --git a/src/fs/io/PeekReader.cxx b/src/fs/io/PeekReader.cxx new file mode 100644 index 000000000..2e8042ab6 --- /dev/null +++ b/src/fs/io/PeekReader.cxx @@ -0,0 +1,60 @@ +/* + * 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 "PeekReader.hxx" + +#include <algorithm> + +#include <assert.h> +#include <string.h> + +const void * +PeekReader::Peek(size_t size, Error &error) +{ + assert(size > 0); + assert(size < sizeof(buffer)); + assert(buffer_size == 0); + assert(buffer_position == 0); + + do { + size_t nbytes = next.Read(buffer + buffer_size, + size - buffer_size, error); + if (nbytes == 0) + return nullptr; + + buffer_size += nbytes; + } while (buffer_size < size); + + return buffer; +} + +size_t +PeekReader::Read(void *data, size_t size, Error &error) +{ + size_t buffer_remaining = buffer_size - buffer_position; + if (buffer_remaining > 0) { + size_t nbytes = std::min(buffer_remaining, size); + memcpy(data, buffer + buffer_position, nbytes); + buffer_position += nbytes; + return nbytes; + } + + return next.Read(data, size, error); +} diff --git a/src/fs/io/PeekReader.hxx b/src/fs/io/PeekReader.hxx new file mode 100644 index 000000000..27e7ff7c1 --- /dev/null +++ b/src/fs/io/PeekReader.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PEEK_READER_HXX +#define MPD_PEEK_READER_HXX + +#include "check.h" +#include "Reader.hxx" + +#include <stdint.h> + +class AutoGunzipReader; + +/** + * A filter that allows the caller to peek the first few bytes without + * consuming them. The first call must be Peek(), and the following + * Read() will deliver the same bytes again. + */ +class PeekReader final : public Reader { + Reader &next; + + size_t buffer_size, buffer_position; + + uint8_t buffer[64]; + +public: + PeekReader(Reader &_next) + :next(_next), buffer_size(0), buffer_position(0) {} + + const void *Peek(size_t size, Error &error); + + /* virtual methods from class Reader */ + virtual size_t Read(void *data, size_t size, Error &error) override; +}; + +#endif diff --git a/src/fs/io/Reader.hxx b/src/fs/io/Reader.hxx new file mode 100644 index 000000000..d41e92dd0 --- /dev/null +++ b/src/fs/io/Reader.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_READER_HXX +#define MPD_READER_HXX + +#include "check.h" +#include "Compiler.h" + +#include <stddef.h> + +class Error; + +/** + * An interface that can read bytes from a stream until the stream + * ends. + * + * This interface is simpler and less cumbersome to use than + * #InputStream. + */ +class Reader { +public: + Reader() = default; + Reader(const Reader &) = delete; + + /** + * Read data from the stream. + * + * @return the number of bytes read into the given buffer or 0 + * on error/end-of-stream + */ + gcc_nonnull_all + virtual size_t Read(void *data, size_t size, Error &error) = 0; +}; + +#endif diff --git a/src/fs/io/StdioOutputStream.hxx b/src/fs/io/StdioOutputStream.hxx new file mode 100644 index 000000000..e00db922f --- /dev/null +++ b/src/fs/io/StdioOutputStream.hxx @@ -0,0 +1,45 @@ +/* + * 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_STDIO_OUTPUT_STREAM_HXX +#define MPD_STDIO_OUTPUT_STREAM_HXX + +#include "check.h" +#include "OutputStream.hxx" +#include "fs/AllocatedPath.hxx" + +#include <stdio.h> + +class StdioOutputStream final : public OutputStream { + FILE *const file; + +public: + StdioOutputStream(FILE *_file):file(_file) {} + + /* virtual methods from class OutputStream */ + bool Write(const void *data, size_t size, + gcc_unused Error &error) override { + fwrite(data, 1, size, file); + + /* this class is debug-only and ignores errors */ + return true; + } +}; + +#endif diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx new file mode 100644 index 000000000..28d6dabcb --- /dev/null +++ b/src/fs/io/TextFile.cxx @@ -0,0 +1,71 @@ +/* + * 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 "TextFile.hxx" +#include "FileReader.hxx" +#include "AutoGunzipReader.hxx" +#include "BufferedReader.hxx" +#include "fs/Path.hxx" + +#include <assert.h> + +TextFile::TextFile(Path path_fs, Error &error) + :file_reader(new FileReader(path_fs, error)), +#ifdef HAVE_ZLIB + gunzip_reader(file_reader->IsDefined() + ? new AutoGunzipReader(*file_reader) + : nullptr), +#endif + buffered_reader(file_reader->IsDefined() + ? new BufferedReader(* +#ifdef HAVE_ZLIB + gunzip_reader +#else + file_reader +#endif + ) + : nullptr) +{ +} + +TextFile::~TextFile() +{ + delete buffered_reader; +#ifdef HAVE_ZLIB + delete gunzip_reader; +#endif + delete file_reader; +} + +char * +TextFile::ReadLine() +{ + assert(buffered_reader != nullptr); + + return buffered_reader->ReadLine(); +} + +bool +TextFile::Check(Error &error) const +{ + assert(buffered_reader != nullptr); + + return buffered_reader->Check(error); +} diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx new file mode 100644 index 000000000..5577363e7 --- /dev/null +++ b/src/fs/io/TextFile.hxx @@ -0,0 +1,73 @@ +/* + * 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_FILE_HXX +#define MPD_TEXT_FILE_HXX + +#include "check.h" +#include "Compiler.h" + +#include <stddef.h> + +class Path; +class Error; +class FileReader; +class AutoGunzipReader; +class BufferedReader; + +class TextFile { + FileReader *const file_reader; + +#ifdef HAVE_ZLIB + AutoGunzipReader *const gunzip_reader; +#endif + + BufferedReader *const buffered_reader; + +public: + TextFile(Path path_fs, Error &error); + + TextFile(const TextFile &other) = delete; + + ~TextFile(); + + bool HasFailed() const { + return gcc_unlikely(buffered_reader == nullptr); + } + + /** + * Reads a line from the input file, and strips trailing + * space. There is a reasonable maximum line length, only to + * prevent denial of service. + * + * Use Check() after nullptr has been returned to check + * whether an error occurred or end-of-file has been reached. + * + * @param file the source file, opened in text mode + * @return a pointer to the line, or nullptr on end-of-file or error + */ + char *ReadLine(); + + /** + * Check whether a ReadLine() call has thrown an error. + */ + bool Check(Error &error) const; +}; + +#endif diff --git a/src/gerror.h b/src/gerror.h deleted file mode 100644 index fe4c54da9..000000000 --- a/src/gerror.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_GERROR_H -#define MPD_GERROR_H - -typedef struct _GError GError; - -#endif diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx deleted file mode 100644 index 5288f2b3b..000000000 --- a/src/input/ArchiveInputPlugin.cxx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "ArchiveInputPlugin.hxx" -#include "ArchiveDomain.hxx" -#include "ArchiveLookup.hxx" -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "fs/Traits.hxx" -#include "Log.hxx" - -#include <glib.h> - -/** - * select correct archive plugin to handle the input stream - * may allow stacking of archive plugins. for example for handling - * tar.gz a gzip handler opens file (through inputfile stream) - * then it opens a tar handler and sets gzip inputstream as - * parent_stream so tar plugin fetches file data from gzip - * plugin and gzip fetches file from disk - */ -static InputStream * -input_archive_open(const char *pathname, - Mutex &mutex, Cond &cond, - Error &error) -{ - const struct archive_plugin *arplug; - InputStream *is; - - if (!PathTraits::IsAbsoluteFS(pathname)) - return nullptr; - - char *pname = g_strdup(pathname); - // archive_lookup will modify pname when true is returned - const char *archive, *filename, *suffix; - if (!archive_lookup(pname, &archive, &filename, &suffix)) { - FormatDebug(archive_domain, - "not an archive, lookup %s failed", pname); - g_free(pname); - return nullptr; - } - - //check which archive plugin to use (by ext) - arplug = archive_plugin_from_suffix(suffix); - if (!arplug) { - FormatWarning(archive_domain, - "can't handle archive %s", archive); - g_free(pname); - return nullptr; - } - - auto file = archive_file_open(arplug, archive, error); - if (file == nullptr) { - g_free(pname); - return nullptr; - } - - //setup fileops - is = file->OpenStream(filename, mutex, cond, error); - g_free(pname); - file->Close(); - - return is; -} - -const InputPlugin input_plugin_archive = { - "archive", - nullptr, - nullptr, - input_archive_open, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, -}; diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/ArchiveInputPlugin.hxx deleted file mode 100644 index 9ac70b2fc..000000000 --- a/src/input/ArchiveInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_ARCHIVE_HXX -#define MPD_INPUT_ARCHIVE_HXX - -extern const struct InputPlugin input_plugin_archive; - -#endif diff --git a/src/input/AsyncInputStream.cxx b/src/input/AsyncInputStream.cxx new file mode 100644 index 000000000..8942b5116 --- /dev/null +++ b/src/input/AsyncInputStream.cxx @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AsyncInputStream.hxx" +#include "tag/Tag.hxx" +#include "event/Call.hxx" +#include "thread/Cond.hxx" +#include "IOThread.hxx" +#include "util/HugeAllocator.hxx" + +#include <assert.h> +#include <string.h> + +AsyncInputStream::AsyncInputStream(const char *_url, + Mutex &_mutex, Cond &_cond, + void *_buffer, size_t _buffer_size, + size_t _resume_at) + :InputStream(_url, _mutex, _cond), DeferredMonitor(io_thread_get()), + buffer((uint8_t *)_buffer, _buffer_size), + resume_at(_resume_at), + open(true), + paused(false), + seek_state(SeekState::NONE), + tag(nullptr) {} + +AsyncInputStream::~AsyncInputStream() +{ + delete tag; + + buffer.Clear(); + HugeFree(buffer.Write().data, buffer.GetCapacity()); +} + +void +AsyncInputStream::SetTag(Tag *_tag) +{ + delete tag; + tag = _tag; +} + +void +AsyncInputStream::Pause() +{ + assert(io_thread_inside()); + + paused = true; +} + +void +AsyncInputStream::PostponeError(Error &&error) +{ + assert(io_thread_inside()); + + seek_state = SeekState::NONE; + postponed_error = std::move(error); + cond.broadcast(); +} + +inline void +AsyncInputStream::Resume() +{ + assert(io_thread_inside()); + + if (paused) { + paused = false; + DoResume(); + } +} + +bool +AsyncInputStream::Check(Error &error) +{ + bool success = !postponed_error.IsDefined(); + if (!success) { + error = std::move(postponed_error); + postponed_error.Clear(); + } + + return success; +} + +bool +AsyncInputStream::IsEOF() +{ + return (KnownSize() && offset >= size) || + (!open && buffer.IsEmpty()); +} + +bool +AsyncInputStream::Seek(offset_type new_offset, Error &error) +{ + assert(IsReady()); + assert(seek_state == SeekState::NONE); + + if (new_offset == offset) + /* no-op */ + return true; + + if (!IsSeekable()) + return false; + + if (new_offset < 0) + return false; + + /* check if we can fast-forward the buffer */ + + while (new_offset > offset) { + auto r = buffer.Read(); + if (r.IsEmpty()) + break; + + const size_t nbytes = + new_offset - offset < (offset_type)r.size + ? new_offset - offset + : r.size; + + buffer.Consume(nbytes); + offset += nbytes; + } + + if (new_offset == offset) + return true; + + /* no: ask the implementation to seek */ + + seek_offset = new_offset; + seek_state = SeekState::SCHEDULED; + + DeferredMonitor::Schedule(); + + while (seek_state != SeekState::NONE) + cond.wait(mutex); + + if (!Check(error)) + return false; + + return true; +} + +void +AsyncInputStream::SeekDone() +{ + assert(io_thread_inside()); + assert(IsSeekPending()); + + seek_state = SeekState::NONE; + cond.broadcast(); +} + +Tag * +AsyncInputStream::ReadTag() +{ + Tag *result = tag; + tag = nullptr; + return result; +} + +bool +AsyncInputStream::IsAvailable() +{ + return postponed_error.IsDefined() || + IsEOF() || + !buffer.IsEmpty(); +} + +size_t +AsyncInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + assert(!io_thread_inside()); + + /* wait for data */ + CircularBuffer<uint8_t>::Range r; + while (true) { + if (!Check(error)) + return 0; + + r = buffer.Read(); + if (!r.IsEmpty() || IsEOF()) + break; + + cond.wait(mutex); + } + + const size_t nbytes = std::min(read_size, r.size); + memcpy(ptr, r.data, nbytes); + buffer.Consume(nbytes); + + offset += (offset_type)nbytes; + + if (paused && buffer.GetSize() < resume_at) + DeferredMonitor::Schedule(); + + return nbytes; +} + +void +AsyncInputStream::AppendToBuffer(const void *data, size_t append_size) +{ + auto w = buffer.Write(); + assert(!w.IsEmpty()); + + size_t nbytes = std::min(w.size, append_size); + memcpy(w.data, data, nbytes); + buffer.Append(nbytes); + + const size_t remaining = append_size - nbytes; + if (remaining > 0) { + w = buffer.Write(); + assert(!w.IsEmpty()); + assert(w.size >= remaining); + + memcpy(w.data, (const uint8_t *)data + nbytes, remaining); + buffer.Append(remaining); + } + + if (!IsReady()) + SetReady(); + else + cond.broadcast(); +} + +void +AsyncInputStream::RunDeferred() +{ + const ScopeLock protect(mutex); + + Resume(); + + if (seek_state == SeekState::SCHEDULED) { + seek_state = SeekState::PENDING; + buffer.Clear(); + paused = false; + DoSeek(seek_offset); + } +} diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx new file mode 100644 index 000000000..6e0031a0a --- /dev/null +++ b/src/input/AsyncInputStream.hxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ASYNC_INPUT_STREAM_HXX +#define MPD_ASYNC_INPUT_STREAM_HXX + +#include "InputStream.hxx" +#include "event/DeferredMonitor.hxx" +#include "util/CircularBuffer.hxx" +#include "util/Error.hxx" + +/** + * Helper class for moving asynchronous (non-blocking) InputStream + * implementations to the I/O thread. Data is being read into a ring + * buffer, and that buffer is then consumed by another thread using + * the regular #InputStream API. + */ +class AsyncInputStream : public InputStream, private DeferredMonitor { + enum class SeekState : uint8_t { + NONE, SCHEDULED, PENDING + }; + + CircularBuffer<uint8_t> buffer; + const size_t resume_at; + + bool open; + + /** + * Is the connection currently paused? That happens when the + * buffer was getting too large. It will be unpaused when the + * buffer is below the threshold again. + */ + bool paused; + + SeekState seek_state; + + /** + * The #Tag object ready to be requested via + * InputStream::ReadTag(). + */ + Tag *tag; + + offset_type seek_offset; + +protected: + Error postponed_error; + +public: + AsyncInputStream(const char *_url, + Mutex &_mutex, Cond &_cond, + void *_buffer, size_t _buffer_size, + size_t _resume_at); + + virtual ~AsyncInputStream(); + + /* virtual methods from InputStream */ + bool Check(Error &error) final; + bool IsEOF() final; + bool Seek(offset_type new_offset, Error &error) final; + Tag *ReadTag() final; + bool IsAvailable() final; + size_t Read(void *ptr, size_t read_size, Error &error) final; + +protected: + /** + * Pass an tag from the I/O thread to the client thread. + */ + void SetTag(Tag *_tag); + + void Pause(); + + /** + * Declare that the underlying stream was closed. We will + * continue feeding Read() calls from the buffer until it runs + * empty. + */ + void SetClosed() { + open = false; + } + + /** + * Pass an error from the I/O thread to the client thread. + */ + void PostponeError(Error &&error); + + bool IsBufferEmpty() const { + return buffer.IsEmpty(); + } + + bool IsBufferFull() const { + return buffer.IsFull(); + } + + /** + * Determine how many bytes can be added to the buffer. + */ + gcc_pure + size_t GetBufferSpace() const { + return buffer.GetSpace(); + } + + /** + * Append data to the buffer. The size must fit into the + * buffer; see GetBufferSpace(). + */ + void AppendToBuffer(const void *data, size_t append_size); + + /** + * Implement code here that will resume the stream after it + * has been paused due to full input buffer. + */ + virtual void DoResume() = 0; + + /** + * The actual Seek() implementation. This virtual method will + * be called from within the I/O thread. When the operation + * is finished, call SeekDone() to notify the caller. + */ + virtual void DoSeek(offset_type new_offset) = 0; + + bool IsSeekPending() const { + return seek_state == SeekState::PENDING; + } + + /** + * Call this after seeking has finished. It will notify the + * client thread. + */ + void SeekDone(); + +private: + void Resume(); + + /* virtual methods from DeferredMonitor */ + void RunDeferred() final; +}; + +#endif diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx deleted file mode 100644 index b3ac57413..000000000 --- a/src/input/CdioParanoiaInputPlugin.cxx +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * CD-Audio handling (requires libcdio_paranoia) - */ - -#include "config.h" -#include "CdioParanoiaInputPlugin.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.hxx" -#include "fs/AllocatedPath.hxx" -#include "Log.hxx" -#include "ConfigData.hxx" -#include "ConfigError.hxx" - -#include <stdio.h> -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> -#include <assert.h> - -#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H -#include <cdio/paranoia/paranoia.h> -#else -#include <cdio/paranoia.h> -#endif - -#include <cdio/cd_types.h> - -struct CdioParanoiaInputStream { - InputStream base; - - cdrom_drive_t *drv; - CdIo_t *cdio; - cdrom_paranoia_t *para; - - lsn_t lsn_from, lsn_to; - int lsn_relofs; - - int trackno; - - char buffer[CDIO_CD_FRAMESIZE_RAW]; - int buffer_lsn; - - CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond, - int _trackno) - :base(input_plugin_cdio_paranoia, uri, mutex, cond), - drv(nullptr), cdio(nullptr), para(nullptr), - trackno(_trackno) - { - } - - ~CdioParanoiaInputStream() { - if (para != nullptr) - cdio_paranoia_free(para); - if (drv != nullptr) - cdio_cddap_close_no_free_cdio(drv); - if (cdio != nullptr) - cdio_destroy(cdio); - } -}; - -static constexpr Domain cdio_domain("cdio"); - -static bool default_reverse_endian; - -static bool -input_cdio_init(const config_param ¶m, Error &error) -{ - const char *value = param.GetBlockValue("default_byte_order"); - if (value != nullptr) { - if (strcmp(value, "little_endian") == 0) - default_reverse_endian = IsBigEndian(); - else if (strcmp(value, "big_endian") == 0) - default_reverse_endian = IsLittleEndian(); - else { - error.Format(config_domain, 0, - "Unrecognized 'default_byte_order' setting: %s", - value); - return false; - } - } - - return true; -} - -static void -input_cdio_close(InputStream *is) -{ - CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is; - - delete i; -} - -struct cdio_uri { - char device[64]; - int track; -}; - -static bool -parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error) -{ - if (!g_str_has_prefix(src, "cdda://")) - return false; - - src += 7; - - if (*src == 0) { - /* play the whole CD in the default drive */ - dest->device[0] = 0; - dest->track = -1; - return true; - } - - const char *slash = strrchr(src, '/'); - if (slash == nullptr) { - /* play the whole CD in the specified drive */ - g_strlcpy(dest->device, src, sizeof(dest->device)); - dest->track = -1; - return true; - } - - size_t device_length = slash - src; - if (device_length >= sizeof(dest->device)) - device_length = sizeof(dest->device) - 1; - - memcpy(dest->device, src, device_length); - dest->device[device_length] = 0; - - const char *track = slash + 1; - - char *endptr; - dest->track = strtoul(track, &endptr, 10); - if (*endptr != 0) { - error.Set(cdio_domain, "Malformed track number"); - return false; - } - - if (endptr == track) - /* play the whole CD */ - dest->track = -1; - - return true; -} - -static AllocatedPath -cdio_detect_device(void) -{ - char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, - false); - if (devices == nullptr) - return AllocatedPath::Null(); - - AllocatedPath path = AllocatedPath::FromFS(devices[0]); - cdio_free_device_list(devices); - return path; -} - -static InputStream * -input_cdio_open(const char *uri, - Mutex &mutex, Cond &cond, - Error &error) -{ - struct cdio_uri parsed_uri; - if (!parse_cdio_uri(&parsed_uri, uri, error)) - return nullptr; - - CdioParanoiaInputStream *i = - new CdioParanoiaInputStream(uri, mutex, cond, - parsed_uri.track); - - /* get list of CD's supporting CD-DA */ - const AllocatedPath device = parsed_uri.device[0] != 0 - ? AllocatedPath::FromFS(parsed_uri.device) - : cdio_detect_device(); - if (device.IsNull()) { - error.Set(cdio_domain, - "Unable find or access a CD-ROM drive with an audio CD in it."); - delete i; - return nullptr; - } - - /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ - i->cdio = cdio_open(device.c_str(), DRIVER_UNKNOWN); - - i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr); - - if ( !i->drv ) { - error.Set(cdio_domain, "Unable to identify audio CD disc."); - delete i; - return nullptr; - } - - cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); - - if ( 0 != cdio_cddap_open(i->drv) ) { - error.Set(cdio_domain, "Unable to open disc."); - delete i; - return nullptr; - } - - bool reverse_endian; - switch (data_bigendianp(i->drv)) { - case -1: - LogDebug(cdio_domain, "drive returns unknown audio data"); - reverse_endian = default_reverse_endian; - break; - - case 0: - LogDebug(cdio_domain, "drive returns audio data Little Endian"); - reverse_endian = IsBigEndian(); - break; - - case 1: - LogDebug(cdio_domain, "drive returns audio data Big Endian"); - reverse_endian = IsLittleEndian(); - break; - - default: - error.Format(cdio_domain, "Drive returns unknown data type %d", - data_bigendianp(i->drv)); - delete i; - return nullptr; - } - - i->lsn_relofs = 0; - - if (i->trackno >= 0) { - i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); - i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); - } else { - i->lsn_from = 0; - i->lsn_to = cdio_get_disc_last_lsn(i->cdio); - } - - i->para = cdio_paranoia_init(i->drv); - - /* Set reading mode for full paranoia, but allow skipping sectors. */ - paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); - - /* seek to beginning of the track */ - cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); - - i->base.ready = true; - i->base.seekable = true; - i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; - - /* hack to make MPD select the "pcm" decoder plugin */ - i->base.mime = reverse_endian - ? "audio/x-mpd-cdda-pcm-reverse" - : "audio/x-mpd-cdda-pcm"; - - return &i->base; -} - -static bool -input_cdio_seek(InputStream *is, - InputPlugin::offset_type offset, int whence, Error &error) -{ - CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; - - /* calculate absolute offset */ - switch (whence) { - case SEEK_SET: - break; - case SEEK_CUR: - offset += cis->base.offset; - break; - case SEEK_END: - offset += cis->base.size; - break; - } - - if (offset < 0 || offset > cis->base.size) { - error.Format(cdio_domain, "Invalid offset to seek %ld (%ld)", - (long int)offset, (long int)cis->base.size); - return false; - } - - /* simple case */ - if (offset == cis->base.offset) - return true; - - /* calculate current LSN */ - cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; - cis->base.offset = offset; - - cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); - - return true; -} - -static size_t -input_cdio_read(InputStream *is, void *ptr, size_t length, - Error &error) -{ - CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; - size_t nbytes = 0; - int diff; - size_t len, maxwrite; - int16_t *rbuf; - char *s_err, *s_mess; - char *wptr = (char *) ptr; - - while (length > 0) { - - - /* end of track ? */ - if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) - break; - - //current sector was changed ? - if (cis->lsn_relofs != cis->buffer_lsn) { - rbuf = cdio_paranoia_read(cis->para, nullptr); - - s_err = cdda_errors(cis->drv); - if (s_err) { - FormatError(cdio_domain, - "paranoia_read: %s", s_err); - free(s_err); - } - s_mess = cdda_messages(cis->drv); - if (s_mess) { - free(s_mess); - } - if (!rbuf) { - error.Set(cdio_domain, - "paranoia read error. Stopping."); - return 0; - } - //store current buffer - memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); - cis->buffer_lsn = cis->lsn_relofs; - } else { - //use cached sector - rbuf = (int16_t*) cis->buffer; - } - - //correct offset - diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; - - assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); - - maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer - len = (length < maxwrite? length : maxwrite); - - //skip diff bytes from this lsn - memcpy(wptr, ((char*)rbuf) + diff, len); - //update pointer - wptr += len; - nbytes += len; - - //update offset - cis->base.offset += len; - cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; - //update length - length -= len; - } - - return nbytes; -} - -static bool -input_cdio_eof(InputStream *is) -{ - CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; - - return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); -} - -const InputPlugin input_plugin_cdio_paranoia = { - "cdio_paranoia", - input_cdio_init, - nullptr, - input_cdio_open, - input_cdio_close, - nullptr, - nullptr, - nullptr, - nullptr, - input_cdio_read, - input_cdio_eof, - input_cdio_seek, -}; diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/CdioParanoiaInputPlugin.hxx deleted file mode 100644 index 847802a48..000000000 --- a/src/input/CdioParanoiaInputPlugin.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX -#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX - -/** - * An input plugin based on libcdio_paranoia library. - */ -extern const struct InputPlugin input_plugin_cdio_paranoia; - -#endif diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx deleted file mode 100644 index b78545951..000000000 --- a/src/input/CurlInputPlugin.cxx +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "CurlInputPlugin.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "tag/Tag.hxx" -#include "IcyMetaDataParser.hxx" -#include "event/SocketMonitor.hxx" -#include "event/TimeoutMonitor.hxx" -#include "event/Call.hxx" -#include "IOThread.hxx" -#include "util/ASCII.hxx" -#include "util/CharUtil.hxx" -#include "util/NumberParser.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> - -#if defined(WIN32) - #include <winsock2.h> -#else - #include <sys/select.h> -#endif - -#include <string.h> -#include <errno.h> - -#include <list> - -#include <curl/curl.h> -#include <glib.h> - -#if LIBCURL_VERSION_NUM < 0x071200 -#error libcurl is too old -#endif - -/** - * Do not buffer more than this number of bytes. It should be a - * reasonable limit that doesn't make low-end machines suffer too - * much, but doesn't cause stuttering on high-latency lines. - */ -static const size_t CURL_MAX_BUFFERED = 512 * 1024; - -/** - * Resume the stream at this number of bytes after it has been paused. - */ -static const size_t CURL_RESUME_AT = 384 * 1024; - -/** - * Buffers created by input_curl_writefunction(). - */ -class CurlInputBuffer { - /** size of the payload */ - size_t size; - - /** how much has been consumed yet? */ - size_t consumed; - - /** the payload */ - uint8_t *data; - -public: - CurlInputBuffer(const void *_data, size_t _size) - :size(_size), consumed(0), data(new uint8_t[size]) { - memcpy(data, _data, size); - } - - ~CurlInputBuffer() { - delete[] data; - } - - CurlInputBuffer(const CurlInputBuffer &) = delete; - CurlInputBuffer &operator=(const CurlInputBuffer &) = delete; - - const void *Begin() const { - return data + consumed; - } - - size_t TotalSize() const { - return size; - } - - size_t Available() const { - return size - consumed; - } - - /** - * Mark a part of the buffer as consumed. - * - * @return false if the buffer is now empty - */ - bool Consume(size_t length) { - assert(consumed < size); - - consumed += length; - if (consumed < size) - return true; - - assert(consumed == size); - return false; - } - - bool Read(void *dest, size_t length) { - assert(consumed + length <= size); - - memcpy(dest, data + consumed, length); - return Consume(length); - } -}; - -struct input_curl { - InputStream base; - - /* some buffers which were passed to libcurl, which we have - too free */ - char range[32]; - struct curl_slist *request_headers; - - /** the curl handles */ - CURL *easy; - - /** list of buffers, where input_curl_writefunction() appends - to, and input_curl_read() reads from them */ - std::list<CurlInputBuffer> buffers; - - /** - * Is the connection currently paused? That happens when the - * buffer was getting too large. It will be unpaused when the - * buffer is below the threshold again. - */ - bool paused; - - /** error message provided by libcurl */ - char error[CURL_ERROR_SIZE]; - - /** parser for icy-metadata */ - IcyMetaDataParser icy; - - /** the stream name from the icy-name response header */ - std::string meta_name; - - /** the tag object ready to be requested via - InputStream::ReadTag() */ - Tag *tag; - - Error postponed_error; - - input_curl(const char *url, Mutex &mutex, Cond &cond) - :base(input_plugin_curl, url, mutex, cond), - request_headers(nullptr), - paused(false), - tag(nullptr) {} - - ~input_curl(); - - input_curl(const input_curl &) = delete; - input_curl &operator=(const input_curl &) = delete; -}; - -class CurlMulti; - -/** - * Monitor for one socket created by CURL. - */ -class CurlSocket final : SocketMonitor { - CurlMulti &multi; - -public: - CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd) - :SocketMonitor(_fd, _loop), multi(_multi) {} - - ~CurlSocket() { - /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after - closing the socket, and sometimes, it uses - CURL_POLL_REMOVE just to move the (still open) - connection to the pool; in the first case, - Abandon() would be most appropriate, but it breaks - the second case - is that a CURL bug? is there a - better solution? */ - - Steal(); - } - - /** - * Callback function for CURLMOPT_SOCKETFUNCTION. - */ - static int SocketFunction(CURL *easy, - curl_socket_t s, int action, - void *userp, void *socketp); - - virtual bool OnSocketReady(unsigned flags) override; - -private: - static constexpr int FlagsToCurlCSelect(unsigned flags) { - return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) | - (flags & WRITE ? CURL_CSELECT_OUT : 0) | - (flags & ERROR ? CURL_CSELECT_ERR : 0); - } - - gcc_const - static unsigned CurlPollToFlags(int action) { - switch (action) { - case CURL_POLL_NONE: - return 0; - - case CURL_POLL_IN: - return READ; - - case CURL_POLL_OUT: - return WRITE; - - case CURL_POLL_INOUT: - return READ|WRITE; - } - - assert(false); - gcc_unreachable(); - } -}; - -/** - * Manager for the global CURLM object. - */ -class CurlMulti final : private TimeoutMonitor { - CURLM *const multi; - -public: - CurlMulti(EventLoop &_loop, CURLM *_multi); - - ~CurlMulti() { - curl_multi_cleanup(multi); - } - - bool Add(input_curl *c, Error &error); - void Remove(input_curl *c); - - /** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ - void ReadInfo(); - - void Assign(curl_socket_t fd, CurlSocket &cs) { - curl_multi_assign(multi, fd, &cs); - } - - void SocketAction(curl_socket_t fd, int ev_bitmask); - - void InvalidateSockets() { - SocketAction(CURL_SOCKET_TIMEOUT, 0); - } - - /** - * This is a kludge to allow pausing/resuming a stream with - * libcurl < 7.32.0. Read the curl_easy_pause manpage for - * more information. - */ - void ResumeSockets() { - int running_handles; - curl_multi_socket_all(multi, &running_handles); - } - -private: - static int TimerFunction(CURLM *multi, long timeout_ms, void *userp); - - virtual void OnTimeout() override; -}; - -/** - * libcurl version number encoded in a 24 bit integer. - */ -static unsigned curl_version_num; - -/** libcurl should accept "ICY 200 OK" */ -static struct curl_slist *http_200_aliases; - -/** HTTP proxy settings */ -static const char *proxy, *proxy_user, *proxy_password; -static unsigned proxy_port; - -static CurlMulti *curl_multi; - -static constexpr Domain http_domain("http"); -static constexpr Domain curl_domain("curl"); -static constexpr Domain curlm_domain("curlm"); - -CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi) - :TimeoutMonitor(_loop), multi(_multi) -{ - curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, - CurlSocket::SocketFunction); - curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this); - - curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction); - curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this); -} - -/** - * Find a request by its CURL "easy" handle. - * - * Runs in the I/O thread. No lock needed. - */ -gcc_pure -static struct input_curl * -input_curl_find_request(CURL *easy) -{ - assert(io_thread_inside()); - - void *p; - CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p); - if (code != CURLE_OK) - return nullptr; - - return (input_curl *)p; -} - -static void -input_curl_resume(struct input_curl *c) -{ - assert(io_thread_inside()); - - if (c->paused) { - c->paused = false; - curl_easy_pause(c->easy, CURLPAUSE_CONT); - - if (curl_version_num < 0x072000) - /* libcurl older than 7.32.0 does not update - its sockets after curl_easy_pause(); force - libcurl to do it now */ - curl_multi->ResumeSockets(); - - curl_multi->InvalidateSockets(); - } -} - -int -CurlSocket::SocketFunction(gcc_unused CURL *easy, - curl_socket_t s, int action, - void *userp, void *socketp) { - CurlMulti &multi = *(CurlMulti *)userp; - CurlSocket *cs = (CurlSocket *)socketp; - - assert(io_thread_inside()); - - if (action == CURL_POLL_REMOVE) { - delete cs; - return 0; - } - - if (cs == nullptr) { - cs = new CurlSocket(multi, io_thread_get(), s); - multi.Assign(s, *cs); - } else { -#ifdef USE_EPOLL - /* when using epoll, we need to unregister the socket - each time this callback is invoked, because older - CURL versions may omit the CURL_POLL_REMOVE call - when the socket has been closed and recreated with - the same file number (bug found in CURL 7.26, CURL - 7.33 not affected); in that case, epoll refuses the - EPOLL_CTL_MOD because it does not know the new - socket yet */ - cs->Cancel(); -#endif - } - - unsigned flags = CurlPollToFlags(action); - if (flags != 0) - cs->Schedule(flags); - return 0; -} - -bool -CurlSocket::OnSocketReady(unsigned flags) -{ - assert(io_thread_inside()); - - multi.SocketAction(Get(), FlagsToCurlCSelect(flags)); - return true; -} - -/** - * Runs in the I/O thread. No lock needed. - */ -inline bool -CurlMulti::Add(struct input_curl *c, Error &error) -{ - assert(io_thread_inside()); - assert(c != nullptr); - assert(c->easy != nullptr); - - CURLMcode mcode = curl_multi_add_handle(multi, c->easy); - if (mcode != CURLM_OK) { - error.Format(curlm_domain, mcode, - "curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - InvalidateSockets(); - return true; -} - -/** - * Call input_curl_easy_add() in the I/O thread. May be called from - * any thread. Caller must not hold a mutex. - */ -static bool -input_curl_easy_add_indirect(struct input_curl *c, Error &error) -{ - assert(c != nullptr); - assert(c->easy != nullptr); - - bool result; - BlockingCall(io_thread_get(), [c, &error, &result](){ - result = curl_multi->Add(c, error); - }); - return result; -} - -inline void -CurlMulti::Remove(input_curl *c) -{ - curl_multi_remove_handle(multi, c->easy); -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * Runs in the I/O thread. - */ -static void -input_curl_easy_free(struct input_curl *c) -{ - assert(io_thread_inside()); - assert(c != nullptr); - - if (c->easy == nullptr) - return; - - curl_multi->Remove(c); - - curl_easy_cleanup(c->easy); - c->easy = nullptr; - - curl_slist_free_all(c->request_headers); - c->request_headers = nullptr; -} - -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - * - * The mutex must not be locked. - */ -static void -input_curl_easy_free_indirect(struct input_curl *c) -{ - BlockingCall(io_thread_get(), [c](){ - input_curl_easy_free(c); - curl_multi->InvalidateSockets(); - }); - - assert(c->easy == nullptr); -} - -/** - * A HTTP request is finished. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -static void -input_curl_request_done(struct input_curl *c, CURLcode result, long status) -{ - assert(io_thread_inside()); - assert(c != nullptr); - assert(c->easy == nullptr); - assert(!c->postponed_error.IsDefined()); - - const ScopeLock protect(c->base.mutex); - - if (result != CURLE_OK) { - c->postponed_error.Format(curl_domain, result, - "curl failed: %s", c->error); - } else if (status < 200 || status >= 300) { - c->postponed_error.Format(http_domain, status, - "got HTTP status %ld", - status); - } - - c->base.ready = true; - - c->base.cond.broadcast(); -} - -static void -input_curl_handle_done(CURL *easy_handle, CURLcode result) -{ - struct input_curl *c = input_curl_find_request(easy_handle); - assert(c != nullptr); - - long status = 0; - curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); - - input_curl_easy_free(c); - input_curl_request_done(c, result, status); -} - -void -CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask) -{ - int running_handles; - CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask, - &running_handles); - if (mcode != CURLM_OK) - FormatError(curlm_domain, - "curl_multi_socket_action() failed: %s", - curl_multi_strerror(mcode)); - - ReadInfo(); -} - -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -inline void -CurlMulti::ReadInfo() -{ - assert(io_thread_inside()); - - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(multi, - &msgs_in_queue)) != nullptr) { - if (msg->msg == CURLMSG_DONE) - input_curl_handle_done(msg->easy_handle, msg->data.result); - } -} - -int -CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp) -{ - CurlMulti &multi = *(CurlMulti *)userp; - assert(_multi == multi.multi); - - if (timeout_ms < 0) { - multi.Cancel(); - return 0; - } - - if (timeout_ms >= 0 && timeout_ms < 10) - /* CURL 7.21.1 likes to report "timeout=0", which - means we're running in a busy loop. Quite a bad - idea to waste so much CPU. Let's use a lower limit - of 10ms. */ - timeout_ms = 10; - - multi.Schedule(timeout_ms); - return 0; -} - -void -CurlMulti::OnTimeout() -{ - SocketAction(CURL_SOCKET_TIMEOUT, 0); -} - -/* - * InputPlugin methods - * - */ - -static bool -input_curl_init(const config_param ¶m, Error &error) -{ - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); - if (code != CURLE_OK) { - error.Format(curl_domain, code, - "curl_global_init() failed: %s", - curl_easy_strerror(code)); - return false; - } - - const auto version_info = curl_version_info(CURLVERSION_FIRST); - if (version_info != nullptr) { - FormatDebug(curl_domain, "version %s", version_info->version); - if (version_info->features & CURL_VERSION_SSL) - FormatDebug(curl_domain, "with %s", - version_info->ssl_version); - - curl_version_num = version_info->version_num; - } - - http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); - - proxy = param.GetBlockValue("proxy"); - proxy_port = param.GetBlockValue("proxy_port", 0u); - proxy_user = param.GetBlockValue("proxy_user"); - proxy_password = param.GetBlockValue("proxy_password"); - - if (proxy == nullptr) { - /* deprecated proxy configuration */ - proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr); - proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); - proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr); - proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, - ""); - } - - CURLM *multi = curl_multi_init(); - if (multi == nullptr) { - error.Set(curl_domain, 0, "curl_multi_init() failed"); - return false; - } - - curl_multi = new CurlMulti(io_thread_get(), multi); - return true; -} - -static void -input_curl_finish(void) -{ - BlockingCall(io_thread_get(), [](){ - delete curl_multi; - }); - - curl_slist_free_all(http_200_aliases); - - curl_global_cleanup(); -} - -/** - * Determine the total sizes of all buffers, including portions that - * have already been consumed. - * - * The caller must lock the mutex. - */ -gcc_pure -static size_t -curl_total_buffer_size(const struct input_curl *c) -{ - size_t total = 0; - - for (const auto &i : c->buffers) - total += i.TotalSize(); - - return total; -} - -input_curl::~input_curl() -{ - delete tag; - - input_curl_easy_free_indirect(this); -} - -static bool -input_curl_check(InputStream *is, Error &error) -{ - struct input_curl *c = (struct input_curl *)is; - - bool success = !c->postponed_error.IsDefined(); - if (!success) { - error = std::move(c->postponed_error); - c->postponed_error.Clear(); - } - - return success; -} - -static Tag * -input_curl_tag(InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - Tag *tag = c->tag; - - c->tag = nullptr; - return tag; -} - -static bool -fill_buffer(struct input_curl *c, Error &error) -{ - while (c->easy != nullptr && c->buffers.empty()) - c->base.cond.wait(c->base.mutex); - - if (c->postponed_error.IsDefined()) { - error = std::move(c->postponed_error); - c->postponed_error.Clear(); - return false; - } - - return !c->buffers.empty(); -} - -static size_t -read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers, - void *dest0, size_t length) -{ - auto &buffer = buffers.front(); - uint8_t *dest = (uint8_t *)dest0; - size_t nbytes = 0; - - if (length > buffer.Available()) - length = buffer.Available(); - - while (true) { - size_t chunk; - - chunk = icy.Data(length); - if (chunk > 0) { - const bool empty = !buffer.Read(dest, chunk); - - nbytes += chunk; - dest += chunk; - length -= chunk; - - if (empty) { - buffers.pop_front(); - break; - } - - if (length == 0) - break; - } - - chunk = icy.Meta(buffer.Begin(), length); - if (chunk > 0) { - const bool empty = !buffer.Consume(chunk); - - length -= chunk; - - if (empty) { - buffers.pop_front(); - break; - } - - if (length == 0) - break; - } - } - - return nbytes; -} - -static void -copy_icy_tag(struct input_curl *c) -{ - Tag *tag = c->icy.ReadTag(); - - if (tag == nullptr) - return; - - delete c->tag; - - if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) - tag->AddItem(TAG_NAME, c->meta_name.c_str()); - - c->tag = tag; -} - -static bool -input_curl_available(InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->postponed_error.IsDefined() || c->easy == nullptr || - !c->buffers.empty(); -} - -static size_t -input_curl_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - struct input_curl *c = (struct input_curl *)is; - bool success; - size_t nbytes = 0; - char *dest = (char *)ptr; - - do { - /* fill the buffer */ - - success = fill_buffer(c, error); - if (!success) - return 0; - - /* send buffer contents */ - - while (size > 0 && !c->buffers.empty()) { - size_t copy = read_from_buffer(c->icy, c->buffers, - dest + nbytes, size); - - nbytes += copy; - size -= copy; - } - } while (nbytes == 0); - - if (c->icy.IsDefined()) - copy_icy_tag(c); - - is->offset += (InputPlugin::offset_type)nbytes; - - if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { - c->base.mutex.unlock(); - - BlockingCall(io_thread_get(), [c](){ - input_curl_resume(c); - }); - - c->base.mutex.lock(); - } - - return nbytes; -} - -static void -input_curl_close(InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - delete c; -} - -static bool -input_curl_eof(gcc_unused InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->easy == nullptr && c->buffers.empty(); -} - -/** called by curl when new data is available */ -static size_t -input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - char name[64]; - - size *= nmemb; - - const char *header = (const char *)ptr; - const char *end = header + size; - - const char *value = (const char *)memchr(header, ':', size); - if (value == nullptr || (size_t)(value - header) >= sizeof(name)) - return size; - - memcpy(name, header, value - header); - name[value - header] = 0; - - /* skip the colon */ - - ++value; - - /* strip the value */ - - while (value < end && IsWhitespaceOrNull(*value)) - ++value; - - while (end > value && IsWhitespaceOrNull(end[-1])) - --end; - - if (StringEqualsCaseASCII(name, "accept-ranges")) { - /* a stream with icy-metadata is not seekable */ - if (!c->icy.IsDefined()) - c->base.seekable = true; - } else if (StringEqualsCaseASCII(name, "content-length")) { - char buffer[64]; - - if ((size_t)(end - header) >= sizeof(buffer)) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - c->base.size = c->base.offset + ParseUint64(buffer); - } else if (StringEqualsCaseASCII(name, "content-type")) { - c->base.mime.assign(value, end); - } else if (StringEqualsCaseASCII(name, "icy-name") || - StringEqualsCaseASCII(name, "ice-name") || - StringEqualsCaseASCII(name, "x-audiocast-name")) { - c->meta_name.assign(value, end); - - delete c->tag; - - c->tag = new Tag(); - c->tag->AddItem(TAG_NAME, c->meta_name.c_str()); - } else if (StringEqualsCaseASCII(name, "icy-metaint")) { - char buffer[64]; - size_t icy_metaint; - - if ((size_t)(end - header) >= sizeof(buffer) || - c->icy.IsDefined()) - return size; - - memcpy(buffer, value, end - value); - buffer[end - value] = 0; - - icy_metaint = ParseUint64(buffer); - FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint); - - if (icy_metaint > 0) { - c->icy.Start(icy_metaint); - - /* a stream with icy-metadata is not - seekable */ - c->base.seekable = false; - } - } - - return size; -} - -/** called by curl when new data is available */ -static size_t -input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - struct input_curl *c = (struct input_curl *)stream; - - size *= nmemb; - if (size == 0) - return 0; - - const ScopeLock protect(c->base.mutex); - - if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { - c->paused = true; - return CURL_WRITEFUNC_PAUSE; - } - - c->buffers.emplace_back(ptr, size); - c->base.ready = true; - - c->base.cond.broadcast(); - return size; -} - -static bool -input_curl_easy_init(struct input_curl *c, Error &error) -{ - CURLcode code; - - c->easy = curl_easy_init(); - if (c->easy == nullptr) { - error.Set(curl_domain, "curl_easy_init() failed"); - return false; - } - - curl_easy_setopt(c->easy, CURLOPT_PRIVATE, (void *)c); - curl_easy_setopt(c->easy, CURLOPT_USERAGENT, - "Music Player Daemon " VERSION); - curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, - input_curl_headerfunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); - curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, - input_curl_writefunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); - curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); - curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(c->easy, CURLOPT_NETRC, 1); - curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); - curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); - curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); - curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); - curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); - curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); - - if (proxy != nullptr) - curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); - - if (proxy_port > 0) - curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); - - if (proxy_user != nullptr && proxy_password != nullptr) { - char proxy_auth_str[1024]; - snprintf(proxy_auth_str, sizeof(proxy_auth_str), - "%s:%s", - proxy_user, proxy_password); - curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); - } - - code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str()); - if (code != CURLE_OK) { - error.Format(curl_domain, code, - "curl_easy_setopt() failed: %s", - curl_easy_strerror(code)); - return false; - } - - c->request_headers = nullptr; - c->request_headers = curl_slist_append(c->request_headers, - "Icy-Metadata: 1"); - curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); - - return true; -} - -static bool -input_curl_seek(InputStream *is, InputPlugin::offset_type offset, - int whence, - Error &error) -{ - struct input_curl *c = (struct input_curl *)is; - bool ret; - - assert(is->ready); - - if (whence == SEEK_SET && offset == is->offset) - /* no-op */ - return true; - - if (!is->seekable) - return false; - - /* calculate the absolute offset */ - - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - offset += is->offset; - break; - - case SEEK_END: - if (is->size < 0) - /* stream size is not known */ - return false; - - offset += is->size; - break; - - default: - return false; - } - - if (offset < 0) - return false; - - /* check if we can fast-forward the buffer */ - - while (offset > is->offset && !c->buffers.empty()) { - auto &buffer = c->buffers.front(); - size_t length = buffer.Available(); - if (offset - is->offset < (InputPlugin::offset_type)length) - length = offset - is->offset; - - const bool empty = !buffer.Consume(length); - if (empty) - c->buffers.pop_front(); - - is->offset += length; - } - - if (offset == is->offset) - return true; - - /* close the old connection and open a new one */ - - c->base.mutex.unlock(); - - input_curl_easy_free_indirect(c); - c->buffers.clear(); - - is->offset = offset; - if (is->offset == is->size) { - /* seek to EOF: simulate empty result; avoid - triggering a "416 Requested Range Not Satisfiable" - response */ - return true; - } - - ret = input_curl_easy_init(c, error); - if (!ret) - return false; - - /* send the "Range" header */ - - if (is->offset > 0) { - sprintf(c->range, "%lld-", (long long)is->offset); - curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); - } - - c->base.ready = false; - - if (!input_curl_easy_add_indirect(c, error)) - return false; - - c->base.mutex.lock(); - - while (!c->base.ready) - c->base.cond.wait(c->base.mutex); - - if (c->postponed_error.IsDefined()) { - error = std::move(c->postponed_error); - c->postponed_error.Clear(); - return false; - } - - return true; -} - -static InputStream * -input_curl_open(const char *url, Mutex &mutex, Cond &cond, - Error &error) -{ - if (memcmp(url, "http://", 7) != 0 && - memcmp(url, "https://", 8) != 0) - return nullptr; - - struct input_curl *c = new input_curl(url, mutex, cond); - - if (!input_curl_easy_init(c, error)) { - delete c; - return nullptr; - } - - if (!input_curl_easy_add_indirect(c, error)) { - delete c; - return nullptr; - } - - return &c->base; -} - -const struct InputPlugin input_plugin_curl = { - "curl", - input_curl_init, - input_curl_finish, - input_curl_open, - input_curl_close, - input_curl_check, - nullptr, - input_curl_tag, - input_curl_available, - input_curl_read, - input_curl_eof, - input_curl_seek, -}; diff --git a/src/input/CurlInputPlugin.hxx b/src/input/CurlInputPlugin.hxx deleted file mode 100644 index 30e917257..000000000 --- a/src/input/CurlInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_CURL_HXX -#define MPD_INPUT_CURL_HXX - -extern const struct InputPlugin input_plugin_curl; - -#endif diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx deleted file mode 100644 index b08299516..000000000 --- a/src/input/DespotifyInputPlugin.cxx +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2011-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "DespotifyInputPlugin.hxx" -#include "DespotifyUtils.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "tag/Tag.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 { - InputStream base; - - struct despotify_session *session; - struct ds_track *track; - Tag *tag; - struct ds_pcm_data pcm; - size_t len_available; - bool eof; - - DespotifyInputStream(const char *uri, - Mutex &mutex, Cond &cond, - despotify_session *_session, - ds_track *_track) - :base(input_plugin_despotify, uri, mutex, cond), - session(_session), track(_track), - tag(mpd_despotify_tag_from_track(track)), - len_available(0), eof(false) { - - memset(&pcm, 0, sizeof(pcm)); - - /* Despotify outputs pcm data */ - base.mime = "audio/x-mpd-cdda-pcm"; - base.ready = true; - } - - ~DespotifyInputStream() { - delete tag; - - despotify_free_track(track); - } -}; - -static void -refill_buffer(DespotifyInputStream *ctx) -{ - /* Wait until there is data */ - while (1) { - int rc = despotify_get_pcm(ctx->session, &ctx->pcm); - - if (rc == 0 && ctx->pcm.len) { - ctx->len_available = ctx->pcm.len; - break; - } - if (ctx->eof == true) - break; - - if (rc < 0) { - LogDebug(despotify_domain, "despotify_get_pcm error"); - ctx->eof = true; - break; - } - - /* Wait a while until next iteration */ - usleep(50 * 1000); - } -} - -static void callback(gcc_unused struct despotify_session* ds, - int sig, gcc_unused void* data, void* callback_data) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; - - switch (sig) { - case DESPOTIFY_NEW_TRACK: - break; - - case DESPOTIFY_TIME_TELL: - break; - - case DESPOTIFY_TRACK_PLAY_ERROR: - LogWarning(despotify_domain, "Track play error"); - ctx->eof = true; - ctx->len_available = 0; - break; - - case DESPOTIFY_END_OF_PLAYLIST: - ctx->eof = true; - FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof); - break; - } -} - - -static InputStream * -input_despotify_open(const char *url, - Mutex &mutex, Cond &cond, - gcc_unused Error &error) -{ - struct despotify_session *session; - struct ds_link *ds_link; - struct ds_track *track; - - if (!g_str_has_prefix(url, "spt://")) - return nullptr; - - session = mpd_despotify_get_session(); - if (!session) - return nullptr; - - ds_link = despotify_link_from_uri(url + 6); - if (!ds_link) { - FormatDebug(despotify_domain, "Can't find %s", url); - return nullptr; - } - if (ds_link->type != LINK_TYPE_TRACK) { - despotify_free_link(ds_link); - return nullptr; - } - - track = despotify_link_get_track(session, ds_link); - despotify_free_link(ds_link); - if (!track) - return nullptr; - - DespotifyInputStream *ctx = - new DespotifyInputStream(url, mutex, cond, - session, track); - - if (!mpd_despotify_register_callback(callback, ctx)) { - delete ctx; - return nullptr; - } - - if (despotify_play(ctx->session, ctx->track, false) == false) { - mpd_despotify_unregister_callback(callback); - delete ctx; - return nullptr; - } - - return &ctx->base; -} - -static size_t -input_despotify_read(InputStream *is, void *ptr, size_t size, - gcc_unused Error &error) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - size_t to_cpy = size; - - if (ctx->len_available == 0) - refill_buffer(ctx); - - if (ctx->len_available < size) - to_cpy = ctx->len_available; - memcpy(ptr, ctx->pcm.buf, to_cpy); - ctx->len_available -= to_cpy; - - is->offset += to_cpy; - - return to_cpy; -} - -static void -input_despotify_close(InputStream *is) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - - mpd_despotify_unregister_callback(callback); - delete ctx; -} - -static bool -input_despotify_eof(InputStream *is) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - - return ctx->eof; -} - -static Tag * -input_despotify_tag(InputStream *is) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - Tag *tag = ctx->tag; - - ctx->tag = nullptr; - - return tag; -} - -const InputPlugin input_plugin_despotify = { - "spt", - nullptr, - nullptr, - input_despotify_open, - input_despotify_close, - nullptr, - nullptr, - input_despotify_tag, - nullptr, - input_despotify_read, - input_despotify_eof, - nullptr, -}; diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx deleted file mode 100644 index f1911f235..000000000 --- a/src/input/DespotifyInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef INPUT_DESPOTIFY_HXX -#define INPUT_DESPOTIFY_HXX - -extern const struct InputPlugin input_plugin_despotify; - -#endif diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx deleted file mode 100644 index 8f9cd0b86..000000000 --- a/src/input/FfmpegInputPlugin.cxx +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* necessary because libavutil/common.h uses UINT64_C */ -#define __STDC_CONSTANT_MACROS - -#include "config.h" -#include "FfmpegInputPlugin.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -extern "C" { -#include <libavutil/avutil.h> -#include <libavformat/avio.h> -#include <libavformat/avformat.h> -} - -#include <glib.h> - -struct FfmpegInputStream { - InputStream base; - - AVIOContext *h; - - bool eof; - - FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond, - AVIOContext *_h) - :base(input_plugin_ffmpeg, uri, mutex, cond), - h(_h), eof(false) { - base.ready = true; - base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0; - base.size = avio_size(h); - - /* hack to make MPD select the "ffmpeg" decoder plugin - - since avio.h doesn't tell us the MIME type of the - resource, we can't select a decoder plugin, but the - "ffmpeg" plugin is quite good at auto-detection */ - base.mime = "audio/x-mpd-ffmpeg"; - } - - ~FfmpegInputStream() { - avio_close(h); - } -}; - -static constexpr Domain ffmpeg_domain("ffmpeg"); - -static inline bool -input_ffmpeg_supported(void) -{ - void *opaque = nullptr; - return avio_enum_protocols(&opaque, 0) != nullptr; -} - -static bool -input_ffmpeg_init(gcc_unused const config_param ¶m, - Error &error) -{ - av_register_all(); - - /* disable this plugin if there's no registered protocol */ - if (!input_ffmpeg_supported()) { - error.Set(ffmpeg_domain, "No protocol"); - return false; - } - - return true; -} - -static InputStream * -input_ffmpeg_open(const char *uri, - Mutex &mutex, Cond &cond, - Error &error) -{ - if (!g_str_has_prefix(uri, "gopher://") && - !g_str_has_prefix(uri, "rtp://") && - !g_str_has_prefix(uri, "rtsp://") && - !g_str_has_prefix(uri, "rtmp://") && - !g_str_has_prefix(uri, "rtmpt://") && - !g_str_has_prefix(uri, "rtmps://")) - return nullptr; - - AVIOContext *h; - int ret = avio_open(&h, uri, AVIO_FLAG_READ); - if (ret != 0) { - error.Set(ffmpeg_domain, ret, - "libavformat failed to open the URI"); - return nullptr; - } - - auto *i = new FfmpegInputStream(uri, mutex, cond, h); - return &i->base; -} - -static size_t -input_ffmpeg_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - FfmpegInputStream *i = (FfmpegInputStream *)is; - - int ret = avio_read(i->h, (unsigned char *)ptr, size); - if (ret <= 0) { - if (ret < 0) - error.Set(ffmpeg_domain, "avio_read() failed"); - - i->eof = true; - return false; - } - - is->offset += ret; - return (size_t)ret; -} - -static void -input_ffmpeg_close(InputStream *is) -{ - FfmpegInputStream *i = (FfmpegInputStream *)is; - - delete i; -} - -static bool -input_ffmpeg_eof(InputStream *is) -{ - FfmpegInputStream *i = (FfmpegInputStream *)is; - - return i->eof; -} - -static bool -input_ffmpeg_seek(InputStream *is, InputPlugin::offset_type offset, - int whence, - Error &error) -{ - FfmpegInputStream *i = (FfmpegInputStream *)is; - int64_t ret = avio_seek(i->h, offset, whence); - - if (ret >= 0) { - i->eof = false; - return true; - } else { - error.Set(ffmpeg_domain, "avio_seek() failed"); - return false; - } -} - -const InputPlugin input_plugin_ffmpeg = { - "ffmpeg", - input_ffmpeg_init, - nullptr, - input_ffmpeg_open, - input_ffmpeg_close, - nullptr, - nullptr, - nullptr, - nullptr, - input_ffmpeg_read, - input_ffmpeg_eof, - input_ffmpeg_seek, -}; diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/FfmpegInputPlugin.hxx deleted file mode 100644 index 9bc2eeaea..000000000 --- a/src/input/FfmpegInputPlugin.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX -#define MPD_FFMPEG_INPUT_PLUGIN_HXX - -/** - * An input plugin based on libavformat's "avio" library. - */ -extern const struct InputPlugin input_plugin_ffmpeg; - -#endif diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx deleted file mode 100644 index 26e40d609..000000000 --- a/src/input/FileInputPlugin.cxx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" /* must be first for large file support */ -#include "FileInputPlugin.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/Traits.hxx" -#include "system/fd_util.h" -#include "open.h" - -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <glib.h> - -static constexpr Domain file_domain("file"); - -struct FileInputStream { - InputStream base; - - int fd; - - FileInputStream(const char *path, int _fd, off_t size, - Mutex &mutex, Cond &cond) - :base(input_plugin_file, path, mutex, cond), - fd(_fd) { - base.size = size; - base.seekable = true; - base.ready = true; - } - - ~FileInputStream() { - close(fd); - } -}; - -static InputStream * -input_file_open(const char *filename, - Mutex &mutex, Cond &cond, - Error &error) -{ - int fd, ret; - struct stat st; - - if (!PathTraits::IsAbsoluteFS(filename)) - return nullptr; - - fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); - if (fd < 0) { - if (errno != ENOENT && errno != ENOTDIR) - error.FormatErrno("Failed to open \"%s\"", - filename); - return nullptr; - } - - ret = fstat(fd, &st); - if (ret < 0) { - error.FormatErrno("Failed to stat \"%s\"", filename); - close(fd); - return nullptr; - } - - if (!S_ISREG(st.st_mode)) { - error.Format(file_domain, "Not a regular file: %s", filename); - close(fd); - return nullptr; - } - -#ifdef POSIX_FADV_SEQUENTIAL - posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); -#endif - - FileInputStream *fis = new FileInputStream(filename, fd, st.st_size, - mutex, cond); - return &fis->base; -} - -static bool -input_file_seek(InputStream *is, InputPlugin::offset_type offset, - int whence, - Error &error) -{ - FileInputStream *fis = (FileInputStream *)is; - - offset = (InputPlugin::offset_type)lseek(fis->fd, (off_t)offset, whence); - if (offset < 0) { - error.SetErrno("Failed to seek"); - return false; - } - - is->offset = offset; - return true; -} - -static size_t -input_file_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - FileInputStream *fis = (FileInputStream *)is; - ssize_t nbytes; - - nbytes = read(fis->fd, ptr, size); - if (nbytes < 0) { - error.SetErrno("Failed to read"); - return 0; - } - - is->offset += nbytes; - return (size_t)nbytes; -} - -static void -input_file_close(InputStream *is) -{ - FileInputStream *fis = (FileInputStream *)is; - - delete fis; -} - -static bool -input_file_eof(InputStream *is) -{ - return is->offset >= is->size; -} - -const InputPlugin input_plugin_file = { - "file", - nullptr, - nullptr, - input_file_open, - input_file_close, - nullptr, - nullptr, - nullptr, - nullptr, - input_file_read, - input_file_eof, - input_file_seek, -}; diff --git a/src/input/FileInputPlugin.hxx b/src/input/FileInputPlugin.hxx deleted file mode 100644 index f76f4dd0e..000000000 --- a/src/input/FileInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_INPUT_FILE_HXX -#define MPD_INPUT_FILE_HXX - -extern const struct InputPlugin input_plugin_file; - -#endif diff --git a/src/input/IcyInputStream.cxx b/src/input/IcyInputStream.cxx new file mode 100644 index 000000000..fb82cdec6 --- /dev/null +++ b/src/input/IcyInputStream.cxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "IcyInputStream.hxx" +#include "tag/Tag.hxx" + +IcyInputStream::IcyInputStream(InputStream *_input) + :ProxyInputStream(_input), + input_tag(nullptr), icy_tag(nullptr), + override_offset(0) +{ +} + +IcyInputStream::~IcyInputStream() +{ + delete input_tag; + delete icy_tag; +} + +void +IcyInputStream::Update() +{ + ProxyInputStream::Update(); + + if (IsEnabled()) + offset = override_offset; +} + +Tag * +IcyInputStream::ReadTag() +{ + Tag *new_input_tag = ProxyInputStream::ReadTag(); + if (!IsEnabled()) + return new_input_tag; + + if (new_input_tag != nullptr) { + delete input_tag; + input_tag = new_input_tag; + } + + Tag *new_icy_tag = parser.ReadTag(); + if (new_icy_tag != nullptr) { + delete icy_tag; + icy_tag = new_icy_tag; + } + + if (new_input_tag == nullptr && new_icy_tag == nullptr) + /* no change */ + return nullptr; + + if (input_tag == nullptr && icy_tag == nullptr) + /* no tag */ + return nullptr; + + if (input_tag == nullptr) + return new Tag(*icy_tag); + + if (icy_tag == nullptr) + return new Tag(*input_tag); + + return Tag::Merge(*input_tag, *icy_tag); +} + +size_t +IcyInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + if (!IsEnabled()) + return ProxyInputStream::Read(ptr, read_size, error); + + while (true) { + size_t nbytes = ProxyInputStream::Read(ptr, read_size, error); + if (nbytes == 0) + return 0; + + size_t result = parser.ParseInPlace(ptr, nbytes); + if (result > 0) { + override_offset += result; + offset = override_offset; + return result; + } + } +} diff --git a/src/input/IcyInputStream.hxx b/src/input/IcyInputStream.hxx new file mode 100644 index 000000000..6a85f9ffd --- /dev/null +++ b/src/input/IcyInputStream.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICY_INPUT_STREAM_HXX +#define MPD_ICY_INPUT_STREAM_HXX + +#include "ProxyInputStream.hxx" +#include "IcyMetaDataParser.hxx" + +struct Tag; + +/** + * An #InputStream filter that parses Icy metadata. + */ +class IcyInputStream final : public ProxyInputStream { + IcyMetaDataParser parser; + + /** + * The #Tag object ready to be requested via ReadTag(). + */ + Tag *input_tag; + + /** + * The #Tag object ready to be requested via ReadTag(). + */ + Tag *icy_tag; + + offset_type override_offset; + +public: + IcyInputStream(InputStream *_input); + virtual ~IcyInputStream(); + + IcyInputStream(const IcyInputStream &) = delete; + IcyInputStream &operator=(const IcyInputStream &) = delete; + + void Enable(size_t _data_size) { + parser.Start(_data_size); + } + + bool IsEnabled() const { + return parser.IsDefined(); + } + + /* virtual methods from InputStream */ + void Update() override; + Tag *ReadTag() override; + size_t Read(void *ptr, size_t size, Error &error) override; +}; + +#endif diff --git a/src/input/Init.cxx b/src/input/Init.cxx new file mode 100644 index 000000000..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 = ∅ + } 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..1bdf44b77 --- /dev/null +++ b/src/input/InputPlugin.hxx @@ -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. + */ + +#ifndef MPD_INPUT_PLUGIN_HXX +#define MPD_INPUT_PLUGIN_HXX + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <stddef.h> +#include <stdint.h> + +#ifdef WIN32 +#include <windows.h> +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +struct config_param; +class InputStream; +class Error; +struct Tag; + +struct InputPlugin { + enum class InitResult { + /** + * A fatal error has occurred (e.g. misconfiguration). + * The #Error has been set. + */ + ERROR, + + /** + * The plugin was initialized successfully and is + * ready to be used. + */ + SUCCESS, + + /** + * The plugin is not available and shall be disabled. + * The #Error may be set describing the situation (to + * be logged). + */ + UNAVAILABLE, + }; + + 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 ¶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); +}; + +#endif diff --git a/src/input/InputStream.cxx b/src/input/InputStream.cxx new file mode 100644 index 000000000..657b9df09 --- /dev/null +++ b/src/input/InputStream.cxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "InputStream.hxx" +#include "thread/Cond.hxx" +#include "util/UriUtil.hxx" + +#include <assert.h> + +InputStream::~InputStream() +{ +} + +bool +InputStream::Check(gcc_unused Error &error) +{ + return true; +} + +void +InputStream::Update() +{ +} + +void +InputStream::SetReady() +{ + assert(!ready); + + ready = true; + cond.broadcast(); +} + +void +InputStream::WaitReady() +{ + while (true) { + Update(); + if (ready) + break; + + cond.wait(mutex); + } +} + +void +InputStream::LockWaitReady() +{ + const ScopeLock protect(mutex); + WaitReady(); +} + +bool +InputStream::CheapSeeking() const +{ + return IsSeekable() && !uri_has_scheme(uri.c_str()); +} + +bool +InputStream::Seek(gcc_unused offset_type new_offset, + gcc_unused Error &error) +{ + return false; +} + +bool +InputStream::LockSeek(offset_type _offset, Error &error) +{ + const ScopeLock protect(mutex); + return Seek(_offset, error); +} + +Tag * +InputStream::ReadTag() +{ + return nullptr; +} + +Tag * +InputStream::LockReadTag() +{ + const ScopeLock protect(mutex); + return ReadTag(); +} + +bool +InputStream::IsAvailable() +{ + return true; +} + +size_t +InputStream::LockRead(void *ptr, size_t _size, Error &error) +{ + assert(ptr != nullptr); + assert(_size > 0); + + const ScopeLock protect(mutex); + return Read(ptr, _size, error); +} + +bool +InputStream::LockIsEOF() +{ + const ScopeLock protect(mutex); + return IsEOF(); +} + diff --git a/src/input/InputStream.hxx b/src/input/InputStream.hxx new file mode 100644 index 000000000..25e99de3d --- /dev/null +++ b/src/input/InputStream.hxx @@ -0,0 +1,364 @@ +/* + * 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; + +class InputStream { +public: + typedef int64_t offset_type; + +private: + /** + * The absolute URI which was used to open this stream. + */ + std::string uri; + +public: + /** + * A mutex that protects the mutable attributes of this object + * and its implementation. It must be locked before calling + * any of the public methods. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Mutex &mutex; + + /** + * A cond that gets signalled when the state of this object + * changes from the I/O thread. The client of this object may + * wait on it. Optional, may be nullptr. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + Cond &cond; + +protected: + /** + * indicates whether the stream is ready for reading and + * whether the other attributes in this struct are valid + */ + bool ready; + + /** + * if true, then the stream is fully seekable + */ + bool seekable; + + /** + * the size of the resource, or -1 if unknown + */ + offset_type size; + + /** + * the current offset within the stream + */ + offset_type offset; + +private: + /** + * the MIME content type of the resource, or empty if unknown. + */ + std::string mime; + +public: + InputStream(const char *_uri, Mutex &_mutex, Cond &_cond) + :uri(_uri), + mutex(_mutex), cond(_cond), + ready(false), seekable(false), + size(-1), offset(0) { + assert(_uri != nullptr); + } + + /** + * Close the input stream and free resources. + * + * The caller must not lock the mutex. + */ + virtual ~InputStream(); + + /** + * Opens a new input stream. You may not access it until the "ready" + * flag is set. + * + * @param mutex a mutex that is used to protect this object; must be + * locked before calling any of the public methods + * @param cond a cond that gets signalled when the state of + * this object changes; may be nullptr if the caller doesn't want to get + * notifications + * @return an #InputStream object on success, nullptr on error + */ + gcc_nonnull_all + gcc_malloc + static InputStream *Open(const char *uri, Mutex &mutex, Cond &cond, + Error &error); + + /** + * Just like Open(), but waits for the stream to become ready. + * It is a wrapper for Open(), WaitReady() and Check(). + */ + gcc_malloc gcc_nonnull_all + static InputStream *OpenReady(const char *uri, + Mutex &mutex, Cond &cond, + Error &error); + + /** + * The absolute URI which was used to open this stream. + * + * No lock necessary for this method. + */ + const char *GetURI() const { + return uri.c_str(); + } + + void Lock() { + mutex.lock(); + } + + void Unlock() { + mutex.unlock(); + } + + /** + * Check for errors that may have occurred in the I/O thread. + * + * @return false on error + */ + virtual bool Check(Error &error); + + /** + * Update the public attributes. Call before accessing attributes + * such as "ready" or "offset". + */ + virtual void Update(); + + void SetReady(); + + /** + * Return whether the stream is ready for reading and whether + * the other attributes in this struct are valid. + * + * The caller must lock the mutex. + */ + bool IsReady() const { + return ready; + } + + void WaitReady(); + + /** + * Wrapper for WaitReady() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ + void LockWaitReady(); + + gcc_pure + bool HasMimeType() const { + assert(ready); + + return !mime.empty(); + } + + gcc_pure + const char *GetMimeType() const { + assert(ready); + + return mime.empty() ? nullptr : mime.c_str(); + } + + gcc_nonnull_all + void SetMimeType(const char *_mime) { + assert(!ready); + + mime = _mime; + } + + void SetMimeType(std::string &&_mime) { + assert(!ready); + + mime = std::move(_mime); + } + + gcc_nonnull_all + void OverrideMimeType(const char *_mime) { + assert(ready); + + mime = _mime; + } + + gcc_pure + bool KnownSize() const { + assert(ready); + + return size >= 0; + } + + gcc_pure + offset_type GetSize() const { + assert(ready); + + return size; + } + + void AddOffset(offset_type delta) { + assert(ready); + assert(offset >= 0); + assert(delta >= 0); + + offset += delta; + } + + gcc_pure + offset_type GetOffset() const { + assert(ready); + + return offset; + } + + gcc_pure + offset_type GetRest() const { + assert(ready); + assert(size >= 0); + assert(offset >= 0); + + return size - offset; + } + + gcc_pure + bool IsSeekable() const { + assert(ready); + + return seekable; + } + + /** + * Determines whether seeking is cheap. This is true for local files. + */ + gcc_pure + bool CheapSeeking() const; + + /** + * Seeks to the specified position in the stream. This will most + * likely fail if the "seekable" flag is false. + * + * The caller must lock the mutex. + * + * @param offset the relative offset + */ + virtual bool Seek(offset_type offset, Error &error); + + /** + * Wrapper for Seek() which locks and unlocks the mutex; the + * caller must not be holding it already. + */ + bool LockSeek(offset_type offset, Error &error); + + /** + * Rewind to the beginning of the stream. This is a wrapper + * for Seek(0, error). + */ + bool Rewind(Error &error) { + return Seek(0, error); + } + + bool LockRewind(Error &error) { + return LockSeek(0, error); + } + + /** + * Returns true if the stream has reached end-of-file. + * + * The caller must lock the mutex. + */ + gcc_pure + virtual bool IsEOF() = 0; + + /** + * Wrapper for IsEOF() which locks and unlocks the mutex; the + * caller must not be holding it already. + */ + gcc_pure + bool LockIsEOF(); + + /** + * Reads the tag from the stream. + * + * The caller must lock the mutex. + * + * @return a tag object which must be freed by the caller, or + * nullptr if the tag has not changed since the last call + */ + gcc_malloc + virtual Tag *ReadTag(); + + /** + * Wrapper for ReadTag() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ + gcc_malloc + Tag *LockReadTag(); + + /** + * Returns true if the next read operation will not block: either data + * is available, or end-of-stream has been reached, or an error has + * occurred. + * + * The caller must lock the mutex. + */ + gcc_pure + virtual bool IsAvailable(); + + /** + * Reads data from the stream into the caller-supplied buffer. + * Returns 0 on error or eof (check with IsEOF()). + * + * The caller must lock the mutex. + * + * @param is the InputStream object + * @param ptr the buffer to read into + * @param size the maximum number of bytes to read + * @return the number of bytes read + */ + gcc_nonnull_all + virtual size_t Read(void *ptr, size_t size, Error &error) = 0; + + /** + * Wrapper for Read() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ + gcc_nonnull_all + size_t LockRead(void *ptr, size_t size, Error &error); +}; + +#endif diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx deleted file mode 100644 index e97c1eb3f..000000000 --- a/src/input/MmsInputPlugin.cxx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "MmsInputPlugin.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <glib.h> -#include <libmms/mmsx.h> - -#include <string.h> -#include <errno.h> - -struct MmsInputStream { - InputStream base; - - mmsx_t *mms; - - bool eof; - - MmsInputStream(const char *uri, - Mutex &mutex, Cond &cond, - mmsx_t *_mms) - :base(input_plugin_mms, uri, mutex, cond), - mms(_mms), eof(false) { - /* XX is this correct? at least this selects the ffmpeg - decoder, which seems to work fine*/ - base.mime = "audio/x-ms-wma"; - - base.ready = true; - } - - ~MmsInputStream() { - mmsx_close(mms); - } -}; - -static constexpr Domain mms_domain("mms"); - -static InputStream * -input_mms_open(const char *url, - Mutex &mutex, Cond &cond, - Error &error) -{ - if (!g_str_has_prefix(url, "mms://") && - !g_str_has_prefix(url, "mmsh://") && - !g_str_has_prefix(url, "mmst://") && - !g_str_has_prefix(url, "mmsu://")) - return nullptr; - - const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024); - if (mms == nullptr) { - error.Set(mms_domain, "mmsx_connect() failed"); - return nullptr; - } - - auto m = new MmsInputStream(url, mutex, cond, mms); - return &m->base; -} - -static size_t -input_mms_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - MmsInputStream *m = (MmsInputStream *)is; - int ret; - - ret = mmsx_read(nullptr, m->mms, (char *)ptr, size); - if (ret <= 0) { - if (ret < 0) - error.SetErrno("mmsx_read() failed"); - - m->eof = true; - return false; - } - - is->offset += ret; - - return (size_t)ret; -} - -static void -input_mms_close(InputStream *is) -{ - MmsInputStream *m = (MmsInputStream *)is; - - delete m; -} - -static bool -input_mms_eof(InputStream *is) -{ - MmsInputStream *m = (MmsInputStream *)is; - - return m->eof; -} - -const InputPlugin input_plugin_mms = { - "mms", - nullptr, - nullptr, - input_mms_open, - input_mms_close, - nullptr, - nullptr, - nullptr, - nullptr, - input_mms_read, - input_mms_eof, - nullptr, -}; diff --git a/src/input/MmsInputPlugin.hxx b/src/input/MmsInputPlugin.hxx deleted file mode 100644 index e3d3ba3c8..000000000 --- a/src/input/MmsInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef INPUT_MMS_H -#define INPUT_MMS_H - -extern const struct InputPlugin input_plugin_mms; - -#endif diff --git a/src/input/Open.cxx b/src/input/Open.cxx new file mode 100644 index 000000000..6e89569d6 --- /dev/null +++ b/src/input/Open.cxx @@ -0,0 +1,71 @@ +/* + * 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/Error.hxx" +#include "util/Domain.hxx" + +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) { + is = input_rewind_open(is); + + return is; + } else if (error.IsDefined()) + return nullptr; + } + + error.Set(input_domain, "Unrecognized URI"); + return nullptr; +} + +InputStream * +InputStream::OpenReady(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + InputStream *is = Open(uri, mutex, cond, error); + if (is == nullptr) + return nullptr; + + mutex.lock(); + is->WaitReady(); + bool success = is->Check(error); + mutex.unlock(); + + if (!success) { + delete is; + is = nullptr; + } + + return is; +} diff --git a/src/input/ProxyInputStream.cxx b/src/input/ProxyInputStream.cxx new file mode 100644 index 000000000..d65fb5df1 --- /dev/null +++ b/src/input/ProxyInputStream.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ProxyInputStream.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> + +ProxyInputStream::ProxyInputStream(InputStream *_input) + :InputStream(_input->GetURI(), _input->mutex, _input->cond), + input(*_input) {} + +ProxyInputStream::~ProxyInputStream() +{ + delete &input; +} + +void +ProxyInputStream::CopyAttributes() +{ + if (input.IsReady()) { + if (!IsReady()) { + if (input.HasMimeType()) + SetMimeType(input.GetMimeType()); + + size = input.GetSize(); + seekable = input.IsSeekable(); + SetReady(); + } + + offset = input.GetOffset(); + } +} + +bool +ProxyInputStream::Check(Error &error) +{ + return input.Check(error); +} + +void +ProxyInputStream::Update() +{ + input.Update(); + CopyAttributes(); +} + +bool +ProxyInputStream::Seek(offset_type new_offset, Error &error) +{ + bool success = input.Seek(new_offset, error); + CopyAttributes(); + return success; +} + +bool +ProxyInputStream::IsEOF() +{ + return input.IsEOF(); +} + +Tag * +ProxyInputStream::ReadTag() +{ + return input.ReadTag(); +} + +bool +ProxyInputStream::IsAvailable() +{ + return input.IsAvailable(); +} + +size_t +ProxyInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + size_t nbytes = input.Read(ptr, read_size, error); + CopyAttributes(); + return nbytes; +} diff --git a/src/input/ProxyInputStream.hxx b/src/input/ProxyInputStream.hxx new file mode 100644 index 000000000..727ae5917 --- /dev/null +++ b/src/input/ProxyInputStream.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PROXY_INPUT_STREAM_HXX +#define MPD_PROXY_INPUT_STREAM_HXX + +#include "InputStream.hxx" + +struct Tag; + +/** + * An #InputStream that forwards all methods call to another + * #InputStream instance. This can be used as a base class to + * override selected methods. + */ +class ProxyInputStream : public InputStream { +protected: + InputStream &input; + +public: + gcc_nonnull_all + ProxyInputStream(InputStream *_input); + + virtual ~ProxyInputStream(); + + ProxyInputStream(const ProxyInputStream &) = delete; + ProxyInputStream &operator=(const ProxyInputStream &) = delete; + + /* virtual methods from InputStream */ + bool Check(Error &error) override; + void Update() override; + bool Seek(offset_type new_offset, Error &error) override; + bool IsEOF() override; + Tag *ReadTag() override; + bool IsAvailable() override; + size_t Read(void *ptr, size_t read_size, Error &error) override; + +protected: + /** + * Copy public attributes from the underlying input stream to the + * "rewind" input stream. This function is called when a method of + * the underlying stream has returned, which may have modified these + * attributes. + */ + void CopyAttributes(); +}; + +#endif diff --git a/src/input/Registry.cxx b/src/input/Registry.cxx new file mode 100644 index 000000000..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/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx deleted file mode 100644 index e11f56631..000000000 --- a/src/input/RewindInputPlugin.cxx +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "RewindInputPlugin.hxx" -#include "InputStream.hxx" -#include "InputPlugin.hxx" -#include "tag/Tag.hxx" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -extern const InputPlugin rewind_input_plugin; - -struct RewindInputStream { - InputStream base; - - InputStream *input; - - /** - * The read position within the buffer. Undefined as long as - * ReadingFromBuffer() returns false. - */ - size_t head; - - /** - * The write/append position within the buffer. - */ - size_t tail; - - /** - * The size of this buffer is the maximum number of bytes - * which can be rewinded cheaply without passing the "seek" - * call to CURL. - * - * The origin of this buffer is always the beginning of the - * stream (offset 0). - */ - char buffer[64 * 1024]; - - RewindInputStream(InputStream *_input) - :base(rewind_input_plugin, _input->uri.c_str(), - _input->mutex, _input->cond), - input(_input), tail(0) { - } - - ~RewindInputStream() { - input->Close(); - } - - /** - * Are we currently reading from the buffer, and does the - * buffer contain more data for the next read operation? - */ - bool ReadingFromBuffer() const { - return tail > 0 && base.offset < input->offset; - } - - /** - * Copy public attributes from the underlying input stream to the - * "rewind" input stream. This function is called when a method of - * the underlying stream has returned, which may have modified these - * attributes. - */ - void CopyAttributes() { - InputStream *dest = &base; - const InputStream *src = input; - - assert(dest != src); - - bool dest_ready = dest->ready; - - dest->ready = src->ready; - dest->seekable = src->seekable; - dest->size = src->size; - dest->offset = src->offset; - - if (!dest_ready && src->ready) - dest->mime = src->mime; - } -}; - -static void -input_rewind_close(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - delete r; -} - -static bool -input_rewind_check(InputStream *is, Error &error) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return r->input->Check(error); -} - -static void -input_rewind_update(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - if (!r->ReadingFromBuffer()) - r->CopyAttributes(); -} - -static Tag * -input_rewind_tag(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return r->input->ReadTag(); -} - -static bool -input_rewind_available(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return r->input->IsAvailable(); -} - -static size_t -input_rewind_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - RewindInputStream *r = (RewindInputStream *)is; - - if (r->ReadingFromBuffer()) { - /* buffered read */ - - assert(r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - if (size > r->tail - r->head) - size = r->tail - r->head; - - memcpy(ptr, r->buffer + r->head, size); - r->head += size; - is->offset += size; - - return size; - } else { - /* pass method call to underlying stream */ - - size_t nbytes = r->input->Read(ptr, size, error); - - if (r->input->offset > (InputPlugin::offset_type)sizeof(r->buffer)) - /* disable buffering */ - r->tail = 0; - else if (r->tail == (size_t)is->offset) { - /* append to buffer */ - - memcpy(r->buffer + r->tail, ptr, nbytes); - r->tail += nbytes; - - assert(r->tail == (size_t)r->input->offset); - } - - r->CopyAttributes(); - - return nbytes; - } -} - -static bool -input_rewind_eof(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return !r->ReadingFromBuffer() && r->input->IsEOF(); -} - -static bool -input_rewind_seek(InputStream *is, InputPlugin::offset_type offset, - int whence, - Error &error) -{ - RewindInputStream *r = (RewindInputStream *)is; - - assert(is->ready); - - if (whence == SEEK_SET && r->tail > 0 && - offset <= (InputPlugin::offset_type)r->tail) { - /* buffered seek */ - - assert(!r->ReadingFromBuffer() || - r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input->offset); - - r->head = (size_t)offset; - is->offset = offset; - - return true; - } else { - bool success = r->input->Seek(offset, whence, error); - r->CopyAttributes(); - - /* disable the buffer, because r->input has left the - buffered range now */ - r->tail = 0; - - return success; - } -} - -const InputPlugin rewind_input_plugin = { - nullptr, - nullptr, - nullptr, - nullptr, - input_rewind_close, - input_rewind_check, - input_rewind_update, - input_rewind_tag, - input_rewind_available, - input_rewind_read, - input_rewind_eof, - input_rewind_seek, -}; - -InputStream * -input_rewind_open(InputStream *is) -{ - assert(is != nullptr); - assert(is->offset == 0); - - if (is->seekable) - /* seekable resources don't need this plugin */ - return is; - - RewindInputStream *c = new RewindInputStream(is); - return &c->base; -} diff --git a/src/input/RewindInputPlugin.hxx b/src/input/RewindInputPlugin.hxx deleted file mode 100644 index 2d461970a..000000000 --- a/src/input/RewindInputPlugin.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * A wrapper for an input_stream object which allows cheap buffered - * rewinding. This is useful while detecting the stream codec (let - * each decoder plugin peek a portion from the stream). - */ - -#ifndef MPD_INPUT_REWIND_HXX -#define MPD_INPUT_REWIND_HXX - -#include "check.h" - -struct InputStream; - -InputStream * -input_rewind_open(InputStream *is); - -#endif diff --git a/src/input/TextInputStream.cxx b/src/input/TextInputStream.cxx new file mode 100644 index 000000000..c23616782 --- /dev/null +++ b/src/input/TextInputStream.cxx @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TextInputStream.hxx" +#include "InputStream.hxx" +#include "util/Error.hxx" +#include "util/TextFile.hxx" +#include "Log.hxx" + +#include <assert.h> + +char * +TextInputStream::ReadLine() +{ + char *line = ReadBufferedLine(buffer); + if (line != nullptr) + return line; + + while (true) { + auto dest = buffer.Write(); + if (dest.size < 2) { + /* end of file (or line too long): terminate + the current line */ + + assert(!dest.IsEmpty()); + dest[0] = 0; + line = buffer.Read().data; + buffer.Clear(); + return line; + } + + /* reserve one byte for the null terminator if the + last line is not terminated by a newline + character */ + --dest.size; + + Error error; + size_t nbytes = is.LockRead(dest.data, dest.size, error); + if (nbytes > 0) + buffer.Append(nbytes); + else if (error.IsDefined()) { + LogError(error); + return nullptr; + } + + line = ReadBufferedLine(buffer); + if (line != nullptr) + return line; + + if (nbytes == 0) + return nullptr; + } +} diff --git a/src/input/TextInputStream.hxx b/src/input/TextInputStream.hxx new file mode 100644 index 000000000..6f39d22cf --- /dev/null +++ b/src/input/TextInputStream.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_INPUT_STREAM_HXX +#define MPD_TEXT_INPUT_STREAM_HXX + +#include "util/StaticFifoBuffer.hxx" + +class InputStream; + +class TextInputStream { + InputStream &is; + StaticFifoBuffer<char, 4096> buffer; + +public: + /** + * Wraps an existing #input_stream object into a #TextInputStream, + * to read its contents as text lines. + * + * @param _is an open #input_stream object + */ + explicit TextInputStream(InputStream &_is) + :is(_is) {} + + TextInputStream(const TextInputStream &) = delete; + TextInputStream& operator=(const TextInputStream &) = delete; + + /** + * Reads the next line from the stream with newline character stripped. + * + * @return a pointer to the line, or nullptr on end-of-file or error + */ + char *ReadLine(); +}; + +#endif diff --git a/src/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx new file mode 100644 index 000000000..dc08f3b6b --- /dev/null +++ b/src/input/ThreadInputStream.cxx @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ThreadInputStream.hxx" +#include "thread/Name.hxx" +#include "util/CircularBuffer.hxx" +#include "util/HugeAllocator.hxx" + +#include <assert.h> +#include <string.h> + +ThreadInputStream::~ThreadInputStream() +{ + Lock(); + close = true; + wake_cond.signal(); + Unlock(); + + Cancel(); + + thread.Join(); + + if (buffer != nullptr) { + buffer->Clear(); + HugeFree(buffer->Write().data, buffer_size); + delete buffer; + } +} + +InputStream * +ThreadInputStream::Start(Error &error) +{ + assert(buffer == nullptr); + + void *p = HugeAllocate(buffer_size); + if (p == nullptr) { + error.SetErrno(); + return nullptr; + } + + buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size); + + if (!thread.Start(ThreadFunc, this, error)) + return nullptr; + + return this; +} + +inline void +ThreadInputStream::ThreadFunc() +{ + FormatThreadName("input:%s", plugin); + + Lock(); + if (!Open(postponed_error)) { + cond.broadcast(); + Unlock(); + return; + } + + /* we're ready, tell it to our client */ + SetReady(); + + while (!close) { + assert(!postponed_error.IsDefined()); + + auto w = buffer->Write(); + if (w.IsEmpty()) { + wake_cond.wait(mutex); + } else { + Unlock(); + + Error error; + size_t nbytes = Read(w.data, w.size, error); + + Lock(); + cond.broadcast(); + + if (nbytes == 0) { + eof = true; + postponed_error = std::move(error); + break; + } + + buffer->Append(nbytes); + } + } + + Unlock(); + + Close(); +} + +void +ThreadInputStream::ThreadFunc(void *ctx) +{ + ThreadInputStream &tis = *(ThreadInputStream *)ctx; + tis.ThreadFunc(); +} + +bool +ThreadInputStream::Check(Error &error) +{ + if (postponed_error.IsDefined()) { + error = std::move(postponed_error); + return false; + } + + return true; +} + +bool +ThreadInputStream::IsAvailable() +{ + return !buffer->IsEmpty() || eof || postponed_error.IsDefined(); +} + +inline size_t +ThreadInputStream::Read(void *ptr, size_t read_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(read_size, r.size); + memcpy(ptr, r.data, nbytes); + buffer->Consume(nbytes); + wake_cond.broadcast(); + offset += nbytes; + return nbytes; + } + + if (eof) + return 0; + + cond.wait(mutex); + } +} + +bool +ThreadInputStream::IsEOF() +{ + return eof; +} diff --git a/src/input/ThreadInputStream.hxx b/src/input/ThreadInputStream.hxx new file mode 100644 index 000000000..c6ac7669c --- /dev/null +++ b/src/input/ThreadInputStream.hxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_INPUT_STREAM_HXX +#define MPD_THREAD_INPUT_STREAM_HXX + +#include "check.h" +#include "InputStream.hxx" +#include "thread/Thread.hxx" +#include "thread/Cond.hxx" +#include "util/Error.hxx" + +#include <stdint.h> + +template<typename T> class CircularBuffer; + +/** + * Helper class for moving InputStream implementations with blocking + * backend library implementation to a dedicated thread. Data is + * being read into a ring buffer, and that buffer is then consumed by + * another thread using the regular #InputStream API. This class + * manages the thread and the buffer. + * + * This works only for "streams": unknown length, no seeking, no tags. + */ +class ThreadInputStream : public InputStream { + const char *const plugin; + + Thread thread; + + /** + * Signalled when the thread shall be woken up: when data from + * the buffer has been consumed and when the stream shall be + * closed. + */ + Cond wake_cond; + + Error postponed_error; + + const size_t buffer_size; + CircularBuffer<uint8_t> *buffer; + + /** + * Shall the stream be closed? + */ + bool close; + + /** + * Has the end of the stream been seen by the thread? + */ + bool eof; + +public: + ThreadInputStream(const char *_plugin, + const char *_uri, Mutex &_mutex, Cond &_cond, + size_t _buffer_size) + :InputStream(_uri, _mutex, _cond), + plugin(_plugin), + buffer_size(_buffer_size), + buffer(nullptr), + close(false), eof(false) {} + + virtual ~ThreadInputStream(); + + /** + * Initialize the object and start the thread. + * + * @return false on error + */ + InputStream *Start(Error &error); + + /* virtual methods from InputStream */ + bool Check(Error &error) override final; + bool IsEOF() override final; + bool IsAvailable() override final; + size_t Read(void *ptr, size_t size, Error &error) override final; + +protected: + void SetMimeType(const char *_mime) { + assert(thread.IsInside()); + + InputStream::SetMimeType(_mime); + } + + /* to be implemented by the plugin */ + + /** + * Optional initialization after entering the thread. After + * this returns with success, the InputStream::ready flag is + * set. + * + * The #InputStream is locked. Unlock/relock it if you do a + * blocking operation. + */ + virtual bool Open(gcc_unused Error &error) { + return true; + } + + /** + * Read from the stream. + * + * The #InputStream is not locked. + * + * @return 0 on end-of-file or on error + */ + virtual size_t ThreadRead(void *ptr, size_t size, Error &error) = 0; + + /** + * Optional deinitialization before leaving the thread. + * + * The #InputStream is not locked. + */ + virtual void Close() {} + + /** + * Called from the client thread to cancel a Read() inside the + * thread. + * + * The #InputStream is not locked. + */ + virtual void Cancel() {} + +private: + void ThreadFunc(); + static void ThreadFunc(void *ctx); +}; + +#endif diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx new file mode 100644 index 000000000..82b96f7df --- /dev/null +++ b/src/input/plugins/AlsaInputPlugin.cxx @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * ALSA code based on an example by Paul Davis released under GPL here: + * http://equalarea.com/paul/alsa-audio.html + * and one by Matthias Nagorni, also GPL, here: + * http://alsamodular.sourceforge.net/alsa_programming_howto.html + */ + +#include "config.h" +#include "AlsaInputPlugin.hxx" +#include "../InputPlugin.hxx" +#include "../InputStream.hxx" +#include "util/Domain.hxx" +#include "util/Error.hxx" +#include "util/StringUtil.hxx" +#include "util/ReusableArray.hxx" + +#include "Log.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "event/Call.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "IOThread.hxx" + +#include <alsa/asoundlib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain alsa_input_domain("alsa"); + +static constexpr const char *default_device = "hw:0,0"; + +// the following defaults are because the PcmDecoderPlugin forces CD format +static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16; +static constexpr int default_channels = 2; // stereo +static constexpr unsigned int default_rate = 44100; // cd quality + +/** + * This value should be the same as the read buffer size defined in + * PcmDecoderPlugin.cxx:pcm_stream_decode(). + * We use it to calculate how many audio frames to buffer in the alsa driver + * before reading from the device. snd_pcm_readi() blocks until that many + * frames are ready. + */ +static constexpr size_t read_buffer_size = 4096; + +class AlsaInputStream final + : public InputStream, + MultiSocketMonitor, DeferredMonitor { + snd_pcm_t *capture_handle; + size_t frame_size; + int frames_to_read; + bool eof; + + /** + * Is somebody waiting for data? This is set by method + * Available(). + */ + std::atomic_bool waiting; + + ReusableArray<pollfd> pfd_buffer; + +public: + AlsaInputStream(EventLoop &loop, + const char *_uri, Mutex &_mutex, Cond &_cond, + snd_pcm_t *_handle, int _frame_size) + :InputStream(_uri, _mutex, _cond), + MultiSocketMonitor(loop), + DeferredMonitor(loop), + capture_handle(_handle), + frame_size(_frame_size), + eof(false) + { + assert(_uri != nullptr); + assert(_handle != nullptr); + + /* this mime type forces use of the PcmDecoderPlugin. + Needs to be generalised when/if that decoder is + updated to support other audio formats */ + SetMimeType("audio/x-mpd-cdda-pcm"); + InputStream::SetReady(); + + frames_to_read = read_buffer_size / frame_size; + + snd_pcm_start(capture_handle); + + DeferredMonitor::Schedule(); + } + + ~AlsaInputStream() { + snd_pcm_close(capture_handle); + } + + using DeferredMonitor::GetEventLoop; + + static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond, + Error &error); + + /* virtual methods from InputStream */ + + bool IsEOF() override { + return eof; + } + + bool IsAvailable() override { + if (snd_pcm_avail(capture_handle) > frames_to_read) + return true; + + if (!waiting.exchange(true)) + SafeInvalidateSockets(); + + return false; + } + + size_t Read(void *ptr, size_t size, Error &error) override; + +private: + static snd_pcm_t *OpenDevice(const char *device, int rate, + snd_pcm_format_t format, int channels, + Error &error); + + int Recover(int err); + + void SafeInvalidateSockets() { + DeferredMonitor::Schedule(); + } + + virtual void RunDeferred() override { + InvalidateSockets(); + } + + virtual int PrepareSockets() override; + virtual void DispatchSockets() override; +}; + +inline InputStream * +AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond, + Error &error) +{ + const char *const scheme = "alsa://"; + if (!StringStartsWith(uri, scheme)) + return nullptr; + + const char *device = uri + strlen(scheme); + if (strlen(device) == 0) + device = default_device; + + /* placeholders - eventually user-requested audio format will + be passed via the URI. For now we just force the + defaults */ + int rate = default_rate; + snd_pcm_format_t format = default_format; + int channels = default_channels; + + snd_pcm_t *handle = OpenDevice(device, rate, format, channels, + error); + if (handle == nullptr) + return nullptr; + + int frame_size = snd_pcm_format_width(format) / 8 * channels; + return new AlsaInputStream(io_thread_get(), + uri, mutex, cond, + handle, frame_size); +} + +size_t +AlsaInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + assert(ptr != nullptr); + + int num_frames = read_size / frame_size; + int ret; + while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) { + if (Recover(ret) < 0) { + eof = true; + error.Format(alsa_input_domain, + "PCM error - stream aborted"); + return 0; + } + } + + size_t nbytes = ret * frame_size; + offset += nbytes; + return nbytes; +} + +int +AlsaInputStream::PrepareSockets() +{ + if (!waiting) { + ClearSocketList(); + return -1; + } + + int count = snd_pcm_poll_descriptors_count(capture_handle); + if (count < 0) { + ClearSocketList(); + return -1; + } + + struct pollfd *pfds = pfd_buffer.Get(count); + + count = snd_pcm_poll_descriptors(capture_handle, pfds, count); + if (count < 0) + count = 0; + + ReplaceSocketList(pfds, count); + return -1; +} + +void +AlsaInputStream::DispatchSockets() +{ + waiting = false; + + const ScopeLock protect(mutex); + /* wake up the thread that is waiting for more data */ + cond.broadcast(); +} + +inline int +AlsaInputStream::Recover(int err) +{ + switch(err) { + case -EPIPE: + LogDebug(alsa_input_domain, "Buffer Overrun"); + // drop through + case -ESTRPIPE: + case -EINTR: + err = snd_pcm_recover(capture_handle, err, 1); + break; + default: + // something broken somewhere, give up + err = -1; + } + return err; +} + +inline snd_pcm_t * +AlsaInputStream::OpenDevice(const char *device, + int rate, snd_pcm_format_t format, int channels, + Error &error) +{ + snd_pcm_t *capture_handle; + int err; + if ((err = snd_pcm_open(&capture_handle, device, + SND_PCM_STREAM_CAPTURE, 0)) < 0) { + error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err)); + return nullptr; + } + + snd_pcm_hw_params_t *hw_params; + if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { + error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err)); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) { + error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) { + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err)); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) { + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err)); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) { + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err)); + return nullptr; + } + + /* period needs to be big enough so that poll() doesn't fire too often, + * but small enough that buffer overruns don't occur if Read() is not + * invoked often enough. + * the calculation here is empirical; however all measurements were + * done using 44100:16:2. When we extend this plugin to support + * other audio formats then this may need to be revisited */ + snd_pcm_uframes_t period = read_buffer_size * 2; + int direction = -1; + if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, + &period, &direction)) < 0) { + error.Format(alsa_input_domain, "Cannot set period size (%s)", + snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) { + error.Format(alsa_input_domain, "Cannot set parameters (%s)", + snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + snd_pcm_hw_params_free (hw_params); + + snd_pcm_sw_params_t *sw_params; + + snd_pcm_sw_params_malloc(&sw_params); + snd_pcm_sw_params_current(capture_handle, sw_params); + + if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params, + period)) < 0) { + error.Format(alsa_input_domain, + "unable to set start threshold (%s)", snd_strerror(err)); + snd_pcm_sw_params_free(sw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) { + error.Format(alsa_input_domain, + "unable to install sw params (%s)", snd_strerror(err)); + snd_pcm_sw_params_free(sw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + snd_pcm_sw_params_free(sw_params); + + snd_pcm_prepare(capture_handle); + + return capture_handle; +} + +/*######################### Plugin Functions ##############################*/ + +static InputStream * +alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error) +{ + return AlsaInputStream::Create(uri, mutex, cond, error); +} + +const struct InputPlugin input_plugin_alsa = { + "alsa", + nullptr, + nullptr, + alsa_input_open, +}; diff --git a/src/input/plugins/AlsaInputPlugin.hxx b/src/input/plugins/AlsaInputPlugin.hxx new file mode 100644 index 000000000..dddf7dfd7 --- /dev/null +++ b/src/input/plugins/AlsaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ALSA_INPUT_PLUGIN_HXX +#define MPD_ALSA_INPUT_PLUGIN_HXX + +#include "../InputPlugin.hxx" + +extern const struct InputPlugin input_plugin_alsa; + + +#endif diff --git a/src/input/plugins/ArchiveInputPlugin.cxx b/src/input/plugins/ArchiveInputPlugin.cxx new file mode 100644 index 000000000..b51dc6835 --- /dev/null +++ b/src/input/plugins/ArchiveInputPlugin.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ArchiveInputPlugin.hxx" +#include "archive/ArchiveDomain.hxx" +#include "archive/ArchiveLookup.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" +#include "archive/ArchiveFile.hxx" +#include "../InputPlugin.hxx" +#include "fs/Traits.hxx" +#include "fs/Path.hxx" +#include "util/Alloc.hxx" +#include "Log.hxx" + +#include <stdlib.h> + +/** + * select correct archive plugin to handle the input stream + * may allow stacking of archive plugins. for example for handling + * tar.gz a gzip handler opens file (through inputfile stream) + * then it opens a tar handler and sets gzip inputstream as + * parent_stream so tar plugin fetches file data from gzip + * plugin and gzip fetches file from disk + */ +static InputStream * +input_archive_open(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + const ArchivePlugin *arplug; + InputStream *is; + + if (!PathTraitsFS::IsAbsolute(pathname)) + return nullptr; + + 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); + free(pname); + return nullptr; + } + + //check which archive plugin to use (by ext) + arplug = archive_plugin_from_suffix(suffix); + if (!arplug) { + FormatWarning(archive_domain, + "can't handle archive %s", archive); + free(pname); + return nullptr; + } + + auto file = archive_file_open(arplug, Path::FromFS(archive), error); + if (file == nullptr) { + free(pname); + return nullptr; + } + + //setup fileops + is = file->OpenStream(filename, mutex, cond, error); + free(pname); + file->Close(); + + return is; +} + +const InputPlugin input_plugin_archive = { + "archive", + nullptr, + nullptr, + input_archive_open, +}; diff --git a/src/input/plugins/ArchiveInputPlugin.hxx b/src/input/plugins/ArchiveInputPlugin.hxx new file mode 100644 index 000000000..024723726 --- /dev/null +++ b/src/input/plugins/ArchiveInputPlugin.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_ARCHIVE_HXX +#define MPD_INPUT_ARCHIVE_HXX + +extern const struct InputPlugin input_plugin_archive; + +#endif diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx new file mode 100644 index 000000000..6f1ea976a --- /dev/null +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * CD-Audio handling (requires libcdio_paranoia) + */ + +#include "config.h" +#include "CdioParanoiaInputPlugin.hxx" +#include "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" +#include "fs/AllocatedPath.hxx" +#include "Log.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigError.hxx" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H +#include <cdio/paranoia/paranoia.h> +#else +#include <cdio/paranoia.h> +#endif + +#include <cdio/cd_types.h> + +class CdioParanoiaInputStream final : public InputStream { + cdrom_drive_t *const drv; + CdIo_t *const cdio; + cdrom_paranoia_t *const para; + + const lsn_t lsn_from, lsn_to; + int lsn_relofs; + + char buffer[CDIO_CD_FRAMESIZE_RAW]; + int buffer_lsn; + + public: + CdioParanoiaInputStream(const char *_uri, Mutex &_mutex, Cond &_cond, + cdrom_drive_t *_drv, CdIo_t *_cdio, + bool reverse_endian, + lsn_t _lsn_from, lsn_t _lsn_to) + :InputStream(_uri, _mutex, _cond), + drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)), + lsn_from(_lsn_from), lsn_to(_lsn_to), + lsn_relofs(0), + buffer_lsn(-1) + { + /* Set reading mode for full paranoia, but allow + skipping sectors. */ + paranoia_modeset(para, + PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + + /* seek to beginning of the track */ + cdio_paranoia_seek(para, lsn_from, SEEK_SET); + + seekable = true; + size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; + + /* hack to make MPD select the "pcm" decoder plugin */ + SetMimeType(reverse_endian + ? "audio/x-mpd-cdda-pcm-reverse" + : "audio/x-mpd-cdda-pcm"); + SetReady(); + } + + ~CdioParanoiaInputStream() { + cdio_paranoia_free(para); + cdio_cddap_close_no_free_cdio(drv); + cdio_destroy(cdio); + } + + /* virtual methods from InputStream */ + bool IsEOF() override; + size_t Read(void *ptr, size_t size, Error &error) override; + bool Seek(offset_type offset, Error &error) override; +}; + +static constexpr Domain cdio_domain("cdio"); + +static bool default_reverse_endian; + +static InputPlugin::InitResult +input_cdio_init(const config_param ¶m, Error &error) +{ + const char *value = param.GetBlockValue("default_byte_order"); + if (value != nullptr) { + if (strcmp(value, "little_endian") == 0) + default_reverse_endian = IsBigEndian(); + else if (strcmp(value, "big_endian") == 0) + default_reverse_endian = IsLittleEndian(); + else { + error.Format(config_domain, 0, + "Unrecognized 'default_byte_order' setting: %s", + value); + return InputPlugin::InitResult::ERROR; + } + } + + return InputPlugin::InitResult::SUCCESS; +} + +struct cdio_uri { + char device[64]; + int track; +}; + +static bool +parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error) +{ + if (!StringStartsWith(src, "cdda://")) + return false; + + src += 7; + + if (*src == 0) { + /* play the whole CD in the default drive */ + dest->device[0] = 0; + dest->track = -1; + return true; + } + + const char *slash = strrchr(src, '/'); + if (slash == nullptr) { + /* play the whole CD in the specified drive */ + g_strlcpy(dest->device, src, sizeof(dest->device)); + dest->track = -1; + return true; + } + + size_t device_length = slash - src; + if (device_length >= sizeof(dest->device)) + device_length = sizeof(dest->device) - 1; + + memcpy(dest->device, src, device_length); + dest->device[device_length] = 0; + + const char *track = slash + 1; + + char *endptr; + dest->track = strtoul(track, &endptr, 10); + if (*endptr != 0) { + error.Set(cdio_domain, "Malformed track number"); + return false; + } + + if (endptr == track) + /* play the whole CD */ + dest->track = -1; + + return true; +} + +static AllocatedPath +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, + false); + if (devices == nullptr) + return AllocatedPath::Null(); + + AllocatedPath path = AllocatedPath::FromFS(devices[0]); + cdio_free_device_list(devices); + return path; +} + +static InputStream * +input_cdio_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + struct cdio_uri parsed_uri; + if (!parse_cdio_uri(&parsed_uri, uri, error)) + return nullptr; + + /* get list of CD's supporting CD-DA */ + const AllocatedPath device = parsed_uri.device[0] != 0 + ? AllocatedPath::FromFS(parsed_uri.device) + : cdio_detect_device(); + if (device.IsNull()) { + error.Set(cdio_domain, + "Unable find or access a CD-ROM drive with an audio CD in it."); + return nullptr; + } + + /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ + const auto cdio = cdio_open(device.c_str(), DRIVER_UNKNOWN); + if (cdio == nullptr) { + error.Set(cdio_domain, "Failed to open CD drive"); + return nullptr; + } + + const auto drv = cdio_cddap_identify_cdio(cdio, 1, nullptr); + if (drv == nullptr) { + error.Set(cdio_domain, "Unable to identify audio CD disc."); + cdio_destroy(cdio); + return nullptr; + } + + cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + if (0 != cdio_cddap_open(drv)) { + cdio_cddap_close_no_free_cdio(drv); + cdio_destroy(cdio); + error.Set(cdio_domain, "Unable to open disc."); + return nullptr; + } + + bool reverse_endian; + switch (data_bigendianp(drv)) { + case -1: + LogDebug(cdio_domain, "drive returns unknown audio data"); + reverse_endian = default_reverse_endian; + break; + + case 0: + LogDebug(cdio_domain, "drive returns audio data Little Endian"); + reverse_endian = IsBigEndian(); + break; + + case 1: + LogDebug(cdio_domain, "drive returns audio data Big Endian"); + reverse_endian = IsLittleEndian(); + break; + + default: + error.Format(cdio_domain, "Drive returns unknown data type %d", + data_bigendianp(drv)); + cdio_cddap_close_no_free_cdio(drv); + cdio_destroy(cdio); + return nullptr; + } + + lsn_t lsn_from, lsn_to; + if (parsed_uri.track >= 0) { + lsn_from = cdio_get_track_lsn(cdio, parsed_uri.track); + lsn_to = cdio_get_track_last_lsn(cdio, parsed_uri.track); + } else { + lsn_from = 0; + lsn_to = cdio_get_disc_last_lsn(cdio); + } + + return new CdioParanoiaInputStream(uri, mutex, cond, + drv, cdio, reverse_endian, + lsn_from, lsn_to); +} + +bool +CdioParanoiaInputStream::Seek(offset_type new_offset, Error &error) +{ + if (new_offset < 0 || new_offset > size) { + error.Format(cdio_domain, "Invalid offset to seek %ld (%ld)", + (long int)new_offset, (long int)size); + return false; + } + + /* simple case */ + if (new_offset == offset) + return true; + + /* calculate current LSN */ + lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW; + offset = new_offset; + + cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET); + + return true; +} + +size_t +CdioParanoiaInputStream::Read(void *ptr, size_t length, Error &error) +{ + size_t nbytes = 0; + int diff; + size_t len, maxwrite; + int16_t *rbuf; + char *s_err, *s_mess; + char *wptr = (char *) ptr; + + while (length > 0) { + + + /* end of track ? */ + if (lsn_from + lsn_relofs > lsn_to) + break; + + //current sector was changed ? + if (lsn_relofs != buffer_lsn) { + rbuf = cdio_paranoia_read(para, nullptr); + + s_err = cdda_errors(drv); + if (s_err) { + FormatError(cdio_domain, + "paranoia_read: %s", s_err); + free(s_err); + } + s_mess = cdda_messages(drv); + if (s_mess) { + free(s_mess); + } + if (!rbuf) { + error.Set(cdio_domain, + "paranoia read error. Stopping."); + return 0; + } + //store current buffer + memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + buffer_lsn = lsn_relofs; + } else { + //use cached sector + rbuf = (int16_t *)buffer; + } + + //correct offset + diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + + assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + + maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + len = (length < maxwrite? length : maxwrite); + + //skip diff bytes from this lsn + memcpy(wptr, ((char*)rbuf) + diff, len); + //update pointer + wptr += len; + nbytes += len; + + //update offset + offset += len; + lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + //update length + length -= len; + } + + return nbytes; +} + +bool +CdioParanoiaInputStream::IsEOF() +{ + return lsn_from + lsn_relofs > lsn_to; +} + +const InputPlugin input_plugin_cdio_paranoia = { + "cdio_paranoia", + input_cdio_init, + nullptr, + input_cdio_open, +}; diff --git a/src/input/plugins/CdioParanoiaInputPlugin.hxx b/src/input/plugins/CdioParanoiaInputPlugin.hxx new file mode 100644 index 000000000..e2804e8c7 --- /dev/null +++ b/src/input/plugins/CdioParanoiaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libcdio_paranoia library. + */ +extern const struct InputPlugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx new file mode 100644 index 000000000..a174fcca6 --- /dev/null +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -0,0 +1,845 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CurlInputPlugin.hxx" +#include "../AsyncInputStream.hxx" +#include "../IcyInputStream.hxx" +#include "../InputPlugin.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "event/SocketMonitor.hxx" +#include "event/TimeoutMonitor.hxx" +#include "event/Call.hxx" +#include "IOThread.hxx" +#include "util/ASCII.hxx" +#include "util/StringUtil.hxx" +#include "util/NumberParser.hxx" +#include "util/CircularBuffer.hxx" +#include "util/HugeAllocator.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +#include <curl/curl.h> + +#if LIBCURL_VERSION_NUM < 0x071200 +#error libcurl is too old +#endif + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t CURL_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t CURL_RESUME_AT = 384 * 1024; + +struct CurlInputStream final : public AsyncInputStream { + /* some buffers which were passed to libcurl, which we have + too free */ + char range[32]; + struct curl_slist *request_headers; + + /** the curl handles */ + CURL *easy; + + /** error message provided by libcurl */ + char error_buffer[CURL_ERROR_SIZE]; + + /** parser for icy-metadata */ + IcyInputStream *icy; + + CurlInputStream(const char *_url, Mutex &_mutex, Cond &_cond, + void *_buffer) + :AsyncInputStream(_url, _mutex, _cond, + _buffer, CURL_MAX_BUFFERED, + CURL_RESUME_AT), + request_headers(nullptr), + icy(new IcyInputStream(this)) {} + + ~CurlInputStream(); + + CurlInputStream(const CurlInputStream &) = delete; + CurlInputStream &operator=(const CurlInputStream &) = delete; + + static InputStream *Open(const char *url, Mutex &mutex, Cond &cond, + Error &error); + + bool InitEasy(Error &error); + + /** + * Frees the current "libcurl easy" handle, and everything + * associated with it. + * + * Runs in the I/O thread. + */ + void FreeEasy(); + + /** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The mutex must not be locked. + */ + void FreeEasyIndirect(); + + void HeaderReceived(const char *name, std::string &&value); + + size_t DataReceived(const void *ptr, size_t size); + + /** + * A HTTP request is finished. + * + * Runs in the I/O thread. The caller must not hold locks. + */ + void RequestDone(CURLcode result, long status); + + /* virtual methods from AsyncInputStream */ + virtual void DoResume() override; + virtual void DoSeek(offset_type new_offset) override; +}; + +class CurlMulti; + +/** + * Monitor for one socket created by CURL. + */ +class CurlSocket final : SocketMonitor { + CurlMulti &multi; + +public: + CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd) + :SocketMonitor(_fd, _loop), multi(_multi) {} + + ~CurlSocket() { + /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after + closing the socket, and sometimes, it uses + CURL_POLL_REMOVE just to move the (still open) + connection to the pool; in the first case, + Abandon() would be most appropriate, but it breaks + the second case - is that a CURL bug? is there a + better solution? */ + } + + /** + * Callback function for CURLMOPT_SOCKETFUNCTION. + */ + static int SocketFunction(CURL *easy, + curl_socket_t s, int action, + void *userp, void *socketp); + + virtual bool OnSocketReady(unsigned flags) override; + +private: + static constexpr int FlagsToCurlCSelect(unsigned flags) { + return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) | + (flags & WRITE ? CURL_CSELECT_OUT : 0) | + (flags & ERROR ? CURL_CSELECT_ERR : 0); + } + + gcc_const + static unsigned CurlPollToFlags(int action) { + switch (action) { + case CURL_POLL_NONE: + return 0; + + case CURL_POLL_IN: + return READ; + + case CURL_POLL_OUT: + return WRITE; + + case CURL_POLL_INOUT: + return READ|WRITE; + } + + assert(false); + gcc_unreachable(); + } +}; + +/** + * Manager for the global CURLM object. + */ +class CurlMulti final : private TimeoutMonitor { + CURLM *const multi; + +public: + CurlMulti(EventLoop &_loop, CURLM *_multi); + + ~CurlMulti() { + curl_multi_cleanup(multi); + } + + bool Add(CurlInputStream *c, Error &error); + void Remove(CurlInputStream *c); + + /** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ + void ReadInfo(); + + void Assign(curl_socket_t fd, CurlSocket &cs) { + curl_multi_assign(multi, fd, &cs); + } + + void SocketAction(curl_socket_t fd, int ev_bitmask); + + void InvalidateSockets() { + SocketAction(CURL_SOCKET_TIMEOUT, 0); + } + + /** + * This is a kludge to allow pausing/resuming a stream with + * libcurl < 7.32.0. Read the curl_easy_pause manpage for + * more information. + */ + void ResumeSockets() { + int running_handles; + curl_multi_socket_all(multi, &running_handles); + } + +private: + static int TimerFunction(CURLM *multi, long timeout_ms, void *userp); + + virtual void OnTimeout() override; +}; + +/** + * libcurl version number encoded in a 24 bit integer. + */ +static unsigned curl_version_num; + +/** libcurl should accept "ICY 200 OK" */ +static struct curl_slist *http_200_aliases; + +/** HTTP proxy settings */ +static const char *proxy, *proxy_user, *proxy_password; +static unsigned proxy_port; + +static bool verify_peer, verify_host; + +static CurlMulti *curl_multi; + +static constexpr Domain http_domain("http"); +static constexpr Domain curl_domain("curl"); +static constexpr Domain curlm_domain("curlm"); + +CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi) + :TimeoutMonitor(_loop), multi(_multi) +{ + curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, + CurlSocket::SocketFunction); + curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this); + + curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction); + curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this); +} + +/** + * Find a request by its CURL "easy" handle. + * + * Runs in the I/O thread. No lock needed. + */ +gcc_pure +static CurlInputStream * +input_curl_find_request(CURL *easy) +{ + assert(io_thread_inside()); + + void *p; + CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p); + if (code != CURLE_OK) + return nullptr; + + return (CurlInputStream *)p; +} + +void +CurlInputStream::DoResume() +{ + assert(io_thread_inside()); + + mutex.unlock(); + + curl_easy_pause(easy, CURLPAUSE_CONT); + + if (curl_version_num < 0x072000) + /* libcurl older than 7.32.0 does not update + its sockets after curl_easy_pause(); force + libcurl to do it now */ + curl_multi->ResumeSockets(); + + curl_multi->InvalidateSockets(); + + mutex.lock(); +} + +int +CurlSocket::SocketFunction(gcc_unused CURL *easy, + curl_socket_t s, int action, + void *userp, void *socketp) { + CurlMulti &multi = *(CurlMulti *)userp; + CurlSocket *cs = (CurlSocket *)socketp; + + assert(io_thread_inside()); + + if (action == CURL_POLL_REMOVE) { + delete cs; + return 0; + } + + if (cs == nullptr) { + cs = new CurlSocket(multi, io_thread_get(), s); + multi.Assign(s, *cs); + } else { +#ifdef USE_EPOLL + /* when using epoll, we need to unregister the socket + each time this callback is invoked, because older + CURL versions may omit the CURL_POLL_REMOVE call + when the socket has been closed and recreated with + the same file number (bug found in CURL 7.26, CURL + 7.33 not affected); in that case, epoll refuses the + EPOLL_CTL_MOD because it does not know the new + socket yet */ + cs->Cancel(); +#endif + } + + unsigned flags = CurlPollToFlags(action); + if (flags != 0) + cs->Schedule(flags); + return 0; +} + +bool +CurlSocket::OnSocketReady(unsigned flags) +{ + assert(io_thread_inside()); + + multi.SocketAction(Get(), FlagsToCurlCSelect(flags)); + return true; +} + +/** + * Runs in the I/O thread. No lock needed. + */ +inline bool +CurlMulti::Add(CurlInputStream *c, Error &error) +{ + assert(io_thread_inside()); + assert(c != nullptr); + assert(c->easy != nullptr); + + CURLMcode mcode = curl_multi_add_handle(multi, c->easy); + if (mcode != CURLM_OK) { + error.Format(curlm_domain, mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + return false; + } + + InvalidateSockets(); + return true; +} + +/** + * Call input_curl_easy_add() in the I/O thread. May be called from + * any thread. Caller must not hold a mutex. + */ +static bool +input_curl_easy_add_indirect(CurlInputStream *c, Error &error) +{ + assert(c != nullptr); + assert(c->easy != nullptr); + + bool result; + BlockingCall(io_thread_get(), [c, &error, &result](){ + result = curl_multi->Add(c, error); + }); + return result; +} + +inline void +CurlMulti::Remove(CurlInputStream *c) +{ + curl_multi_remove_handle(multi, c->easy); +} + +void +CurlInputStream::FreeEasy() +{ + assert(io_thread_inside()); + + if (easy == nullptr) + return; + + curl_multi->Remove(this); + + curl_easy_cleanup(easy); + easy = nullptr; + + curl_slist_free_all(request_headers); + request_headers = nullptr; +} + +void +CurlInputStream::FreeEasyIndirect() +{ + BlockingCall(io_thread_get(), [this](){ + FreeEasy(); + curl_multi->InvalidateSockets(); + }); + + assert(easy == nullptr); +} + +inline void +CurlInputStream::RequestDone(CURLcode result, long status) +{ + assert(io_thread_inside()); + assert(!postponed_error.IsDefined()); + + FreeEasy(); + AsyncInputStream::SetClosed(); + + const ScopeLock protect(mutex); + + if (result != CURLE_OK) { + postponed_error.Format(curl_domain, result, + "curl failed: %s", error_buffer); + } else if (status < 200 || status >= 300) { + postponed_error.Format(http_domain, status, + "got HTTP status %ld", + status); + } + + if (IsSeekPending()) + SeekDone(); + else if (!IsReady()) + SetReady(); +} + +static void +input_curl_handle_done(CURL *easy_handle, CURLcode result) +{ + CurlInputStream *c = input_curl_find_request(easy_handle); + assert(c != nullptr); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + c->RequestDone(result, status); +} + +void +CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask) +{ + int running_handles; + CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask, + &running_handles); + if (mcode != CURLM_OK) + FormatError(curlm_domain, + "curl_multi_socket_action() failed: %s", + curl_multi_strerror(mcode)); + + ReadInfo(); +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +inline void +CurlMulti::ReadInfo() +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(multi, + &msgs_in_queue)) != nullptr) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +int +CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp) +{ + CurlMulti &multi = *(CurlMulti *)userp; + assert(_multi == multi.multi); + + if (timeout_ms < 0) { + multi.Cancel(); + return 0; + } + + if (timeout_ms >= 0 && timeout_ms < 10) + /* CURL 7.21.1 likes to report "timeout=0", which + means we're running in a busy loop. Quite a bad + idea to waste so much CPU. Let's use a lower limit + of 10ms. */ + timeout_ms = 10; + + multi.Schedule(timeout_ms); + return 0; +} + +void +CurlMulti::OnTimeout() +{ + SocketAction(CURL_SOCKET_TIMEOUT, 0); +} + +/* + * InputPlugin methods + * + */ + +static InputPlugin::InitResult +input_curl_init(const config_param ¶m, Error &error) +{ + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + if (code != CURLE_OK) { + error.Format(curl_domain, code, + "curl_global_init() failed: %s", + curl_easy_strerror(code)); + return InputPlugin::InitResult::UNAVAILABLE; + } + + const auto version_info = curl_version_info(CURLVERSION_FIRST); + if (version_info != nullptr) { + FormatDebug(curl_domain, "version %s", version_info->version); + if (version_info->features & CURL_VERSION_SSL) + FormatDebug(curl_domain, "with %s", + version_info->ssl_version); + + curl_version_num = version_info->version_num; + } + + http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); + + proxy = param.GetBlockValue("proxy"); + proxy_port = param.GetBlockValue("proxy_port", 0u); + proxy_user = param.GetBlockValue("proxy_user"); + proxy_password = param.GetBlockValue("proxy_password"); + + if (proxy == nullptr) { + /* deprecated proxy configuration */ + proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr); + proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); + proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr); + proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, + ""); + } + + verify_peer = param.GetBlockValue("verify_peer", true); + verify_host = param.GetBlockValue("verify_host", true); + + CURLM *multi = curl_multi_init(); + if (multi == nullptr) { + curl_slist_free_all(http_200_aliases); + curl_global_cleanup(); + error.Set(curl_domain, 0, "curl_multi_init() failed"); + return InputPlugin::InitResult::UNAVAILABLE; + } + + curl_multi = new CurlMulti(io_thread_get(), multi); + return InputPlugin::InitResult::SUCCESS; +} + +static void +input_curl_finish(void) +{ + BlockingCall(io_thread_get(), [](){ + delete curl_multi; + }); + + curl_slist_free_all(http_200_aliases); + + curl_global_cleanup(); +} + +CurlInputStream::~CurlInputStream() +{ + FreeEasyIndirect(); +} + +inline void +CurlInputStream::HeaderReceived(const char *name, std::string &&value) +{ + if (IsSeekPending()) + /* don't update metadata while seeking */ + return; + + if (StringEqualsCaseASCII(name, "accept-ranges")) { + /* a stream with icy-metadata is not seekable */ + if (!icy->IsEnabled()) + seekable = true; + } else if (StringEqualsCaseASCII(name, "content-length")) { + size = offset + ParseUint64(value.c_str()); + } else if (StringEqualsCaseASCII(name, "content-type")) { + SetMimeType(std::move(value)); + } else if (StringEqualsCaseASCII(name, "icy-name") || + StringEqualsCaseASCII(name, "ice-name") || + StringEqualsCaseASCII(name, "x-audiocast-name")) { + TagBuilder tag_builder; + tag_builder.AddItem(TAG_NAME, value.c_str()); + + SetTag(tag_builder.CommitNew()); + } else if (StringEqualsCaseASCII(name, "icy-metaint")) { + if (icy->IsEnabled()) + return; + + size_t icy_metaint = ParseUint64(value.c_str()); + FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint); + + if (icy_metaint > 0) { + icy->Enable(icy_metaint); + + /* a stream with icy-metadata is not + seekable */ + seekable = false; + } + } +} + +/** called by curl when new data is available */ +static size_t +input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + CurlInputStream &c = *(CurlInputStream *)stream; + + size *= nmemb; + + const char *header = (const char *)ptr; + const char *end = header + size; + + char name[64]; + + const char *value = (const char *)memchr(header, ':', size); + if (value == nullptr || (size_t)(value - header) >= sizeof(name)) + return size; + + memcpy(name, header, value - header); + name[value - header] = 0; + + /* skip the colon */ + + ++value; + + /* strip the value */ + + value = StripLeft(value, end); + end = StripRight(value, end); + + c.HeaderReceived(name, std::string(value, end)); + return size; +} + +inline size_t +CurlInputStream::DataReceived(const void *ptr, size_t received_size) +{ + assert(received_size > 0); + + const ScopeLock protect(mutex); + + if (IsSeekPending()) + SeekDone(); + + if (received_size > GetBufferSpace()) { + AsyncInputStream::Pause(); + return CURL_WRITEFUNC_PAUSE; + } + + AppendToBuffer(ptr, received_size); + return received_size; +} + +/** called by curl when new data is available */ +static size_t +input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + CurlInputStream &c = *(CurlInputStream *)stream; + + size *= nmemb; + if (size == 0) + return 0; + + return c.DataReceived(ptr, size); +} + +bool +CurlInputStream::InitEasy(Error &error) +{ + easy = curl_easy_init(); + if (easy == nullptr) { + error.Set(curl_domain, "curl_easy_init() failed"); + return false; + } + + curl_easy_setopt(easy, CURLOPT_PRIVATE, (void *)this); + curl_easy_setopt(easy, CURLOPT_USERAGENT, + "Music Player Daemon " VERSION); + curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, + input_curl_headerfunction); + curl_easy_setopt(easy, CURLOPT_WRITEHEADER, this); + curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, + input_curl_writefunction); + curl_easy_setopt(easy, CURLOPT_WRITEDATA, this); + curl_easy_setopt(easy, CURLOPT_HTTP200ALIASES, http_200_aliases); + curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 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(easy, CURLOPT_PROXY, proxy); + + if (proxy_port > 0) + curl_easy_setopt(easy, CURLOPT_PROXYPORT, (long)proxy_port); + + if (proxy_user != nullptr && proxy_password != nullptr) { + char proxy_auth_str[1024]; + snprintf(proxy_auth_str, sizeof(proxy_auth_str), + "%s:%s", + proxy_user, proxy_password); + curl_easy_setopt(easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); + } + + curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l); + curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l); + + CURLcode code = curl_easy_setopt(easy, CURLOPT_URL, GetURI()); + if (code != CURLE_OK) { + error.Format(curl_domain, code, + "curl_easy_setopt() failed: %s", + curl_easy_strerror(code)); + return false; + } + + request_headers = nullptr; + request_headers = curl_slist_append(request_headers, + "Icy-Metadata: 1"); + curl_easy_setopt(easy, CURLOPT_HTTPHEADER, request_headers); + + return true; +} + +void +CurlInputStream::DoSeek(offset_type new_offset) +{ + assert(IsReady()); + + /* close the old connection and open a new one */ + + mutex.unlock(); + + FreeEasyIndirect(); + + offset = new_offset; + if (offset == size) { + /* seek to EOF: simulate empty result; avoid + triggering a "416 Requested Range Not Satisfiable" + response */ + mutex.lock(); + SeekDone(); + return; + } + + Error error; + if (!InitEasy(postponed_error)) { + mutex.lock(); + PostponeError(std::move(error)); + return; + } + + /* send the "Range" header */ + + if (offset > 0) { + sprintf(range, "%lld-", (long long)offset); + curl_easy_setopt(easy, CURLOPT_RANGE, range); + } + + if (!input_curl_easy_add_indirect(this, error)) { + mutex.lock(); + PostponeError(std::move(error)); + return; + } + + mutex.lock(); + offset = new_offset; +} + +inline InputStream * +CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond, + Error &error) +{ + void *buffer = HugeAllocate(CURL_MAX_BUFFERED); + if (buffer == nullptr) { + error.Set(curl_domain, "Out of memory"); + return nullptr; + } + + CurlInputStream *c = new CurlInputStream(url, mutex, cond, buffer); + + if (!c->InitEasy(error) || !input_curl_easy_add_indirect(c, error)) { + delete c; + return nullptr; + } + + return c->icy; +} + +static InputStream * +input_curl_open(const char *url, Mutex &mutex, Cond &cond, + Error &error) +{ + if (memcmp(url, "http://", 7) != 0 && + memcmp(url, "https://", 8) != 0) + return nullptr; + + return CurlInputStream::Open(url, mutex, cond, error); +} + +const struct InputPlugin input_plugin_curl = { + "curl", + input_curl_init, + input_curl_finish, + input_curl_open, +}; diff --git a/src/input/plugins/CurlInputPlugin.hxx b/src/input/plugins/CurlInputPlugin.hxx new file mode 100644 index 000000000..4acb18bfc --- /dev/null +++ b/src/input/plugins/CurlInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_CURL_HXX +#define MPD_INPUT_CURL_HXX + +extern const struct InputPlugin input_plugin_curl; + +#endif diff --git a/src/input/plugins/DespotifyInputPlugin.cxx b/src/input/plugins/DespotifyInputPlugin.cxx new file mode 100644 index 000000000..29d9186d0 --- /dev/null +++ b/src/input/plugins/DespotifyInputPlugin.cxx @@ -0,0 +1,227 @@ +/* + * 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 "DespotifyInputPlugin.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 <unistd.h> +#include <string.h> +#include <errno.h> + +#include <stdio.h> + +class DespotifyInputStream final : public InputStream { + struct despotify_session *session; + struct ds_track *track; + Tag tag; + struct ds_pcm_data pcm; + size_t len_available; + bool eof; + + DespotifyInputStream(const char *_uri, + Mutex &_mutex, Cond &_cond, + despotify_session *_session, + ds_track *_track) + :InputStream(_uri, _mutex, _cond), + session(_session), track(_track), + tag(mpd_despotify_tag_from_track(*track)), + len_available(0), eof(false) { + + memset(&pcm, 0, sizeof(pcm)); + + /* Despotify outputs pcm data */ + SetMimeType("audio/x-mpd-cdda-pcm"); + SetReady(); + } + +public: + ~DespotifyInputStream(); + + static InputStream *Open(const char *url, Mutex &mutex, Cond &cond, + Error &error); + + void Callback(int sig); + + /* virtual methods from InputStream */ + + bool IsEOF() override { + return eof; + } + + Tag *ReadTag() override { + if (tag.IsEmpty()) + return nullptr; + + Tag *result = new Tag(std::move(tag)); + tag.Clear(); + return result; + } + + size_t Read(void *ptr, size_t size, Error &error) override; + +private: + void FillBuffer(); +}; + +inline void +DespotifyInputStream::FillBuffer() +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(session, &pcm); + + if (rc == 0 && pcm.len) { + len_available = pcm.len; + break; + } + + if (eof == true) + break; + + if (rc < 0) { + LogDebug(despotify_domain, "despotify_get_pcm error"); + eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +inline void +DespotifyInputStream::Callback(int sig) +{ + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + LogWarning(despotify_domain, "Track play error"); + eof = true; + len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + eof = true; + LogDebug(despotify_domain, "End of playlist"); + break; + } +} + +static void callback(gcc_unused struct despotify_session* ds, + int sig, gcc_unused void* data, void* callback_data) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; + + ctx->Callback(sig); +} + +DespotifyInputStream::~DespotifyInputStream() +{ + mpd_despotify_unregister_callback(callback); + despotify_free_track(track); +} + +inline InputStream * +DespotifyInputStream::Open(const char *url, + Mutex &mutex, Cond &cond, + gcc_unused Error &error) +{ + if (!StringStartsWith(url, "spt://")) + return nullptr; + + despotify_session *session = mpd_despotify_get_session(); + if (session == nullptr) + return nullptr; + + ds_link *ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + FormatDebug(despotify_domain, "Can't find %s", url); + return nullptr; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return nullptr; + } + + ds_track *track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) + return nullptr; + + DespotifyInputStream *ctx = + new DespotifyInputStream(url, mutex, cond, + session, track); + + if (!mpd_despotify_register_callback(callback, ctx)) { + delete ctx; + return nullptr; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + mpd_despotify_unregister_callback(callback); + delete ctx; + return nullptr; + } + + return ctx; +} + +static InputStream * +input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error) +{ + return DespotifyInputStream::Open(url, mutex, cond, error); +} + +size_t +DespotifyInputStream::Read(void *ptr, size_t read_size, + gcc_unused Error &error) +{ + if (len_available == 0) + FillBuffer(); + + size_t to_cpy = std::min(read_size, len_available); + memcpy(ptr, pcm.buf, to_cpy); + len_available -= to_cpy; + + offset += to_cpy; + + return to_cpy; +} + +const InputPlugin input_plugin_despotify = { + "despotify", + nullptr, + nullptr, + input_despotify_open, +}; diff --git a/src/input/plugins/DespotifyInputPlugin.hxx b/src/input/plugins/DespotifyInputPlugin.hxx new file mode 100644 index 000000000..83f963520 --- /dev/null +++ b/src/input/plugins/DespotifyInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INPUT_DESPOTIFY_HXX +#define INPUT_DESPOTIFY_HXX + +extern const struct InputPlugin input_plugin_despotify; + +#endif diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx new file mode 100644 index 000000000..6e0260aee --- /dev/null +++ b/src/input/plugins/FfmpegInputPlugin.cxx @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegInputPlugin.hxx" +#include "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +extern "C" { +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +} + +struct FfmpegInputStream final : public InputStream { + AVIOContext *h; + + bool eof; + + FfmpegInputStream(const char *_uri, Mutex &_mutex, Cond &_cond, + AVIOContext *_h) + :InputStream(_uri, _mutex, _cond), + h(_h), eof(false) { + seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0; + size = avio_size(h); + + /* hack to make MPD select the "ffmpeg" decoder plugin + - since avio.h doesn't tell us the MIME type of the + resource, we can't select a decoder plugin, but the + "ffmpeg" plugin is quite good at auto-detection */ + SetMimeType("audio/x-mpd-ffmpeg"); + SetReady(); + } + + ~FfmpegInputStream() { + avio_close(h); + } + + /* virtual methods from InputStream */ + bool IsEOF() override; + size_t Read(void *ptr, size_t size, Error &error) override; + bool Seek(offset_type offset, Error &error) override; +}; + +static constexpr Domain ffmpeg_domain("ffmpeg"); + +static inline bool +input_ffmpeg_supported(void) +{ + void *opaque = nullptr; + return avio_enum_protocols(&opaque, 0) != nullptr; +} + +static InputPlugin::InitResult +input_ffmpeg_init(gcc_unused const config_param ¶m, + Error &error) +{ + av_register_all(); + + /* disable this plugin if there's no registered protocol */ + if (!input_ffmpeg_supported()) { + error.Set(ffmpeg_domain, "No protocol"); + return InputPlugin::InitResult::UNAVAILABLE; + } + + return InputPlugin::InitResult::SUCCESS; +} + +static InputStream * +input_ffmpeg_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!StringStartsWith(uri, "gopher://") && + !StringStartsWith(uri, "rtp://") && + !StringStartsWith(uri, "rtsp://") && + !StringStartsWith(uri, "rtmp://") && + !StringStartsWith(uri, "rtmpt://") && + !StringStartsWith(uri, "rtmps://")) + return nullptr; + + AVIOContext *h; + int ret = avio_open(&h, uri, AVIO_FLAG_READ); + if (ret != 0) { + error.Set(ffmpeg_domain, ret, + "libavformat failed to open the URI"); + return nullptr; + } + + return new FfmpegInputStream(uri, mutex, cond, h); +} + +size_t +FfmpegInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + int ret = avio_read(h, (unsigned char *)ptr, read_size); + if (ret <= 0) { + if (ret < 0) + error.Set(ffmpeg_domain, "avio_read() failed"); + + eof = true; + return false; + } + + offset += ret; + return (size_t)ret; +} + +bool +FfmpegInputStream::IsEOF() +{ + return eof; +} + +bool +FfmpegInputStream::Seek(offset_type new_offset, Error &error) +{ + int64_t ret = avio_seek(h, new_offset, SEEK_SET); + + if (ret >= 0) { + eof = false; + return true; + } else { + error.Set(ffmpeg_domain, "avio_seek() failed"); + return false; + } +} + +const InputPlugin input_plugin_ffmpeg = { + "ffmpeg", + input_ffmpeg_init, + nullptr, + input_ffmpeg_open, +}; diff --git a/src/input/plugins/FfmpegInputPlugin.hxx b/src/input/plugins/FfmpegInputPlugin.hxx new file mode 100644 index 000000000..43f829e89 --- /dev/null +++ b/src/input/plugins/FfmpegInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX +#define MPD_FFMPEG_INPUT_PLUGIN_HXX + +/** + * An input plugin based on libavformat's "avio" library. + */ +extern const struct InputPlugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/plugins/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx new file mode 100644 index 000000000..60c316567 --- /dev/null +++ b/src/input/plugins/FileInputPlugin.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "FileInputPlugin.hxx" +#include "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Traits.hxx" +#include "system/fd_util.h" +#include "open.h" + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +static constexpr Domain file_domain("file"); + +struct FileInputStream final : public InputStream { + int fd; + + FileInputStream(const char *path, int _fd, off_t _size, + Mutex &_mutex, Cond &_cond) + :InputStream(path, _mutex, _cond), + fd(_fd) { + size = _size; + seekable = true; + SetReady(); + } + + ~FileInputStream() { + close(fd); + } + + /* virtual methods from InputStream */ + + bool IsEOF() override { + return GetOffset() >= GetSize(); + } + + size_t Read(void *ptr, size_t size, Error &error) override; + bool Seek(offset_type offset, Error &error) override; +}; + +static InputStream * +input_file_open(const char *filename, + Mutex &mutex, Cond &cond, + Error &error) +{ + int fd, ret; + struct stat st; + + if (!PathTraitsFS::IsAbsolute(filename)) + return nullptr; + + fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); + if (fd < 0) { + if (errno != ENOENT && errno != ENOTDIR) + error.FormatErrno("Failed to open \"%s\"", + filename); + return nullptr; + } + + ret = fstat(fd, &st); + if (ret < 0) { + error.FormatErrno("Failed to stat \"%s\"", filename); + close(fd); + return nullptr; + } + + if (!S_ISREG(st.st_mode)) { + error.Format(file_domain, "Not a regular file: %s", filename); + close(fd); + return nullptr; + } + +#ifdef POSIX_FADV_SEQUENTIAL + posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); +#endif + + return new FileInputStream(filename, fd, st.st_size, mutex, cond); +} + +bool +FileInputStream::Seek(offset_type new_offset, Error &error) +{ + new_offset = (offset_type)lseek(fd, (off_t)new_offset, SEEK_SET); + if (new_offset < 0) { + error.SetErrno("Failed to seek"); + return false; + } + + offset = new_offset; + return true; +} + +size_t +FileInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + ssize_t nbytes = read(fd, ptr, read_size); + if (nbytes < 0) { + error.SetErrno("Failed to read"); + return 0; + } + + offset += nbytes; + return (size_t)nbytes; +} + +const InputPlugin input_plugin_file = { + "file", + nullptr, + nullptr, + input_file_open, +}; diff --git a/src/input/plugins/FileInputPlugin.hxx b/src/input/plugins/FileInputPlugin.hxx new file mode 100644 index 000000000..4aef94637 --- /dev/null +++ b/src/input/plugins/FileInputPlugin.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_FILE_HXX +#define MPD_INPUT_FILE_HXX + +extern const struct InputPlugin input_plugin_file; + +#endif diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx new file mode 100644 index 000000000..1aed9c662 --- /dev/null +++ b/src/input/plugins/MmsInputPlugin.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MmsInputPlugin.hxx" +#include "input/ThreadInputStream.hxx" +#include "input/InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <libmms/mmsx.h> + +static constexpr size_t MMS_BUFFER_SIZE = 256 * 1024; + +class MmsInputStream final : public ThreadInputStream { + mmsx_t *mms; + +public: + MmsInputStream(const char *_uri, Mutex &_mutex, Cond &_cond) + :ThreadInputStream(input_plugin_mms.name, _uri, _mutex, _cond, + MMS_BUFFER_SIZE) { + } + +protected: + virtual bool Open(gcc_unused Error &error) override; + virtual size_t ThreadRead(void *ptr, size_t size, + Error &error) override; + + 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::ThreadRead(void *ptr, size_t read_size, Error &error) +{ + int nbytes = mmsx_read(nullptr, mms, (char *)ptr, read_size); + if (nbytes <= 0) { + if (nbytes < 0) + error.SetErrno("mmsx_read() failed"); + return 0; + } + + return (size_t)nbytes; +} + +const InputPlugin input_plugin_mms = { + "mms", + nullptr, + nullptr, + input_mms_open, +}; diff --git a/src/input/plugins/MmsInputPlugin.hxx b/src/input/plugins/MmsInputPlugin.hxx new file mode 100644 index 000000000..b4017ffd6 --- /dev/null +++ b/src/input/plugins/MmsInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INPUT_MMS_H +#define INPUT_MMS_H + +extern const struct InputPlugin input_plugin_mms; + +#endif diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx new file mode 100644 index 000000000..baa707738 --- /dev/null +++ b/src/input/plugins/NfsInputPlugin.cxx @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "NfsInputPlugin.hxx" +#include "../AsyncInputStream.hxx" +#include "../InputPlugin.hxx" +#include "lib/nfs/Domain.hxx" +#include "lib/nfs/Glue.hxx" +#include "lib/nfs/FileReader.hxx" +#include "util/HugeAllocator.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +extern "C" { +#include <nfsc/libnfs.h> +} + +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t NFS_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t NFS_RESUME_AT = 384 * 1024; + +class NfsInputStream final : public AsyncInputStream, NfsFileReader { + uint64_t next_offset; + +public: + NfsInputStream(const char *_uri, + Mutex &_mutex, Cond &_cond, + void *_buffer) + :AsyncInputStream(_uri, _mutex, _cond, + _buffer, NFS_MAX_BUFFERED, + NFS_RESUME_AT) {} + + virtual ~NfsInputStream() { + DeferClose(); + } + + bool Open(Error &error) { + assert(!IsReady()); + + return NfsFileReader::Open(GetURI(), error); + } + +private: + bool DoRead(); + +protected: + /* virtual methods from AsyncInputStream */ + virtual void DoResume() override; + virtual void DoSeek(offset_type new_offset) override; + +private: + /* virtual methods from NfsFileReader */ + void OnNfsFileOpen(uint64_t size) override; + void OnNfsFileRead(const void *data, size_t size) override; + void OnNfsFileError(Error &&error) override; +}; + +bool +NfsInputStream::DoRead() +{ + assert(NfsFileReader::IsIdle()); + + int64_t remaining = size - next_offset; + if (remaining <= 0) + return true; + + const size_t buffer_space = GetBufferSpace(); + if (buffer_space == 0) { + Pause(); + return true; + } + + size_t nbytes = std::min<size_t>(std::min<uint64_t>(remaining, 32768), + buffer_space); + + mutex.unlock(); + Error error; + bool success = NfsFileReader::Read(next_offset, nbytes, error); + mutex.lock(); + + if (!success) { + PostponeError(std::move(error)); + return false; + } + + return true; +} + +void +NfsInputStream::DoResume() +{ + assert(NfsFileReader::IsIdle()); + + DoRead(); +} + +void +NfsInputStream::DoSeek(offset_type new_offset) +{ + mutex.unlock(); + NfsFileReader::CancelRead(); + mutex.lock(); + + next_offset = offset = new_offset; + SeekDone(); + DoRead(); +} + +void +NfsInputStream::OnNfsFileOpen(uint64_t _size) +{ + const ScopeLock protect(mutex); + + size = _size; + seekable = true; + next_offset = 0; + SetReady(); + DoRead(); +} + +void +NfsInputStream::OnNfsFileRead(const void *data, size_t data_size) +{ + const ScopeLock protect(mutex); + assert(!IsBufferFull()); + assert(IsBufferFull() == (GetBufferSpace() == 0)); + AppendToBuffer(data, data_size); + + next_offset += data_size; + + DoRead(); +} + +void +NfsInputStream::OnNfsFileError(Error &&error) +{ + const ScopeLock protect(mutex); + postponed_error = std::move(error); + + if (IsSeekPending()) + SeekDone(); + else if (!IsReady()) + SetReady(); +} + +/* + * InputPlugin methods + * + */ + +static InputPlugin::InitResult +input_nfs_init(const config_param &, Error &) +{ + nfs_init(); + return InputPlugin::InitResult::SUCCESS; +} + +static void +input_nfs_finish() +{ + nfs_finish(); +} + +static InputStream * +input_nfs_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!StringStartsWith(uri, "nfs://")) + return nullptr; + + void *buffer = HugeAllocate(NFS_MAX_BUFFERED); + if (buffer == nullptr) { + error.Set(nfs_domain, "Out of memory"); + return nullptr; + } + + NfsInputStream *is = new NfsInputStream(uri, mutex, cond, buffer); + if (!is->Open(error)) { + delete is; + return nullptr; + } + + return is; +} + +const InputPlugin input_plugin_nfs = { + "nfs", + input_nfs_init, + input_nfs_finish, + input_nfs_open, +}; diff --git a/src/input/plugins/NfsInputPlugin.hxx b/src/input/plugins/NfsInputPlugin.hxx new file mode 100644 index 000000000..d2cc87549 --- /dev/null +++ b/src/input/plugins/NfsInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_NFS_H +#define MPD_INPUT_NFS_H + +extern const struct InputPlugin input_plugin_nfs; + +#endif diff --git a/src/input/plugins/RewindInputPlugin.cxx b/src/input/plugins/RewindInputPlugin.cxx new file mode 100644 index 000000000..95f604044 --- /dev/null +++ b/src/input/plugins/RewindInputPlugin.cxx @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "RewindInputPlugin.hxx" +#include "../ProxyInputStream.hxx" + +#include <assert.h> +#include <string.h> + +class RewindInputStream final : public ProxyInputStream { + /** + * The read position within the buffer. Undefined as long as + * ReadingFromBuffer() returns false. + */ + size_t head; + + /** + * The write/append position within the buffer. + */ + size_t tail; + + /** + * The size of this buffer is the maximum number of bytes + * which can be rewinded cheaply without passing the "seek" + * call to CURL. + * + * The origin of this buffer is always the beginning of the + * stream (offset 0). + */ + char buffer[64 * 1024]; + +public: + RewindInputStream(InputStream *_input) + :ProxyInputStream(_input), + tail(0) { + } + + /* virtual methods from InputStream */ + + void Update() override { + if (!ReadingFromBuffer()) + ProxyInputStream::Update(); + } + + bool IsEOF() override { + return !ReadingFromBuffer() && ProxyInputStream::IsEOF(); + } + + size_t Read(void *ptr, size_t size, Error &error) override; + bool Seek(offset_type offset, Error &error) override; + +private: + /** + * Are we currently reading from the buffer, and does the + * buffer contain more data for the next read operation? + */ + bool ReadingFromBuffer() const { + return tail > 0 && offset < input.GetOffset(); + } +}; + +size_t +RewindInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + if (ReadingFromBuffer()) { + /* buffered read */ + + assert(head == (size_t)offset); + assert(tail == (size_t)input.GetOffset()); + + if (read_size > tail - head) + read_size = tail - head; + + memcpy(ptr, buffer + head, read_size); + head += read_size; + offset += read_size; + + return read_size; + } else { + /* pass method call to underlying stream */ + + size_t nbytes = input.Read(ptr, read_size, error); + + if (input.GetOffset() > (offset_type)sizeof(buffer)) + /* disable buffering */ + tail = 0; + else if (tail == (size_t)offset) { + /* append to buffer */ + + memcpy(buffer + tail, ptr, nbytes); + tail += nbytes; + + assert(tail == (size_t)input.GetOffset()); + } + + CopyAttributes(); + + return nbytes; + } +} + +bool +RewindInputStream::Seek(offset_type new_offset, + Error &error) +{ + assert(IsReady()); + + if (tail > 0 && new_offset <= (offset_type)tail) { + /* buffered seek */ + + assert(!ReadingFromBuffer() || + head == (size_t)offset); + assert(tail == (size_t)input.GetOffset()); + + head = (size_t)new_offset; + offset = new_offset; + + return true; + } else { + /* disable the buffer, because input has left the + buffered range now */ + tail = 0; + + return ProxyInputStream::Seek(new_offset, error); + } +} + +InputStream * +input_rewind_open(InputStream *is) +{ + assert(is != nullptr); + assert(!is->IsReady() || is->GetOffset() == 0); + + if (is->IsReady() && is->IsSeekable()) + /* seekable resources don't need this plugin */ + return is; + + return new RewindInputStream(is); +} diff --git a/src/input/plugins/RewindInputPlugin.hxx b/src/input/plugins/RewindInputPlugin.hxx new file mode 100644 index 000000000..56b01b585 --- /dev/null +++ b/src/input/plugins/RewindInputPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * A wrapper for an input_stream object which allows cheap buffered + * rewinding. This is useful while detecting the stream codec (let + * each decoder plugin peek a portion from the stream). + */ + +#ifndef MPD_INPUT_REWIND_HXX +#define MPD_INPUT_REWIND_HXX + +#include "check.h" + +class InputStream; + +InputStream * +input_rewind_open(InputStream *is); + +#endif diff --git a/src/input/plugins/SmbclientInputPlugin.cxx b/src/input/plugins/SmbclientInputPlugin.cxx new file mode 100644 index 000000000..79987180f --- /dev/null +++ b/src/input/plugins/SmbclientInputPlugin.cxx @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SmbclientInputPlugin.hxx" +#include "lib/smbclient/Init.hxx" +#include "lib/smbclient/Mutex.hxx" +#include "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +#include <libsmbclient.h> + +class SmbclientInputStream final : public InputStream { + SMBCCTX *ctx; + int fd; + +public: + SmbclientInputStream(const char *_uri, + Mutex &_mutex, Cond &_cond, + SMBCCTX *_ctx, int _fd, const struct stat &st) + :InputStream(_uri, _mutex, _cond), + ctx(_ctx), fd(_fd) { + seekable = true; + size = st.st_size; + SetReady(); + } + + ~SmbclientInputStream() { + smbclient_mutex.lock(); + smbc_close(fd); + smbc_free_context(ctx, 1); + smbclient_mutex.unlock(); + } + + /* virtual methods from InputStream */ + + bool IsEOF() override { + return offset >= size; + } + + size_t Read(void *ptr, size_t size, Error &error) override; + bool Seek(offset_type offset, Error &error) override; +}; + +/* + * InputPlugin methods + * + */ + +static InputPlugin::InitResult +input_smbclient_init(gcc_unused const config_param ¶m, Error &error) +{ + if (!SmbclientInit(error)) + return InputPlugin::InitResult::UNAVAILABLE; + + // TODO: create one global SMBCCTX here? + + // TODO: evaluate config_param, call smbc_setOption*() + + return InputPlugin::InitResult::SUCCESS; +} + +static InputStream * +input_smbclient_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!StringStartsWith(uri, "smb://")) + return nullptr; + + const ScopeLock protect(smbclient_mutex); + + SMBCCTX *ctx = smbc_new_context(); + if (ctx == nullptr) { + error.SetErrno("smbc_new_context() failed"); + return nullptr; + } + + SMBCCTX *ctx2 = smbc_init_context(ctx); + if (ctx2 == nullptr) { + error.SetErrno("smbc_init_context() failed"); + smbc_free_context(ctx, 1); + return nullptr; + } + + ctx = ctx2; + + int fd = smbc_open(uri, O_RDONLY, 0); + if (fd < 0) { + error.SetErrno("smbc_open() failed"); + smbc_free_context(ctx, 1); + return nullptr; + } + + struct stat st; + if (smbc_fstat(fd, &st) < 0) { + error.SetErrno("smbc_fstat() failed"); + smbc_close(fd); + smbc_free_context(ctx, 1); + return nullptr; + } + + return new SmbclientInputStream(uri, mutex, cond, ctx, fd, st); +} + +size_t +SmbclientInputStream::Read(void *ptr, size_t read_size, Error &error) +{ + smbclient_mutex.lock(); + ssize_t nbytes = smbc_read(fd, ptr, read_size); + smbclient_mutex.unlock(); + if (nbytes < 0) { + error.SetErrno("smbc_read() failed"); + nbytes = 0; + } + + return nbytes; +} + +bool +SmbclientInputStream::Seek(offset_type new_offset, Error &error) +{ + smbclient_mutex.lock(); + off_t result = smbc_lseek(fd, new_offset, SEEK_SET); + smbclient_mutex.unlock(); + if (result < 0) { + error.SetErrno("smbc_lseek() failed"); + return false; + } + + offset = result; + return true; +} + +const InputPlugin input_plugin_smbclient = { + "smbclient", + input_smbclient_init, + nullptr, + input_smbclient_open, +}; diff --git a/src/input/plugins/SmbclientInputPlugin.hxx b/src/input/plugins/SmbclientInputPlugin.hxx new file mode 100644 index 000000000..a0539d020 --- /dev/null +++ b/src/input/plugins/SmbclientInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_SMBCLIENT_H +#define MPD_INPUT_SMBCLIENT_H + +extern const struct InputPlugin input_plugin_smbclient; + +#endif diff --git a/src/java/Class.hxx b/src/java/Class.hxx new file mode 100644 index 000000000..9496ee67d --- /dev/null +++ b/src/java/Class.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_CLASS_HXX +#define JAVA_CLASS_HXX + +#include "Ref.hxx" + +#include <assert.h> + +namespace Java { + /** + * Wrapper for a local "jclass" reference. + */ + class Class : public Java::LocalRef<jclass> { + public: + Class(JNIEnv *env, jclass cls) + :LocalRef<jclass>(env, cls) {} + + Class(JNIEnv *env, const char *name) + :LocalRef<jclass>(env, env->FindClass(name)) {} + }; + + /** + * Wrapper for a global "jclass" reference. + */ + class TrivialClass : public TrivialRef<jclass> { + public: + void Find(JNIEnv *env, const char *name) { + assert(env != nullptr); + assert(name != nullptr); + + jclass cls = env->FindClass(name); + assert(cls != nullptr); + + Set(env, cls); + env->DeleteLocalRef(cls); + } + + bool FindOptional(JNIEnv *env, const char *name) { + assert(env != nullptr); + assert(name != nullptr); + + jclass cls = env->FindClass(name); + if (cls == nullptr) { + env->ExceptionClear(); + return false; + } + + Set(env, cls); + env->DeleteLocalRef(cls); + return true; + } + }; +} + +#endif diff --git a/src/java/Exception.hxx b/src/java/Exception.hxx new file mode 100644 index 000000000..adbdebb47 --- /dev/null +++ b/src/java/Exception.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_EXCEPTION_HXX +#define JAVA_EXCEPTION_HXX + +#include <jni.h> + +namespace Java { + /** + * Check if an exception has occurred, and discard it. + * + * @return true if an exception was found (and discarded) + */ + static inline bool DiscardException(JNIEnv *env) { + bool result = env->ExceptionCheck(); + if (result) + env->ExceptionClear(); + return result; + } +} + +#endif diff --git a/src/java/File.cxx b/src/java/File.cxx new file mode 100644 index 000000000..8e9599dfc --- /dev/null +++ b/src/java/File.cxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "File.hxx" +#include "Class.hxx" +#include "String.hxx" +#include "Object.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Limits.hxx" + +jmethodID Java::File::getAbsolutePath_method; + +void +Java::File::Initialise(JNIEnv *env) +{ + Class cls(env, "java/io/File"); + + getAbsolutePath_method = env->GetMethodID(cls, "getAbsolutePath", + "()Ljava/lang/String;"); +} + +AllocatedPath +Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) +{ + assert(env != nullptr); + assert(_file != nullptr); + + LocalObject file(env, _file); + + const jstring path = getAbsolutePath(env, file); + if (path == nullptr) { + env->ExceptionClear(); + return AllocatedPath::Null(); + } + + Java::String path2(env, path); + char buffer[MPD_PATH_MAX]; + path2.CopyTo(env, buffer, sizeof(buffer)); + return AllocatedPath::FromUTF8(buffer); +} diff --git a/src/java/File.hxx b/src/java/File.hxx new file mode 100644 index 000000000..3636fc7c6 --- /dev/null +++ b/src/java/File.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_FILE_HXX +#define JAVA_FILE_HPP + +#include "Object.hxx" + +#include <jni.h> + +class AllocatedPath; + +namespace Java { + /** + * Wrapper for a java.io.File object. + */ + class File : public LocalObject { + static jmethodID getAbsolutePath_method; + + public: + gcc_nonnull_all + static void Initialise(JNIEnv *env); + + gcc_nonnull_all + static jstring getAbsolutePath(JNIEnv *env, jobject file) { + return (jstring)env->CallObjectMethod(file, + getAbsolutePath_method); + } + + /** + * Invoke File.getAbsolutePath() and release the + * specified File reference. + */ + gcc_pure gcc_nonnull_all + static AllocatedPath ToAbsolutePath(JNIEnv *env, + jobject file); + }; +} + +#endif diff --git a/src/java/Global.cxx b/src/java/Global.cxx new file mode 100644 index 000000000..1d1160127 --- /dev/null +++ b/src/java/Global.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Global.hxx" + +namespace Java { + JavaVM *jvm; + + void Init(JNIEnv *env) + { + env->GetJavaVM(&jvm); + } +} diff --git a/src/java/Global.hxx b/src/java/Global.hxx new file mode 100644 index 000000000..5fdf91daf --- /dev/null +++ b/src/java/Global.hxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_GLOBAL_HXX +#define JAVA_GLOBAL_HXX + +#include "Compiler.h" + +#include <jni.h> + +namespace Java { + extern JavaVM *jvm; + + void Init(JNIEnv *env); + + static inline void + DetachCurrentThread() + { + if (jvm != nullptr) + jvm->DetachCurrentThread(); + } + + static inline gcc_pure + JNIEnv *GetEnv() + { + JNIEnv *env; + jvm->AttachCurrentThread(&env, nullptr); + return env; + } +} + +#endif diff --git a/src/java/Object.hxx b/src/java/Object.hxx new file mode 100644 index 000000000..226ad7623 --- /dev/null +++ b/src/java/Object.hxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_OBJECT_HXX +#define JAVA_OBJECT_HXX + +#include "Ref.hxx" + +#include <jni.h> + +namespace Java { + /** + * Wrapper for a local "jobject" reference. + */ + typedef LocalRef<jobject> LocalObject; + + class Object : public GlobalRef<jobject> { + public: + /** + * Constructs an uninitialized object. The method + * set() must be called before it is destructed. + */ + Object() = default; + + Object(JNIEnv *env, jobject obj):GlobalRef<jobject>(env, obj) {} + }; +} + +#endif diff --git a/src/java/Ref.hxx b/src/java/Ref.hxx new file mode 100644 index 000000000..e9966f1c6 --- /dev/null +++ b/src/java/Ref.hxx @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_REF_HXX +#define JAVA_REF_HXX + +#include "Global.hxx" + +#include <jni.h> + +#include <assert.h> + +namespace Java { + /** + * Hold a local reference on a JNI object. + */ + template<typename T> + class LocalRef { + JNIEnv *const env; + const T value; + + public: + /** + * The local reference is obtained by the caller. + */ + LocalRef(JNIEnv *_env, T _value):env(_env), value(_value) { + assert(env != nullptr); + assert(value != nullptr); + } + + ~LocalRef() { + env->DeleteLocalRef(value); + } + + LocalRef(const LocalRef &other) = delete; + LocalRef &operator=(const LocalRef &other) = delete; + + T Get() const { + return value; + } + + operator T() const { + return value; + } + }; + + /** + * Hold a global reference on a JNI object. + */ + template<typename T> + class GlobalRef { + T value; + + public: + /** + * Constructs an uninitialized object. The method set() must be + * called before it is destructed. + */ + GlobalRef() = default; + + GlobalRef(JNIEnv *env, T _value):value(_value) { + assert(env != nullptr); + assert(value != nullptr); + + value = (T)env->NewGlobalRef(value); + } + + ~GlobalRef() { + GetEnv()->DeleteGlobalRef(value); + } + + GlobalRef(const GlobalRef &other) = delete; + GlobalRef &operator=(const GlobalRef &other) = delete; + + /** + * Sets the object, ignoring the previous value. This is only + * allowed once after the default constructor was used. + */ + void Set(JNIEnv *env, T _value) { + assert(_value != nullptr); + + value = (T)env->NewGlobalRef(_value); + } + + T Get() const { + return value; + } + + operator T() const { + return value; + } + }; + + /** + * Container for a global reference to a JNI object that gets + * initialised and deinitialised explicitly. Since there is no + * implicit initialisation in the default constructor, this is a + * trivial C++ class. It should only be used for global variables + * that are implicitly initialised with zeroes. + */ + template<typename T> + class TrivialRef { + T value; + + public: + constexpr TrivialRef() {}; + + TrivialRef(const TrivialRef &other) = delete; + TrivialRef &operator=(const TrivialRef &other) = delete; + + bool IsDefined() const { + return value != nullptr; + } + + /** + * Obtain a global reference on the specified object and store it. + * This object must not be set already. + */ + void Set(JNIEnv *env, T _value) { + assert(value == nullptr); + assert(_value != nullptr); + + value = (T)env->NewGlobalRef(_value); + } + + /** + * Release the global reference and clear this object. + */ + void Clear(JNIEnv *env) { + assert(value != nullptr); + + env->DeleteGlobalRef(value); + value = nullptr; + } + + /** + * Release the global reference and clear this object. It is + * allowed to call this method without ever calling Set(). + */ + void ClearOptional(JNIEnv *env) { + if (value != nullptr) + Clear(env); + } + + T Get() const { + return value; + } + + operator T() const { + return value; + } + }; +} + +#endif diff --git a/src/java/String.cxx b/src/java/String.cxx new file mode 100644 index 000000000..8ffb82d72 --- /dev/null +++ b/src/java/String.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "String.hxx" +#include "util/StringUtil.hxx" + +char * +Java::String::CopyTo(JNIEnv *env, jstring value, + char *buffer, size_t max_size) +{ + const char *p = env->GetStringUTFChars(value, nullptr); + if (p == nullptr) + return nullptr; + + char *result = CopyString(buffer, p, max_size); + env->ReleaseStringUTFChars(value, p); + return result; +} diff --git a/src/java/String.hxx b/src/java/String.hxx new file mode 100644 index 000000000..a58385f50 --- /dev/null +++ b/src/java/String.hxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef JAVA_STRING_HXX +#define JAVA_STRING_HXX + +#include "Ref.hxx" + +#include <jni.h> + +#include <assert.h> +#include <stddef.h> + +namespace Java { + /** + * Wrapper for a local "jstring" reference. + */ + class String : public LocalRef<jstring> { + public: + String(JNIEnv *env, jstring value) + :LocalRef<jstring>(env, value) {} + + String(JNIEnv *_env, const char *_value) + :LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {} + + /** + * Copy the value to the specified buffer. Truncates + * the value if it does not fit into the buffer. + * + * @return a pointer to the terminating null byte, + * nullptr on error + */ + static char *CopyTo(JNIEnv *env, jstring value, + char *buffer, size_t max_size); + + /** + * Copy the value to the specified buffer. Truncates + * the value if it does not fit into the buffer. + * + * @return a pointer to the terminating null byte, + * nullptr on error + */ + char *CopyTo(JNIEnv *env, char *buffer, size_t max_size) { + return CopyTo(env, Get(), buffer, max_size); + } + }; +} + +#endif diff --git a/src/lib/despotify/DespotifyUtils.cxx b/src/lib/despotify/DespotifyUtils.cxx new file mode 100644 index 000000000..47a83e49b --- /dev/null +++ b/src/lib/despotify/DespotifyUtils.cxx @@ -0,0 +1,153 @@ +/* + * 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 "DespotifyUtils.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <stdio.h> + +const Domain despotify_domain("despotify"); + +static struct despotify_session *g_session; +static void (*registered_callbacks[8])(struct despotify_session *, + int, void *, void *); +static void *registered_callback_data[8]; + +static void +callback(struct despotify_session* ds, int sig, + void *data, gcc_unused void *callback_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; + void *cb_data = registered_callback_data[i]; + + if (cb) + cb(ds, sig, data, cb_data); + } +} + +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (!registered_callbacks[i]) { + registered_callbacks[i] = cb; + registered_callback_data[i] = cb_data; + + return true; + } + } + + return false; +} + +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (registered_callbacks[i] == cb) { + registered_callbacks[i] = nullptr; + } + } +} + +Tag +mpd_despotify_tag_from_track(const ds_track &track) +{ + char tracknum[20]; + char comment[80]; + char date[20]; + + if (!track.has_meta_data) + return Tag(); + + TagBuilder tag; + snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber); + snprintf(date, sizeof(date), "%d", track.year); + snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", + track.file_bitrate / 1000, + track.geo_restricted ? "" : "not "); + tag.AddItem(TAG_TITLE, track.title); + tag.AddItem(TAG_ARTIST, track.artist->name); + tag.AddItem(TAG_TRACK, tracknum); + tag.AddItem(TAG_ALBUM, track.album); + tag.AddItem(TAG_DATE, date); + tag.AddItem(TAG_COMMENT, comment); + tag.SetTime(track.length / 1000); + + return tag.Commit(); +} + +struct despotify_session *mpd_despotify_get_session(void) +{ + const char *user; + const char *passwd; + bool high_bitrate; + + if (g_session) + return g_session; + + user = config_get_string(CONF_DESPOTIFY_USER, nullptr); + passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr); + high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + + if (user == nullptr || passwd == nullptr) { + LogDebug(despotify_domain, + "disabling despotify because account is not configured"); + return nullptr; + } + + if (!despotify_init()) { + LogWarning(despotify_domain, "Can't initialize despotify"); + return nullptr; + } + + g_session = despotify_init_client(callback, nullptr, + high_bitrate, true); + if (!g_session) { + LogWarning(despotify_domain, + "Can't initialize despotify client"); + return nullptr; + } + + if (!despotify_authenticate(g_session, user, passwd)) { + LogWarning(despotify_domain, + "Can't authenticate despotify session"); + despotify_exit(g_session); + return nullptr; + } + + return g_session; +} diff --git a/src/lib/despotify/DespotifyUtils.hxx b/src/lib/despotify/DespotifyUtils.hxx new file mode 100644 index 000000000..835b901a2 --- /dev/null +++ b/src/lib/despotify/DespotifyUtils.hxx @@ -0,0 +1,71 @@ +/* + * 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_DESPOTIFY_H +#define MPD_DESPOTIFY_H + +struct Tag; +struct despotify_session; +struct ds_track; + +extern const class Domain despotify_domain; + +/** + * Return the current despotify session. + * + * If the session isn't initialized, this function will initialize + * it and connect to Spotify. + * + * @return a pointer to the despotify session, or nullptr if it can't + * be initialized (e.g., if the configuration isn't supplied) + */ +struct despotify_session *mpd_despotify_get_session(void); + +/** + * Create a MPD tags structure from a spotify track + * + * @param track the track to convert + * + * @return filled in #Tag structure + */ +Tag +mpd_despotify_tag_from_track(const ds_track &track); + +/** + * Register a despotify callback. + * + * Despotify calls this e.g., when a track ends. + * + * @param cb the callback + * @param cb_data the data to pass to the callback + * + * @return true if the callback could be registered + */ +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data); + +/** + * Unregister a despotify callback. + * + * @param cb the callback to unregister. + */ +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); + +#endif + diff --git a/src/lib/expat/ExpatParser.cxx b/src/lib/expat/ExpatParser.cxx new file mode 100644 index 000000000..c6b1abe76 --- /dev/null +++ b/src/lib/expat/ExpatParser.cxx @@ -0,0 +1,92 @@ +/* + * 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 "ExpatParser.hxx" +#include "input/InputStream.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <string.h> + +static constexpr Domain expat_domain("expat"); + +void +ExpatParser::SetError(Error &error) +{ + XML_Error code = XML_GetErrorCode(parser); + error.Format(expat_domain, int(code), "XML parser failed: %s", + XML_ErrorString(code)); +} + +bool +ExpatParser::Parse(const char *data, size_t length, bool is_final, + Error &error) +{ + bool success = XML_Parse(parser, data, length, + is_final) == XML_STATUS_OK; + if (!success) + SetError(error); + + return success; +} + +bool +ExpatParser::Parse(InputStream &is, Error &error) +{ + assert(is.IsReady()); + + while (true) { + char buffer[4096]; + size_t nbytes = is.LockRead(buffer, sizeof(buffer), error); + if (nbytes == 0) + break; + + if (!Parse(buffer, nbytes, false, error)) + return false; + } + + if (error.IsDefined()) + return false; + + return Parse("", 0, true, error); +} + +const char * +ExpatParser::GetAttribute(const XML_Char **atts, + const char *name) +{ + for (unsigned i = 0; atts[i] != nullptr; i += 2) + if (strcmp(atts[i], name) == 0) + return atts[i + 1]; + + return nullptr; +} + +const char * +ExpatParser::GetAttributeCase(const XML_Char **atts, + const char *name) +{ + for (unsigned i = 0; atts[i] != nullptr; i += 2) + if (StringEqualsCaseASCII(atts[i], name)) + return atts[i + 1]; + + return nullptr; +} diff --git a/src/lib/expat/ExpatParser.hxx b/src/lib/expat/ExpatParser.hxx new file mode 100644 index 000000000..9d2ac65e5 --- /dev/null +++ b/src/lib/expat/ExpatParser.hxx @@ -0,0 +1,129 @@ +/* + * 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_EXPAT_HXX +#define MPD_EXPAT_HXX + +#include "check.h" +#include "Compiler.h" + +#include <expat.h> + +class InputStream; +class Error; + +class ExpatParser final { + const XML_Parser parser; + +public: + ExpatParser(void *userData) + :parser(XML_ParserCreate(nullptr)) { + XML_SetUserData(parser, userData); + } + + ~ExpatParser() { + XML_ParserFree(parser); + } + + void SetElementHandler(XML_StartElementHandler start, + XML_EndElementHandler end) { + XML_SetElementHandler(parser, start, end); + } + + void SetCharacterDataHandler(XML_CharacterDataHandler charhndl) { + XML_SetCharacterDataHandler(parser, charhndl); + } + + bool Parse(const char *data, size_t length, bool is_final, + Error &error); + + bool Parse(InputStream &is, Error &error); + + gcc_pure + static const char *GetAttribute(const XML_Char **atts, + const char *name); + + gcc_pure + static const char *GetAttributeCase(const XML_Char **atts, + const char *name); + +private: + void SetError(Error &error); +}; + +/** + * A specialization of #ExpatParser that provides the most common + * callbacks as virtual methods. + */ +class CommonExpatParser { + ExpatParser parser; + +public: + CommonExpatParser():parser(this) { + parser.SetElementHandler(StartElement, EndElement); + parser.SetCharacterDataHandler(CharacterData); + } + + bool Parse(const char *data, size_t length, bool is_final, + Error &error) { + return parser.Parse(data, length, is_final, error); + } + + bool Parse(InputStream &is, Error &error) { + return parser.Parse(is, error); + } + + gcc_pure + static const char *GetAttribute(const XML_Char **atts, + const char *name) { + return ExpatParser::GetAttribute(atts, name); + } + + gcc_pure + static const char *GetAttributeCase(const XML_Char **atts, + const char *name) { + return ExpatParser::GetAttributeCase(atts, name); + } + +protected: + virtual void StartElement(const XML_Char *name, + const XML_Char **atts) = 0; + virtual void EndElement(const XML_Char *name) = 0; + virtual void CharacterData(const XML_Char *s, int len) = 0; + +private: + static void XMLCALL StartElement(void *user_data, const XML_Char *name, + const XML_Char **atts) { + CommonExpatParser &p = *(CommonExpatParser *)user_data; + p.StartElement(name, atts); + } + + static void XMLCALL EndElement(void *user_data, const XML_Char *name) { + CommonExpatParser &p = *(CommonExpatParser *)user_data; + p.EndElement(name); + } + + static void XMLCALL CharacterData(void *user_data, + const XML_Char *s, int len) { + CommonExpatParser &p = *(CommonExpatParser *)user_data; + p.CharacterData(s, len); + } +}; + +#endif diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx new file mode 100644 index 000000000..b8560a4d8 --- /dev/null +++ b/src/lib/icu/Collate.cxx @@ -0,0 +1,199 @@ +/* + * 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 "Collate.hxx" + +#ifdef HAVE_ICU +#include "Error.hxx" +#include "util/WritableBuffer.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <unicode/ucol.h> +#include <unicode/ustring.h> +#elif defined(HAVE_GLIB) +#include <glib.h> +#else +#include <algorithm> +#include <ctype.h> +#endif + +#include <assert.h> +#include <string.h> +#include <strings.h> + +#ifdef HAVE_ICU +static UCollator *collator; +#endif + +#ifdef HAVE_ICU + +bool +IcuCollateInit(Error &error) +{ + assert(collator == nullptr); + assert(!error.IsDefined()); + + UErrorCode code = U_ZERO_ERROR; + collator = ucol_open("", &code); + if (collator == nullptr) { + error.Format(icu_domain, int(code), + "ucol_open() failed: %s", u_errorName(code)); + return false; + } + + return true; +} + +void +IcuCollateFinish() +{ + assert(collator != nullptr); + + ucol_close(collator); +} + +static WritableBuffer<UChar> +UCharFromUTF8(const char *src) +{ + assert(src != nullptr); + + const size_t src_length = strlen(src); + const size_t dest_capacity = src_length; + UChar *dest = new UChar[dest_capacity]; + + UErrorCode error_code = U_ZERO_ERROR; + int32_t dest_length; + u_strFromUTF8(dest, dest_capacity, &dest_length, + src, src_length, + &error_code); + if (U_FAILURE(error_code)) { + delete[] dest; + return nullptr; + } + + return { dest, size_t(dest_length) }; +} + +static WritableBuffer<char> +UCharToUTF8(ConstBuffer<UChar> src) +{ + assert(!src.IsNull()); + + /* worst-case estimate */ + size_t dest_capacity = 4 * src.size; + + char *dest = new char[dest_capacity]; + + UErrorCode error_code = U_ZERO_ERROR; + int32_t dest_length; + u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size, + &error_code); + if (U_FAILURE(error_code)) { + delete[] dest; + return nullptr; + } + + return { dest, size_t(dest_length) }; +} + +#endif + +gcc_pure +int +IcuCollate(const char *a, const char *b) +{ + assert(a != nullptr); + assert(b != nullptr); + +#ifdef HAVE_ICU + assert(collator != nullptr); + +#if U_ICU_VERSION_MAJOR_NUM >= 50 + UErrorCode code = U_ZERO_ERROR; + return (int)ucol_strcollUTF8(collator, a, -1, b, -1, &code); +#else + /* fall back to ucol_strcoll() */ + + const auto au = UCharFromUTF8(a); + const auto bu = UCharFromUTF8(b); + + int result = !au.IsNull() && !bu.IsNull() + ? (int)ucol_strcoll(collator, au.data, au.size, + bu.data, bu.size) + : strcasecmp(a, b); + + delete[] au.data; + delete[] bu.data; + + return result; +#endif + +#elif defined(HAVE_GLIB) + return g_utf8_collate(a, b); +#else + return strcasecmp(a, b); +#endif +} + +std::string +IcuCaseFold(const char *src) +{ +#ifdef HAVE_ICU + assert(collator != nullptr); + assert(src != nullptr); + + const auto u = UCharFromUTF8(src); + if (u.IsNull()) + return std::string(src); + + size_t folded_capacity = u.size * 2u; + UChar *folded = new UChar[folded_capacity]; + + UErrorCode error_code = U_ZERO_ERROR; + size_t folded_length = u_strFoldCase(folded, folded_capacity, + u.data, u.size, + U_FOLD_CASE_DEFAULT, + &error_code); + delete[] u.data; + if (folded_length == 0 || error_code != U_ZERO_ERROR) { + delete[] folded; + return std::string(src); + } + + auto result2 = UCharToUTF8({folded, folded_length}); + delete[] folded; + if (result2.IsNull()) + return std::string(src); + + std::string result(result2.data, result2.size); + delete[] result2.data; +#elif defined(HAVE_GLIB) + char *tmp = g_utf8_casefold(src, -1); + std::string result(tmp); + g_free(tmp); +#else + std::string result(src); + std::transform(result.begin(), result.end(), result.begin(), tolower); +#endif + return result; +} + diff --git a/src/lib/icu/Collate.hxx b/src/lib/icu/Collate.hxx new file mode 100644 index 000000000..8ae8de46a --- /dev/null +++ b/src/lib/icu/Collate.hxx @@ -0,0 +1,44 @@ +/* + * 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_ICU_COLLATE_HXX +#define MPD_ICU_COLLATE_HXX + +#include "check.h" +#include "Compiler.h" + +#include <string> + +class Error; + +bool +IcuCollateInit(Error &error); + +void +IcuCollateFinish(); + +gcc_pure gcc_nonnull_all +int +IcuCollate(const char *a, const char *b); + +gcc_pure gcc_nonnull_all +std::string +IcuCaseFold(const char *src); + +#endif diff --git a/src/lib/icu/Error.cxx b/src/lib/icu/Error.cxx new file mode 100644 index 000000000..1fef078ac --- /dev/null +++ b/src/lib/icu/Error.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Error.hxx" +#include "util/Domain.hxx" + +const Domain icu_domain("icu"); diff --git a/src/lib/icu/Error.hxx b/src/lib/icu/Error.hxx new file mode 100644 index 000000000..e96667f57 --- /dev/null +++ b/src/lib/icu/Error.hxx @@ -0,0 +1,29 @@ +/* + * 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_ICU_ERROR_HXX +#define MPD_ICU_ERROR_HXX + +#include "check.h" + +class Domain; + +extern const Domain icu_domain; + +#endif diff --git a/src/lib/icu/Init.cxx b/src/lib/icu/Init.cxx new file mode 100644 index 000000000..1d0ad0777 --- /dev/null +++ b/src/lib/icu/Init.cxx @@ -0,0 +1,48 @@ +/* + * 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 "Error.hxx" +#include "Collate.hxx" +#include "util/Error.hxx" + +#include <unicode/uclean.h> + +bool +IcuInit(Error &error) +{ + UErrorCode code = U_ZERO_ERROR; + u_init(&code); + if (U_FAILURE(code)) { + error.Format(icu_domain, int(code), + "u_init() failed: %s", u_errorName(code)); + return false; + } + + return IcuCollateInit(error); +} + +void +IcuFinish() +{ + IcuCollateFinish(); + + u_cleanup(); +} diff --git a/src/lib/icu/Init.hxx b/src/lib/icu/Init.hxx new file mode 100644 index 000000000..9f585e2bd --- /dev/null +++ b/src/lib/icu/Init.hxx @@ -0,0 +1,42 @@ +/* + * 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_ICU_INIT_HXX +#define MPD_ICU_INIT_HXX + +#include "check.h" + +class Error; + +#ifdef HAVE_ICU + +bool +IcuInit(Error &error); + +void +IcuFinish(); + +#else + +static inline bool IcuInit(Error &) { return true; } +static inline void IcuFinish() {} + +#endif + +#endif diff --git a/src/lib/nfs/Callback.hxx b/src/lib/nfs/Callback.hxx new file mode 100644 index 000000000..ae82ecc3c --- /dev/null +++ b/src/lib/nfs/Callback.hxx @@ -0,0 +1,33 @@ +/* + * 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_NFS_CALLBACK_HXX +#define MPD_NFS_CALLBACK_HXX + +#include "check.h" + +class Error; + +class NfsCallback { +public: + virtual void OnNfsCallback(unsigned status, void *data) = 0; + virtual void OnNfsError(Error &&error) = 0; +}; + +#endif diff --git a/src/lib/nfs/Cancellable.hxx b/src/lib/nfs/Cancellable.hxx new file mode 100644 index 000000000..50762b582 --- /dev/null +++ b/src/lib/nfs/Cancellable.hxx @@ -0,0 +1,177 @@ +/* + * 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_NFS_CANCELLABLE_HXX +#define MPD_NFS_CANCELLABLE_HXX + +#include "Compiler.h" + +#include <list> +#include <algorithm> + +#include <assert.h> + +template<typename T> +class CancellablePointer { +public: + typedef T *pointer_type; + typedef T &reference_type; + typedef const T &const_reference_type; + +private: + pointer_type p; + +public: + explicit constexpr CancellablePointer(reference_type _p):p(&_p) {} + + CancellablePointer(const CancellablePointer &) = delete; + + constexpr bool IsCancelled() const { + return p == nullptr; + } + + void Cancel() { + assert(!IsCancelled()); + + p = nullptr; + } + + reference_type Get() { + assert(p != nullptr); + + return *p; + } + + constexpr bool Is(const_reference_type other) const { + return p == &other; + } +}; + +template<typename T, typename CT=CancellablePointer<T>> +class CancellableList { +public: + typedef typename CT::reference_type reference_type; + typedef typename CT::const_reference_type const_reference_type; + +private: + typedef std::list<CT> List; + typedef typename List::iterator iterator; + typedef typename List::const_iterator const_iterator; + List list; + + class MatchPointer { + const_reference_type p; + + public: + explicit constexpr MatchPointer(const_reference_type _p) + :p(_p) {} + + constexpr bool operator()(const CT &a) const { + return a.Is(p); + } + }; + + gcc_pure + iterator Find(reference_type p) { + return std::find_if(list.begin(), list.end(), MatchPointer(p)); + } + + gcc_pure + const_iterator Find(const_reference_type p) const { + return std::find_if(list.begin(), list.end(), MatchPointer(p)); + } + + class MatchReference { + const CT &c; + + public: + constexpr explicit MatchReference(const CT &_c):c(_c) {} + + gcc_pure + bool operator()(const CT &a) const { + return &a == &c; + } + }; + + gcc_pure + iterator Find(CT &c) { + return std::find_if(list.begin(), list.end(), + MatchReference(c)); + } + + gcc_pure + const_iterator Find(const CT &c) const { + return std::find_if(list.begin(), list.end(), + MatchReference(c)); + } + +public: +#ifndef NDEBUG + gcc_pure + bool IsEmpty() const { + for (const auto &c : list) + if (!c.IsCancelled()) + return false; + + return true; + } +#endif + + gcc_pure + bool Contains(const_reference_type p) const { + return Find(p) != list.end(); + } + + template<typename... Args> + CT &Add(reference_type p, Args&&... args) { + assert(Find(p) == list.end()); + + list.emplace_back(p, std::forward<Args>(args)...); + return list.back(); + } + + void RemoveLast() { + list.pop_back(); + } + + bool RemoveOptional(CT &ct) { + auto i = Find(ct); + if (i == list.end()) + return false; + + list.erase(i); + return true; + } + + void Remove(CT &ct) { + auto i = Find(ct); + assert(i != list.end()); + + list.erase(i); + } + + void Cancel(reference_type p) { + auto i = Find(p); + assert(i != list.end()); + + i->Cancel(); + } +}; + +#endif diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx new file mode 100644 index 000000000..a2f0cbb45 --- /dev/null +++ b/src/lib/nfs/Connection.cxx @@ -0,0 +1,417 @@ +/* + * 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 "Connection.hxx" +#include "Lease.hxx" +#include "Domain.hxx" +#include "Callback.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" +#include "event/Call.hxx" + +extern "C" { +#include <nfsc/libnfs.h> +} + +#include <utility> + +#include <poll.h> /* for POLLIN, POLLOUT */ + +inline bool +NfsConnection::CancellableCallback::Open(nfs_context *ctx, + const char *path, int flags, + Error &error) +{ + int result = nfs_open_async(ctx, path, flags, + Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_open_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline bool +NfsConnection::CancellableCallback::Stat(nfs_context *ctx, + struct nfsfh *fh, + Error &error) +{ + int result = nfs_fstat_async(ctx, fh, Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_fstat_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline bool +NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh, + uint64_t offset, size_t size, + Error &error) +{ + int result = nfs_pread_async(ctx, fh, offset, size, Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_pread_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline void +NfsConnection::CancellableCallback::Callback(int err, void *data) +{ + if (!IsCancelled()) { + NfsCallback &cb = Get(); + + connection.callbacks.Remove(*this); + + if (err >= 0) + cb.OnNfsCallback((unsigned)err, data); + else + cb.OnNfsError(Error(nfs_domain, err, + (const char *)data)); + } else { + connection.callbacks.Remove(*this); + } +} + +void +NfsConnection::CancellableCallback::Callback(int err, + gcc_unused struct nfs_context *nfs, + void *data, void *private_data) +{ + CancellableCallback &c = *(CancellableCallback *)private_data; + c.Callback(err, data); +} + +static constexpr unsigned +libnfs_to_events(int i) +{ + return ((i & POLLIN) ? SocketMonitor::READ : 0) | + ((i & POLLOUT) ? SocketMonitor::WRITE : 0); +} + +static constexpr int +events_to_libnfs(unsigned i) +{ + return ((i & SocketMonitor::READ) ? POLLIN : 0) | + ((i & SocketMonitor::WRITE) ? POLLOUT : 0); +} + +NfsConnection::~NfsConnection() +{ + assert(new_leases.empty()); + assert(active_leases.empty()); + assert(callbacks.IsEmpty()); + + if (context != nullptr) + BlockingCall(SocketMonitor::GetEventLoop(), [this](){ + DestroyContext(); + }); +} + +void +NfsConnection::AddLease(NfsLease &lease) +{ + { + const ScopeLock protect(mutex); + new_leases.push_back(&lease); + } + + DeferredMonitor::Schedule(); +} + +void +NfsConnection::RemoveLease(NfsLease &lease) +{ + const ScopeLock protect(mutex); + + new_leases.remove(&lease); + active_leases.remove(&lease); +} + +bool +NfsConnection::Open(const char *path, int flags, NfsCallback &callback, + Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this); + if (!c.Open(context, path, flags, error)) { + callbacks.RemoveLast(); + return false; + } + + ScheduleSocket(); + return true; +} + +bool +NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this); + if (!c.Stat(context, fh, error)) { + callbacks.RemoveLast(); + return false; + } + + ScheduleSocket(); + return true; +} + +bool +NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size, + NfsCallback &callback, Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this); + if (!c.Read(context, fh, offset, size, error)) { + callbacks.RemoveLast(); + return false; + } + + ScheduleSocket(); + return true; +} + +void +NfsConnection::Cancel(NfsCallback &callback) +{ + callbacks.Cancel(callback); +} + +static void +DummyCallback(int, struct nfs_context *, void *, void *) +{ +} + +void +NfsConnection::Close(struct nfsfh *fh) +{ + nfs_close_async(context, fh, DummyCallback, nullptr); + ScheduleSocket(); +} + +void +NfsConnection::DestroyContext() +{ + assert(context != nullptr); + + SocketMonitor::Cancel(); + nfs_destroy_context(context); + context = nullptr; +} + +void +NfsConnection::ScheduleSocket() +{ + assert(context != nullptr); + + if (!SocketMonitor::IsDefined()) { + int _fd = nfs_get_fd(context); + fd_set_cloexec(_fd, true); + SocketMonitor::Open(_fd); + } + + SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context))); +} + +bool +NfsConnection::OnSocketReady(unsigned flags) +{ + bool closed = false; + + const bool was_mounted = mount_finished; + if (!mount_finished) + /* until the mount is finished, the NFS client may use + various sockets, therefore we unregister and + re-register it each time */ + SocketMonitor::Steal(); + + assert(!in_event); + in_event = true; + + assert(!in_service); + in_service = true; + postponed_destroy = false; + + int result = nfs_service(context, events_to_libnfs(flags)); + + assert(context != nullptr); + assert(in_service); + in_service = false; + + if (postponed_destroy) { + /* somebody has called nfs_client_free() while we were inside + nfs_service() */ + const ScopeLock protect(mutex); + DestroyContext(); + closed = true; + // TODO? nfs_client_cleanup_files(client); + } else if (!was_mounted && mount_finished) { + const ScopeLock protect(mutex); + + if (postponed_mount_error.IsDefined()) { + DestroyContext(); + closed = true; + BroadcastMountError(std::move(postponed_mount_error)); + } else if (result == 0) + BroadcastMountSuccess(); + } else if (result < 0) { + /* the connection has failed */ + Error error; + error.Format(nfs_domain, "NFS connection has failed: %s", + nfs_get_error(context)); + + const ScopeLock protect(mutex); + + DestroyContext(); + closed = true; + + if (!mount_finished) + BroadcastMountError(std::move(error)); + else + BroadcastError(std::move(error)); + } + + assert(in_event); + in_event = false; + + if (context != nullptr) + ScheduleSocket(); + + return !closed; +} + +inline void +NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs, + gcc_unused void *data) +{ + assert(context == nfs); + + mount_finished = true; + + if (status < 0) { + postponed_mount_error.Set(nfs_domain, status, + "nfs_mount_async() failed"); + return; + } +} + +void +NfsConnection::MountCallback(int status, nfs_context *nfs, void *data, + void *private_data) +{ + NfsConnection *c = (NfsConnection *)private_data; + + c->MountCallback(status, nfs, data); +} + +inline bool +NfsConnection::MountInternal(Error &error) +{ + if (context != nullptr) + return true; + + context = nfs_init_context(); + if (context == nullptr) { + error.Set(nfs_domain, "nfs_init_context() failed"); + return false; + } + + postponed_mount_error.Clear(); + mount_finished = false; + in_service = false; + in_event = false; + + if (nfs_mount_async(context, server.c_str(), export_name.c_str(), + MountCallback, this) != 0) { + error.Format(nfs_domain, + "nfs_mount_async() failed: %s", + nfs_get_error(context)); + nfs_destroy_context(context); + context = nullptr; + return false; + } + + ScheduleSocket(); + return true; +} + +void +NfsConnection::BroadcastMountSuccess() +{ + while (!new_leases.empty()) { + auto i = new_leases.begin(); + active_leases.splice(active_leases.end(), new_leases, i); + (*i)->OnNfsConnectionReady(); + } +} + +void +NfsConnection::BroadcastMountError(Error &&error) +{ + while (!new_leases.empty()) { + auto l = new_leases.front(); + new_leases.pop_front(); + l->OnNfsConnectionFailed(error); + } + + OnNfsConnectionError(std::move(error)); +} + +void +NfsConnection::BroadcastError(Error &&error) +{ + while (!active_leases.empty()) { + auto l = active_leases.front(); + active_leases.pop_front(); + l->OnNfsConnectionDisconnected(error); + } + + BroadcastMountError(std::move(error)); +} + +void +NfsConnection::RunDeferred() +{ + { + Error error; + if (!MountInternal(error)) { + const ScopeLock protect(mutex); + BroadcastMountError(std::move(error)); + return; + } + } + + if (mount_finished) { + const ScopeLock protect(mutex); + BroadcastMountSuccess(); + } +} diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx new file mode 100644 index 000000000..cb4126d25 --- /dev/null +++ b/src/lib/nfs/Connection.hxx @@ -0,0 +1,176 @@ +/* + * 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_NFS_CONNECTION_HXX +#define MPD_NFS_CONNECTION_HXX + +#include "Lease.hxx" +#include "Cancellable.hxx" +#include "thread/Mutex.hxx" +#include "event/SocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "util/Error.hxx" + +#include <string> +#include <list> + +struct nfs_context; +class NfsCallback; + +/** + * An asynchronous connection to a NFS server. + */ +class NfsConnection : SocketMonitor, DeferredMonitor { + class CancellableCallback : public CancellablePointer<NfsCallback> { + NfsConnection &connection; + + public: + explicit constexpr CancellableCallback(NfsCallback &_callback, + NfsConnection &_connection) + :CancellablePointer<NfsCallback>(_callback), + connection(_connection) {} + + bool Open(nfs_context *context, const char *path, int flags, + Error &error); + bool Stat(nfs_context *context, struct nfsfh *fh, + Error &error); + bool Read(nfs_context *context, struct nfsfh *fh, + uint64_t offset, size_t size, + Error &error); + + private: + static void Callback(int err, struct nfs_context *nfs, + void *data, void *private_data); + void Callback(int err, void *data); + }; + + std::string server, export_name; + + nfs_context *context; + + Mutex mutex; + + typedef std::list<NfsLease *> LeaseList; + LeaseList new_leases, active_leases; + + typedef CancellableList<NfsCallback, CancellableCallback> CallbackList; + CallbackList callbacks; + + Error postponed_mount_error; + + /** + * True when nfs_service() is being called. During that, + * nfs_client_free() is postponed, or libnfs will crash. See + * #postponed_destroy. + */ + bool in_service; + + /** + * True when OnSocketReady() is being called. During that, + * event updates are omitted. + */ + bool in_event; + + /** + * True when nfs_client_free() has been called while #in_service + * was true. + */ + bool postponed_destroy; + + bool mount_finished; + +public: + gcc_nonnull_all + NfsConnection(EventLoop &_loop, + const char *_server, const char *_export_name) + :SocketMonitor(_loop), DeferredMonitor(_loop), + server(_server), export_name(_export_name), + context(nullptr) {} + +#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8) + /* needed for NfsManager::GetConnection() due to lack of + std::map::emplace() */ + NfsConnection(NfsConnection &&other) + :SocketMonitor(((SocketMonitor &)other).GetEventLoop()), + DeferredMonitor(((DeferredMonitor &)other).GetEventLoop()), + server(std::move(other.server)), + export_name(std::move(other.export_name)), + context(nullptr) { + assert(other.context == nullptr); + assert(other.new_leases.empty()); + assert(other.active_leases.empty()); + assert(other.callbacks.IsEmpty()); + } +#endif + + ~NfsConnection(); + + gcc_pure + const char *GetServer() const { + return server.c_str(); + } + + gcc_pure + const char *GetExportName() const { + return export_name.c_str(); + } + + /** + * Ensure that the connection is established. The connection + * is kept up while at least one #NfsLease is registered. + * + * This method is thread-safe. However, #NfsLease's methods + * will be invoked from within the #EventLoop's thread. + */ + void AddLease(NfsLease &lease); + void RemoveLease(NfsLease &lease); + + bool Open(const char *path, int flags, NfsCallback &callback, + Error &error); + bool Stat(struct nfsfh *fh, NfsCallback &callback, Error &error); + bool Read(struct nfsfh *fh, uint64_t offset, size_t size, + NfsCallback &callback, Error &error); + void Cancel(NfsCallback &callback); + + void Close(struct nfsfh *fh); + +protected: + virtual void OnNfsConnectionError(Error &&error) = 0; + +private: + void DestroyContext(); + bool MountInternal(Error &error); + void BroadcastMountSuccess(); + void BroadcastMountError(Error &&error); + void BroadcastError(Error &&error); + + static void MountCallback(int status, nfs_context *nfs, void *data, + void *private_data); + void MountCallback(int status, nfs_context *nfs, void *data); + + void ScheduleSocket(); + + /* virtual methods from SocketMonitor */ + virtual bool OnSocketReady(unsigned flags) override; + + /* virtual methods from DeferredMonitor */ + virtual void RunDeferred() override; +}; + +#endif diff --git a/src/lib/nfs/Domain.cxx b/src/lib/nfs/Domain.cxx new file mode 100644 index 000000000..fefe0dbf3 --- /dev/null +++ b/src/lib/nfs/Domain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain nfs_domain("nfs"); diff --git a/src/lib/nfs/Domain.hxx b/src/lib/nfs/Domain.hxx new file mode 100644 index 000000000..6730b92e1 --- /dev/null +++ b/src/lib/nfs/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NFS_DOMAIN_HXX +#define MPD_NFS_DOMAIN_HXX + +class Domain; + +extern const Domain nfs_domain; + +#endif diff --git a/src/lib/nfs/FileReader.cxx b/src/lib/nfs/FileReader.cxx new file mode 100644 index 000000000..52d951fa6 --- /dev/null +++ b/src/lib/nfs/FileReader.cxx @@ -0,0 +1,244 @@ +/* + * 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 "FileReader.hxx" +#include "Glue.hxx" +#include "Connection.hxx" +#include "Domain.hxx" +#include "event/Call.hxx" +#include "IOThread.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +#include <utility> + +#include <assert.h> +#include <string.h> +#include <fcntl.h> + +NfsFileReader::NfsFileReader() + :DeferredMonitor(io_thread_get()), state(State::INITIAL) +{ +} + +NfsFileReader::~NfsFileReader() +{ + assert(state == State::INITIAL); +} + +void +NfsFileReader::Close() +{ + if (state == State::INITIAL) + return; + + if (state == State::DEFER) { + state = State::INITIAL; + DeferredMonitor::Cancel(); + return; + } + + connection->RemoveLease(*this); + + if (state > State::MOUNT && state != State::IDLE) + connection->Cancel(*this); + + if (state > State::OPEN) + connection->Close(fh); + + state = State::INITIAL; +} + +void +NfsFileReader::DeferClose() +{ + BlockingCall(io_thread_get(), [this](){ Close(); }); +} + +bool +NfsFileReader::Open(const char *uri, Error &error) +{ + assert(state == State::INITIAL); + + if (!StringStartsWith(uri, "nfs://")) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return false; + } + + uri += 6; + + const char *slash = strchr(uri, '/'); + if (slash == nullptr) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return false; + } + + server = std::string(uri, slash); + + uri = slash; + slash = strrchr(uri + 1, '/'); + if (slash == nullptr || slash[1] == 0) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return false; + } + + export_name = std::string(uri, slash); + path = slash; + + state = State::DEFER; + DeferredMonitor::Schedule(); + return true; +} + +bool +NfsFileReader::Read(uint64_t offset, size_t size, Error &error) +{ + assert(state == State::IDLE); + + if (!connection->Read(fh, offset, size, *this, error)) + return false; + + state = State::READ; + return true; +} + +void +NfsFileReader::CancelRead() +{ + if (state == State::READ) { + connection->Cancel(*this); + state = State::IDLE; + } +} + +void +NfsFileReader::OnNfsConnectionReady() +{ + assert(state == State::MOUNT); + + Error error; + if (!connection->Open(path, O_RDONLY, *this, error)) { + OnNfsFileError(std::move(error)); + return; + } + + state = State::OPEN; +} + +void +NfsFileReader::OnNfsConnectionFailed(const Error &error) +{ + assert(state == State::MOUNT); + + Error copy; + copy.Set(error); + OnNfsFileError(std::move(copy)); +} + +void +NfsFileReader::OnNfsConnectionDisconnected(const Error &error) +{ + assert(state > State::MOUNT); + + state = State::INITIAL; + + Error copy; + copy.Set(error); + OnNfsFileError(std::move(copy)); +} + +inline void +NfsFileReader::OpenCallback(nfsfh *_fh) +{ + assert(state == State::OPEN); + assert(connection != nullptr); + assert(_fh != nullptr); + + fh = _fh; + + Error error; + if (!connection->Stat(fh, *this, error)) { + OnNfsFileError(std::move(error)); + return; + } + + state = State::STAT; +} + +inline void +NfsFileReader::StatCallback(const struct stat *st) +{ + assert(state == State::STAT); + assert(connection != nullptr); + assert(fh != nullptr); + assert(st != nullptr); + + if (!S_ISREG(st->st_mode)) { + OnNfsFileError(Error(nfs_domain, "Not a regular file")); + return; + } + + state = State::IDLE; + + OnNfsFileOpen(st->st_size); +} + +void +NfsFileReader::OnNfsCallback(unsigned status, void *data) +{ + switch (state) { + case State::INITIAL: + case State::DEFER: + case State::MOUNT: + case State::IDLE: + assert(false); + gcc_unreachable(); + + case State::OPEN: + OpenCallback((struct nfsfh *)data); + break; + + case State::STAT: + StatCallback((const struct stat *)data); + break; + + case State::READ: + state = State::IDLE; + OnNfsFileRead(data, status); + break; + } +} + +void +NfsFileReader::OnNfsError(Error &&error) +{ + OnNfsFileError(std::move(error)); +} + +void +NfsFileReader::RunDeferred() +{ + assert(state == State::DEFER); + + state = State::MOUNT; + + connection = &nfs_get_connection(server.c_str(), export_name.c_str()); + connection->AddLease(*this); +} diff --git a/src/lib/nfs/FileReader.hxx b/src/lib/nfs/FileReader.hxx new file mode 100644 index 000000000..7f43e0ecf --- /dev/null +++ b/src/lib/nfs/FileReader.hxx @@ -0,0 +1,94 @@ +/* + * 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_NFS_FILE_READER_HXX +#define MPD_NFS_FILE_READER_HXX + +#include "check.h" +#include "Lease.hxx" +#include "Callback.hxx" +#include "event/DeferredMonitor.hxx" + +#include <string> + +#include <stdint.h> +#include <stddef.h> +#include <sys/stat.h> + +struct nfsfh; +class NfsConnection; + +class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor { + enum class State { + INITIAL, + DEFER, + MOUNT, + OPEN, + STAT, + READ, + IDLE, + }; + + State state; + + std::string server, export_name; + const char *path; + + NfsConnection *connection; + + nfsfh *fh; + +public: + NfsFileReader(); + ~NfsFileReader(); + + void Close(); + void DeferClose(); + + bool Open(const char *uri, Error &error); + bool Read(uint64_t offset, size_t size, Error &error); + void CancelRead(); + + bool IsIdle() const { + return state == State::IDLE; + } + +protected: + virtual void OnNfsFileOpen(uint64_t size) = 0; + virtual void OnNfsFileRead(const void *data, size_t size) = 0; + virtual void OnNfsFileError(Error &&error) = 0; + +private: + void OpenCallback(nfsfh *_fh); + void StatCallback(const struct stat *st); + + /* virtual methods from NfsLease */ + void OnNfsConnectionReady() final; + void OnNfsConnectionFailed(const Error &error) final; + void OnNfsConnectionDisconnected(const Error &error) final; + + /* virtual methods from NfsCallback */ + void OnNfsCallback(unsigned status, void *data) final; + void OnNfsError(Error &&error) final; + + /* virtual methods from DeferredMonitor */ + void RunDeferred() final; +}; + +#endif diff --git a/src/lib/nfs/Glue.cxx b/src/lib/nfs/Glue.cxx new file mode 100644 index 000000000..c043f06c6 --- /dev/null +++ b/src/lib/nfs/Glue.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 "Glue.hxx" +#include "Manager.hxx" +#include "IOThread.hxx" +#include "util/Manual.hxx" + +class NfsGlue { + NfsManager manager; + +public: + NfsGlue(EventLoop &_loop) + :manager(_loop) {} + + ~NfsGlue() { + //assert(open_uri.empty()); + } + + NfsConnection &GetConnection(const char *server, const char *export_name) { + return manager.GetConnection(server, export_name); + } +}; + +static Manual<NfsGlue> nfs_glue; +static unsigned in_use; + +void +nfs_init() +{ + if (in_use++ > 0) + return; + + nfs_glue.Construct(io_thread_get()); +} + +void +nfs_finish() +{ + assert(in_use > 0); + + if (--in_use > 0) + return; + + nfs_glue.Destruct(); +} + +NfsConnection & +nfs_get_connection(const char *server, const char *export_name) +{ + return nfs_glue->GetConnection(server, export_name); +} diff --git a/src/lib/nfs/Glue.hxx b/src/lib/nfs/Glue.hxx new file mode 100644 index 000000000..6da8957cb --- /dev/null +++ b/src/lib/nfs/Glue.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NFS_GLUE_HXX +#define MPD_NFS_GLUE_HXX + +#include "check.h" +#include "Compiler.h" + +class NfsConnection; + +void +nfs_init(); + +void +nfs_finish(); + +gcc_pure +NfsConnection & +nfs_get_connection(const char *server, const char *export_name); + +#endif diff --git a/src/lib/nfs/Lease.hxx b/src/lib/nfs/Lease.hxx new file mode 100644 index 000000000..6f88acf53 --- /dev/null +++ b/src/lib/nfs/Lease.hxx @@ -0,0 +1,48 @@ +/* + * 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_NFS_LEASE_HXX +#define MPD_NFS_LEASE_HXX + +#include "check.h" + +class Error; + +class NfsLease { +public: + /** + * The #NfsConnection has successfully mounted the server's + * export and is ready for regular operation. + */ + virtual void OnNfsConnectionReady() = 0; + + /** + * The #NfsConnection has failed to mount the server's export. + * This is being called instead of OnNfsConnectionReady(). + */ + virtual void OnNfsConnectionFailed(const Error &error) = 0; + + /** + * The #NfsConnection has failed after OnNfsConnectionReady() + * had been called already. + */ + virtual void OnNfsConnectionDisconnected(const Error &error) = 0; +}; + +#endif diff --git a/src/lib/nfs/Manager.cxx b/src/lib/nfs/Manager.cxx new file mode 100644 index 000000000..5c236552c --- /dev/null +++ b/src/lib/nfs/Manager.cxx @@ -0,0 +1,57 @@ +/* + * 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 "Manager.hxx" +#include "event/Loop.hxx" +#include "Log.hxx" + +void +NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error) +{ + FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName()); + + manager.connections.erase(Key(GetServer(), GetExportName())); +} + +NfsConnection & +NfsManager::GetConnection(const char *server, const char *export_name) +{ + assert(server != nullptr); + assert(export_name != nullptr); + assert(loop.IsInside()); + + const std::string key = Key(server, export_name); + +#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8) + /* std::map::emplace() not available; this hack uses the move + constructor */ + auto e = connections.insert(std::make_pair(key, + ManagedConnection(*this, loop, + server, + export_name))); +#else + auto e = connections.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(*this, loop, + server, + export_name)); +#endif + return e.first->second; +} diff --git a/src/lib/nfs/Manager.hxx b/src/lib/nfs/Manager.hxx new file mode 100644 index 000000000..11a779a2a --- /dev/null +++ b/src/lib/nfs/Manager.hxx @@ -0,0 +1,79 @@ +/* + * 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_NFS_MANAGER_HXX +#define MPD_NFS_MANAGER_HXX + +#include "check.h" +#include "Connection.hxx" + +#include <string> +#include <map> + +/** + * A manager for NFS connections. Handles multiple connections to + * multiple NFS servers. + */ +class NfsManager { + class ManagedConnection final : public NfsConnection { + NfsManager &manager; + + public: + ManagedConnection(NfsManager &_manager, EventLoop &_loop, + const char *_server, + const char *_export_name) + :NfsConnection(_loop, _server, _export_name), + manager(_manager) {} + +#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8) + /* needed due to lack of std::map::emplace() */ + ManagedConnection(ManagedConnection &&other) + :NfsConnection(std::move(other)), + manager(other.manager) {} +#endif + + protected: + /* virtual methods from NfsConnection */ + void OnNfsConnectionError(Error &&error) override; + }; + + EventLoop &loop; + + /** + * Maps server+":"+export_name (see method Key()) to + * #ManagedConnection. + */ + std::map<std::string, ManagedConnection> connections; + +public: + NfsManager(EventLoop &_loop) + :loop(_loop) {} + + gcc_pure + NfsConnection &GetConnection(const char *server, + const char *export_name); + +private: + gcc_pure + static std::string Key(const char *server, const char *export_name) { + return std::string(server) + ':' + export_name; + } +}; + +#endif diff --git a/src/lib/smbclient/Domain.cxx b/src/lib/smbclient/Domain.cxx new file mode 100644 index 000000000..00f5ee6c1 --- /dev/null +++ b/src/lib/smbclient/Domain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain smbclient_domain("smbclient"); diff --git a/src/lib/smbclient/Domain.hxx b/src/lib/smbclient/Domain.hxx new file mode 100644 index 000000000..3b21c4e60 --- /dev/null +++ b/src/lib/smbclient/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SMBCLIENT_DOMAIN_HXX +#define MPD_SMBCLIENT_DOMAIN_HXX + +class Domain; + +extern const Domain smbclient_domain; + +#endif diff --git a/src/lib/smbclient/Init.cxx b/src/lib/smbclient/Init.cxx new file mode 100644 index 000000000..a7f2da4dd --- /dev/null +++ b/src/lib/smbclient/Init.cxx @@ -0,0 +1,55 @@ +/* + * 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 "Mutex.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <libsmbclient.h> + +#include <string.h> + +static void +mpd_smbc_get_auth_data(gcc_unused const char *srv, + gcc_unused const char *shr, + char *wg, gcc_unused int wglen, + char *un, gcc_unused int unlen, + char *pw, gcc_unused int pwlen) +{ + // TODO: implement + strcpy(wg, "WORKGROUP"); + strcpy(un, ""); + strcpy(pw, ""); +} + +bool +SmbclientInit(Error &error) +{ + const ScopeLock protect(smbclient_mutex); + + constexpr int debug = 0; + if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) { + error.SetErrno("smbc_init() failed"); + return false; + } + + return true; +} diff --git a/src/lib/smbclient/Init.hxx b/src/lib/smbclient/Init.hxx new file mode 100644 index 000000000..21014ec8d --- /dev/null +++ b/src/lib/smbclient/Init.hxx @@ -0,0 +1,33 @@ +/* + * 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_SMBCLIENT_INIT_HXX +#define MPD_SMBCLIENT_INIT_HXX + +#include "check.h" + +class Error; + +/** + * Initialize libsmbclient. + */ +bool +SmbclientInit(Error &error); + +#endif diff --git a/src/lib/smbclient/Mutex.cxx b/src/lib/smbclient/Mutex.cxx new file mode 100644 index 000000000..4dfc5a9d3 --- /dev/null +++ b/src/lib/smbclient/Mutex.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Mutex.hxx" +#include "thread/Mutex.hxx" + +Mutex smbclient_mutex; diff --git a/src/lib/smbclient/Mutex.hxx b/src/lib/smbclient/Mutex.hxx new file mode 100644 index 000000000..dc7372e6e --- /dev/null +++ b/src/lib/smbclient/Mutex.hxx @@ -0,0 +1,31 @@ +/* + * 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_SMBCLIENT_MUTEX_HXX +#define MPD_SMBCLIENT_MUTEX_HXX + +class Mutex; + +/** + * Since libsmbclient is not thread-safe, this mutex must be locked + * during all libsmbclient function calls. + */ +extern Mutex smbclient_mutex; + +#endif diff --git a/src/lib/upnp/Action.hxx b/src/lib/upnp/Action.hxx new file mode 100644 index 000000000..28c88be92 --- /dev/null +++ b/src/lib/upnp/Action.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_UPNP_ACTION_HXX +#define MPD_UPNP_ACTION_HXX + +#include "Compiler.h" + +#include <upnp/upnptools.h> + +static inline constexpr unsigned +CountNameValuePairs() +{ + return 0; +} + +template<typename... Args> +static inline constexpr unsigned +CountNameValuePairs(gcc_unused const char *name, gcc_unused const char *value, + Args... args) +{ + return 1 + CountNameValuePairs(args...); +} + +/** + * A wrapper for UpnpMakeAction() that counts the number of name/value + * pairs and adds the nullptr sentinel. + */ +template<typename... Args> +static inline IXML_Document * +MakeActionHelper(const char *action_name, const char *service_type, + Args... args) +{ + const unsigned n = CountNameValuePairs(args...); + return UpnpMakeAction(action_name, service_type, n, + args..., + nullptr, nullptr); +} + +#endif diff --git a/src/lib/upnp/Callback.hxx b/src/lib/upnp/Callback.hxx new file mode 100644 index 000000000..85daf0a7e --- /dev/null +++ b/src/lib/upnp/Callback.hxx @@ -0,0 +1,46 @@ +/* + * 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_UPNP_CALLBACK_HXX +#define MPD_UPNP_CALLBACK_HXX + +#include <upnp/upnp.h> + +/** + * A class that is supposed to be used for libupnp asynchronous + * callbacks. + */ +class UpnpCallback { +public: + /** + * Pass this value as "cookie" pointer to libupnp asynchronous + * functions. + */ + void *GetUpnpCookie() { + return this; + } + + static UpnpCallback &FromUpnpCookie(void *cookie) { + return *(UpnpCallback *)cookie; + } + + virtual int Invoke(Upnp_EventType et, void *evp) = 0; +}; + +#endif diff --git a/src/lib/upnp/ClientInit.cxx b/src/lib/upnp/ClientInit.cxx new file mode 100644 index 000000000..77d9cf03d --- /dev/null +++ b/src/lib/upnp/ClientInit.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 "ClientInit.hxx" +#include "Init.hxx" +#include "Callback.hxx" +#include "Domain.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <upnp/upnptools.h> + +static Mutex upnp_client_init_mutex; +static unsigned upnp_client_ref; +static UpnpClient_Handle upnp_client_handle; + +static int +UpnpClientCallback(Upnp_EventType et, void *evp, void *cookie) +{ + if (cookie == nullptr) + /* this is the cookie passed to UpnpRegisterClient(); + but can this ever happen? Will libupnp ever invoke + the registered callback without that cookie? */ + return UPNP_E_SUCCESS; + + UpnpCallback &callback = UpnpCallback::FromUpnpCookie(cookie); + return callback.Invoke(et, evp); +} + +static bool +DoInit(Error &error) +{ + auto code = UpnpRegisterClient(UpnpClientCallback, nullptr, + &upnp_client_handle); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpRegisterClient() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + return true; +} + +bool +UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error) +{ + if (!UpnpGlobalInit(error)) + return false; + + upnp_client_init_mutex.lock(); + bool success = upnp_client_ref > 0 || DoInit(error); + upnp_client_init_mutex.unlock(); + + if (success) { + ++upnp_client_ref; + handle = upnp_client_handle; + } else + UpnpGlobalFinish(); + + return success; +} + +void +UpnpClientGlobalFinish() +{ + upnp_client_init_mutex.lock(); + + assert(upnp_client_ref > 0); + if (--upnp_client_ref == 0) + UpnpUnRegisterClient(upnp_client_handle); + + upnp_client_init_mutex.unlock(); + + UpnpGlobalFinish(); +} diff --git a/src/lib/upnp/ClientInit.hxx b/src/lib/upnp/ClientInit.hxx new file mode 100644 index 000000000..645e64ca6 --- /dev/null +++ b/src/lib/upnp/ClientInit.hxx @@ -0,0 +1,35 @@ +/* + * 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_UPNP_CLIENT_INIT_HXX +#define MPD_UPNP_CLIENT_INIT_HXX + +#include "check.h" + +#include <upnp/upnp.h> + +class Error; + +bool +UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error); + +void +UpnpClientGlobalFinish(); + +#endif diff --git a/src/lib/upnp/ContentDirectoryService.cxx b/src/lib/upnp/ContentDirectoryService.cxx new file mode 100644 index 000000000..ef8757ec5 --- /dev/null +++ b/src/lib/upnp/ContentDirectoryService.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 "ContentDirectoryService.hxx" +#include "Domain.hxx" +#include "Device.hxx" +#include "ixmlwrap.hxx" +#include "Util.hxx" +#include "Action.hxx" +#include "util/Error.hxx" + +ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device, + const UPnPService &service) + :m_actionURL(caturl(device.URLBase, service.controlURL)), + m_serviceType(service.serviceType), + m_deviceId(device.UDN), + m_friendlyName(device.friendlyName), + m_manufacturer(device.manufacturer), + m_modelName(device.modelName), + m_rdreqcnt(200) +{ + if (!m_modelName.compare("MediaTomb")) { + // Readdir by 200 entries is good for most, but MediaTomb likes + // them really big. Actually 1000 is better but I don't dare + m_rdreqcnt = 500; + } +} + +ContentDirectoryService::~ContentDirectoryService() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl, + std::list<std::string> &result, + Error &error) const +{ + assert(result.empty()); + + IXML_Document *request = + UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(), + 0, + nullptr, nullptr); + if (request == 0) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + const char *s = ixmlwrap::getFirstElementValue(response, "SearchCaps"); + if (s == nullptr || *s == 0) { + ixmlDocument_free(response); + return true; + } + + bool success = true; + if (!csvToStrings(s, result)) { + error.Set(upnp_domain, "Bad response"); + success = false; + } + + ixmlDocument_free(response); + return success; +} diff --git a/src/lib/upnp/ContentDirectoryService.hxx b/src/lib/upnp/ContentDirectoryService.hxx new file mode 100644 index 000000000..0b03df2e7 --- /dev/null +++ b/src/lib/upnp/ContentDirectoryService.hxx @@ -0,0 +1,135 @@ +/* + * 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 _UPNPDIR_HXX_INCLUDED_ +#define _UPNPDIR_HXX_INCLUDED_ + +#include "Compiler.h" + +#include <upnp/upnp.h> + +#include <string> +#include <list> + +class Error; +class UPnPDevice; +struct UPnPService; +class UPnPDirContent; + +/** + * Content Directory Service class. + * + * This stores identity data from a directory service + * and the device it belongs to, and has methods to query + * the directory, using libupnp for handling the UPnP protocols. + * + * Note: m_rdreqcnt: number of entries requested per directory read. + * 0 means all entries. The device can still return less entries than + * requested, depending on its own limits. In general it's not optimal + * becauses it triggers issues, and is sometimes actually slower, e.g. on + * a D-Link NAS 327 + * + * The value chosen may affect by the UpnpSetMaxContentLength + * (2000*1024) done during initialization, but this should be ample + */ +class ContentDirectoryService { + std::string m_actionURL; + std::string m_serviceType; + std::string m_deviceId; + std::string m_friendlyName; + std::string m_manufacturer; + std::string m_modelName; + + int m_rdreqcnt; // Slice size to use when reading + +public: + /** + * Construct by copying data from device and service objects. + * + * The discovery service does this: use getDirServices() + */ + ContentDirectoryService(const UPnPDevice &device, + const UPnPService &service); + + /** An empty one */ + ContentDirectoryService() = default; + + ~ContentDirectoryService(); + + /** Read a container's children list into dirbuf. + * + * @param objectId the UPnP object Id for the container. Root has Id "0" + * @param[out] dirbuf stores the entries we read. + */ + bool readDir(UpnpClient_Handle handle, + const char *objectId, UPnPDirContent &dirbuf, + Error &error) const; + + bool readDirSlice(UpnpClient_Handle handle, + const char *objectId, unsigned offset, + unsigned count, UPnPDirContent& dirbuf, + unsigned &didread, unsigned &total, + Error &error) const; + + /** Search the content directory service. + * + * @param objectId the UPnP object Id under which the search + * should be done. Not all servers actually support this below + * root. Root has Id "0" + * @param searchstring an UPnP searchcriteria string. Check the + * UPnP document: UPnP-av-ContentDirectory-v1-Service-20020625.pdf + * section 2.5.5. Maybe we'll provide an easier way some day... + * @param[out] dirbuf stores the entries we read. + */ + bool search(UpnpClient_Handle handle, + const char *objectId, const char *searchstring, + UPnPDirContent &dirbuf, + Error &error) const; + + /** Read metadata for a given node. + * + * @param objectId the UPnP object Id. Root has Id "0" + * @param[out] dirbuf stores the entries we read. At most one entry will be + * returned. + */ + bool getMetadata(UpnpClient_Handle handle, + const char *objectId, UPnPDirContent &dirbuf, + Error &error) const; + + /** Retrieve search capabilities + * + * @param[out] result an empty vector: no search, or a single '*' element: + * any tag can be used in a search, or a list of usable tag names. + */ + bool getSearchCapabilities(UpnpClient_Handle handle, + std::list<std::string> &result, + Error &error) const; + + gcc_pure + std::string GetURI() const { + return "upnp://" + m_deviceId + "/" + m_serviceType; + } + + /** Retrieve the "friendly name" for this server, useful for display. */ + const char *getFriendlyName() const { + return m_friendlyName.c_str(); + } +}; + +#endif /* _UPNPDIR_HXX_INCLUDED_ */ diff --git a/src/lib/upnp/Device.cxx b/src/lib/upnp/Device.cxx new file mode 100644 index 000000000..26bffd0f0 --- /dev/null +++ b/src/lib/upnp/Device.cxx @@ -0,0 +1,134 @@ +/* + * 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 "Device.hxx" +#include "Util.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "util/Error.hxx" + +#include <stdlib.h> + +#include <string.h> + +UPnPDevice::~UPnPDevice() +{ + /* this destructor exists here just so it won't get inlined */ +} + +/** + * An XML parser which constructs an UPnP device object from the + * device descriptor. + */ +class UPnPDeviceParser final : public CommonExpatParser { + UPnPDevice &m_device; + + std::string *value; + + UPnPService m_tservice; + +public: + UPnPDeviceParser(UPnPDevice& device) + :m_device(device), + value(nullptr) {} + +protected: + virtual void StartElement(const XML_Char *name, const XML_Char **) { + value = nullptr; + + switch (name[0]) { + case 'c': + if (strcmp(name, "controlURL") == 0) + value = &m_tservice.controlURL; + break; + case 'd': + if (strcmp(name, "deviceType") == 0) + value = &m_device.deviceType; + break; + case 'f': + if (strcmp(name, "friendlyName") == 0) + value = &m_device.friendlyName; + break; + case 'm': + if (strcmp(name, "manufacturer") == 0) + value = &m_device.manufacturer; + else if (strcmp(name, "modelName") == 0) + value = &m_device.modelName; + break; + case 's': + if (strcmp(name, "serviceType") == 0) + value = &m_tservice.serviceType; + break; + case 'U': + if (strcmp(name, "UDN") == 0) + value = &m_device.UDN; + else if (strcmp(name, "URLBase") == 0) + value = &m_device.URLBase; + break; + } + } + + virtual void EndElement(const XML_Char *name) { + if (value != nullptr) { + trimstring(*value); + value = nullptr; + } else if (!strcmp(name, "service")) { + m_device.services.emplace_back(std::move(m_tservice)); + m_tservice.clear(); + } + } + + virtual void CharacterData(const XML_Char *s, int len) { + if (value != nullptr) + value->append(s, len); + } +}; + +bool +UPnPDevice::Parse(const std::string &url, const char *description, + Error &error) +{ + { + UPnPDeviceParser mparser(*this); + if (!mparser.Parse(description, strlen(description), + true, error)) + return false; + } + + if (URLBase.empty()) { + // The standard says that if the URLBase value is empty, we should use + // the url the description was retrieved from. However this is + // sometimes something like http://host/desc.xml, sometimes something + // like http://host/ + + if (url.size() < 8) { + // ??? + URLBase = url; + } else { + auto hostslash = url.find_first_of("/", 7); + if (hostslash == std::string::npos || hostslash == url.size()-1) { + URLBase = url; + } else { + URLBase = path_getfather(url); + } + } + } + + return true; +} diff --git a/src/lib/upnp/Device.hxx b/src/lib/upnp/Device.hxx new file mode 100644 index 000000000..dd7ecac2d --- /dev/null +++ b/src/lib/upnp/Device.hxx @@ -0,0 +1,88 @@ +/* + * 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 _UPNPDEV_HXX_INCLUDED_ +#define _UPNPDEV_HXX_INCLUDED_ + +#include <vector> +#include <string> + +class Error; + +/** + * UPnP Description phase: interpreting the device description which we + * downloaded from the URL obtained by the discovery phase. + */ + +/** + * Data holder for a UPnP service, parsed from the XML description + * downloaded after discovery yielded its URL. + */ +struct UPnPService { + // e.g. urn:schemas-upnp-org:service:ConnectionManager:1 + std::string serviceType; + std::string controlURL; // e.g.: /upnp/control/cm + + void clear() + { + serviceType.clear(); + controlURL.clear(); + } +}; + +/** + * Data holder for a UPnP device, parsed from the XML description obtained + * during discovery. + * A device may include several services. To be of interest to us, + * one of them must be a ContentDirectory. + */ +class UPnPDevice { +public: + // e.g. urn:schemas-upnp-org:device:MediaServer:1 + std::string deviceType; + // e.g. MediaTomb + std::string friendlyName; + // Unique device number. This should match the deviceID in the + // discovery message. e.g. uuid:a7bdcd12-e6c1-4c7e-b588-3bbc959eda8d + std::string UDN; + // Base for all relative URLs. e.g. http://192.168.4.4:49152/ + std::string URLBase; + // Manufacturer: e.g. D-Link, PacketVideo ("manufacturer") + std::string manufacturer; + // Model name: e.g. MediaTomb, DNS-327L ("modelName") + std::string modelName; + // Services provided by this device. + std::vector<UPnPService> services; + + UPnPDevice() = default; + UPnPDevice(const UPnPDevice &) = delete; + UPnPDevice(UPnPDevice &&) = default; + UPnPDevice &operator=(UPnPDevice &&) = default; + + ~UPnPDevice(); + + /** Build device from xml description downloaded from discovery + * @param url where the description came from + * @param description the xml device description + */ + bool Parse(const std::string &url, const char *description, + Error &error); +}; + +#endif /* _UPNPDEV_HXX_INCLUDED_ */ diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx new file mode 100644 index 000000000..9ea78c624 --- /dev/null +++ b/src/lib/upnp/Discovery.cxx @@ -0,0 +1,339 @@ +/* + * 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 "Discovery.hxx" +#include "Domain.hxx" +#include "ContentDirectoryService.hxx" +#include "system/Clock.hxx" +#include "Log.hxx" + +#include <upnp/upnptools.h> + +#include <string.h> + +// The service type string we are looking for. +static constexpr char ContentDirectorySType[] = "urn:schemas-upnp-org:service:ContentDirectory:1"; + +// We don't include a version in comparisons, as we are satisfied with +// version 1 +gcc_pure +static bool +isCDService(const char *st) +{ + constexpr size_t sz = sizeof(ContentDirectorySType) - 3; + return memcmp(ContentDirectorySType, st, sz) == 0; +} + +// The type of device we're asking for in search +static constexpr char MediaServerDType[] = "urn:schemas-upnp-org:device:MediaServer:1"; + +gcc_pure +static bool +isMSDevice(const char *st) +{ + constexpr size_t sz = sizeof(MediaServerDType) - 3; + return memcmp(MediaServerDType, st, sz) == 0; +} + +static void +AnnounceFoundUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device) +{ + for (const auto &service : device.services) + if (isCDService(service.serviceType.c_str())) + listener.FoundUPnP(ContentDirectoryService(device, + service)); +} + +static void +AnnounceLostUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device) +{ + for (const auto &service : device.services) + if (isCDService(service.serviceType.c_str())) + listener.LostUPnP(ContentDirectoryService(device, + service)); +} + +inline void +UPnPDeviceDirectory::LockAdd(ContentDirectoryDescriptor &&d) +{ + const ScopeLock protect(mutex); + + for (auto &i : directories) { + if (i.id == d.id) { + i = std::move(d); + return; + } + } + + directories.emplace_back(std::move(d)); + + if (listener != nullptr) + AnnounceFoundUPnP(*listener, directories.back().device); +} + +inline void +UPnPDeviceDirectory::LockRemove(const std::string &id) +{ + const ScopeLock protect(mutex); + + for (auto i = directories.begin(), end = directories.end(); + i != end; ++i) { + if (i->id == id) { + if (listener != nullptr) + AnnounceLostUPnP(*listener, i->device); + + directories.erase(i); + break; + } + } +} + +inline void +UPnPDeviceDirectory::discoExplorer() +{ + for (;;) { + DiscoveredTask *tsk = 0; + if (!discoveredQueue.take(tsk)) { + discoveredQueue.workerExit(); + return; + } + + // Device signals its existence and well-being. Perform the + // UPnP "description" phase by downloading and decoding the + // description document. + char *buf; + // LINE_SIZE is defined by libupnp's upnp.h... + char contentType[LINE_SIZE]; + int code = UpnpDownloadUrlItem(tsk->url.c_str(), &buf, contentType); + if (code != UPNP_E_SUCCESS) { + continue; + } + + // Update or insert the device + ContentDirectoryDescriptor d(std::move(tsk->deviceId), + MonotonicClockS(), tsk->expires); + + { + Error error2; + bool success = d.Parse(tsk->url, buf, error2); + free(buf); + if (!success) { + delete tsk; + LogError(error2); + continue; + } + } + + LockAdd(std::move(d)); + delete tsk; + } +} + +void * +UPnPDeviceDirectory::discoExplorer(void *ctx) +{ + UPnPDeviceDirectory &directory = *(UPnPDeviceDirectory *)ctx; + directory.discoExplorer(); + return (void*)1; +} + +inline int +UPnPDeviceDirectory::OnAlive(Upnp_Discovery *disco) +{ + if (isMSDevice(disco->DeviceType) || + isCDService(disco->ServiceType)) { + DiscoveredTask *tp = new DiscoveredTask(disco); + if (discoveredQueue.put(tp)) + return UPNP_E_FINISH; + } + + return UPNP_E_SUCCESS; +} + +inline int +UPnPDeviceDirectory::OnByeBye(Upnp_Discovery *disco) +{ + if (isMSDevice(disco->DeviceType) || + isCDService(disco->ServiceType)) { + // Device signals it is going off. + LockRemove(disco->DeviceId); + } + + return UPNP_E_SUCCESS; +} + +// This gets called for all libupnp asynchronous events, in a libupnp +// thread context. +// Example: ContentDirectories appearing and disappearing from the network +// We queue a task for our worker thread(s) +int +UPnPDeviceDirectory::Invoke(Upnp_EventType et, void *evp) +{ + switch (et) { + case UPNP_DISCOVERY_SEARCH_RESULT: + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: + { + Upnp_Discovery *disco = (Upnp_Discovery *)evp; + return OnAlive(disco); + } + + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: + { + Upnp_Discovery *disco = (Upnp_Discovery *)evp; + return OnByeBye(disco); + } + + default: + // Ignore other events for now + break; + } + + return UPNP_E_SUCCESS; +} + +bool +UPnPDeviceDirectory::expireDevices(Error &error) +{ + const ScopeLock protect(mutex); + const unsigned now = MonotonicClockS(); + bool didsomething = false; + + for (auto it = directories.begin(); + it != directories.end();) { + if (now > it->expires) { + it = directories.erase(it); + didsomething = true; + } else { + it++; + } + } + + if (didsomething) + return search(error); + + return true; +} + +UPnPDeviceDirectory::UPnPDeviceDirectory(UpnpClient_Handle _handle, + UPnPDiscoveryListener *_listener) + :handle(_handle), + listener(_listener), + discoveredQueue("DiscoveredQueue"), + m_searchTimeout(2), m_lastSearch(0) +{ +} + +UPnPDeviceDirectory::~UPnPDeviceDirectory() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +UPnPDeviceDirectory::Start(Error &error) +{ + if (!discoveredQueue.start(1, discoExplorer, this)) { + error.Set(upnp_domain, "Discover work queue start failed"); + return false; + } + + return search(error); +} + +bool +UPnPDeviceDirectory::search(Error &error) +{ + const unsigned now = MonotonicClockS(); + if (now - m_lastSearch < 10) + return true; + m_lastSearch = now; + + // We search both for device and service just in case. + int code = UpnpSearchAsync(handle, m_searchTimeout, + ContentDirectorySType, GetUpnpCookie()); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSearchAsync() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + code = UpnpSearchAsync(handle, m_searchTimeout, + MediaServerDType, GetUpnpCookie()); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSearchAsync() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + return true; +} + +bool +UPnPDeviceDirectory::getDirServices(std::vector<ContentDirectoryService> &out, + Error &error) +{ + // Has locking, do it before our own lock + if (!expireDevices(error)) + return false; + + const ScopeLock protect(mutex); + + for (auto dit = directories.begin(); + dit != directories.end(); dit++) { + for (const auto &service : dit->device.services) { + if (isCDService(service.serviceType.c_str())) { + out.emplace_back(dit->device, service); + } + } + } + + return true; +} + +bool +UPnPDeviceDirectory::getServer(const char *friendlyName, + ContentDirectoryService &server, + Error &error) +{ + // Has locking, do it before our own lock + if (!expireDevices(error)) + return false; + + const ScopeLock protect(mutex); + + for (const auto &i : directories) { + const auto &device = i.device; + + if (device.friendlyName != friendlyName) + continue; + + for (const auto &service : device.services) { + if (isCDService(service.serviceType.c_str())) { + server = ContentDirectoryService(device, + service); + return true; + } + } + } + + error.Set(upnp_domain, "Server not found"); + return false; +} diff --git a/src/lib/upnp/Discovery.hxx b/src/lib/upnp/Discovery.hxx new file mode 100644 index 000000000..af07daf61 --- /dev/null +++ b/src/lib/upnp/Discovery.hxx @@ -0,0 +1,163 @@ +/* + * 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 _UPNPPDISC_H_X_INCLUDED_ +#define _UPNPPDISC_H_X_INCLUDED_ + +#include "Callback.hxx" +#include "Device.hxx" +#include "WorkQueue.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <upnp/upnp.h> + +#include <list> +#include <vector> +#include <string> + +class ContentDirectoryService; + +class UPnPDiscoveryListener { +public: + virtual void FoundUPnP(const ContentDirectoryService &service) = 0; + virtual void LostUPnP(const ContentDirectoryService &service) = 0; +}; + +/** + * Manage UPnP discovery and maintain a directory of active devices. Singleton. + * + * We are only interested in MediaServers with a ContentDirectory service + * for now, but this could be made more general, by removing the filtering. + */ +class UPnPDeviceDirectory final : UpnpCallback { + /** + * Each appropriate discovery event (executing in a libupnp thread + * context) queues the following task object for processing by the + * discovery thread. + */ + struct DiscoveredTask { + std::string url; + std::string deviceId; + unsigned expires; // Seconds valid + + DiscoveredTask(const Upnp_Discovery *disco) + :url(disco->Location), + deviceId(disco->DeviceId), + expires(disco->Expires) {} + }; + + /** + * Descriptor for one device having a Content Directory + * service found on the network. + */ + class ContentDirectoryDescriptor { + public: + std::string id; + + UPnPDevice device; + + /** + * The MonotonicClockS() time stamp when this device + * expires. + */ + unsigned expires; + + ContentDirectoryDescriptor() = default; + + ContentDirectoryDescriptor(std::string &&_id, + unsigned last, int exp) + :id(std::move(_id)), expires(last + exp + 20) {} + + bool Parse(const std::string &url, const char *description, + Error &_error) { + return device.Parse(url, description, _error); + } + }; + + const UpnpClient_Handle handle; + UPnPDiscoveryListener *const listener; + + Mutex mutex; + std::list<ContentDirectoryDescriptor> directories; + WorkQueue<DiscoveredTask *> discoveredQueue; + + /** + * The UPnP device search timeout, which should actually be + * called delay because it's the base of a random delay that + * the devices apply to avoid responding all at the same time. + */ + int m_searchTimeout; + + /** + * The MonotonicClockS() time stamp of the last search. + */ + unsigned m_lastSearch; + +public: + UPnPDeviceDirectory(UpnpClient_Handle _handle, + UPnPDiscoveryListener *_listener=nullptr); + ~UPnPDeviceDirectory(); + + UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete; + UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete; + + bool Start(Error &error); + + /** Retrieve the directory services currently seen on the network */ + bool getDirServices(std::vector<ContentDirectoryService> &, Error &); + + /** + * Get server by friendly name. + */ + bool getServer(const char *friendlyName, + ContentDirectoryService &server, + Error &error); + +private: + bool search(Error &error); + + /** + * Look at the devices and get rid of those which have not + * been seen for too long. We do this when listing the top + * directory. + */ + bool expireDevices(Error &error); + + void LockAdd(ContentDirectoryDescriptor &&d); + void LockRemove(const std::string &id); + + /** + * Worker routine for the discovery queue. Get messages about + * devices appearing and disappearing, and update the + * directory pool accordingly. + */ + static void *discoExplorer(void *); + void discoExplorer(); + + int OnAlive(Upnp_Discovery *disco); + int OnByeBye(Upnp_Discovery *disco); + int cluCallBack(Upnp_EventType et, void *evp); + + /* virtual methods from class UpnpCallback */ + virtual int Invoke(Upnp_EventType et, void *evp) override; +}; + + +#endif /* _UPNPPDISC_H_X_INCLUDED_ */ diff --git a/src/lib/upnp/Domain.cxx b/src/lib/upnp/Domain.cxx new file mode 100644 index 000000000..010d4c7c2 --- /dev/null +++ b/src/lib/upnp/Domain.cxx @@ -0,0 +1,23 @@ +/* + * 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 "Domain.hxx" +#include "util/Domain.hxx" + +const Domain upnp_domain("upnp"); diff --git a/src/lib/upnp/Domain.hxx b/src/lib/upnp/Domain.hxx new file mode 100644 index 000000000..ec01ef735 --- /dev/null +++ b/src/lib/upnp/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_DOMAIN_HXX +#define MPD_UPNP_DOMAIN_HXX + +class Domain; + +extern const Domain upnp_domain; + +#endif diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx new file mode 100644 index 000000000..4fc606de9 --- /dev/null +++ b/src/lib/upnp/Init.cxx @@ -0,0 +1,73 @@ +/* + * 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 "Domain.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <upnp/upnp.h> +#include <upnp/upnptools.h> +#include <upnp/ixml.h> + +static Mutex upnp_init_mutex; +static unsigned upnp_ref; + +static bool +DoInit(Error &error) +{ + auto code = UpnpInit(0, 0); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpInit() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + UpnpSetMaxContentLength(2000*1024); + + // Servers sometimes make error (e.g.: minidlna returns bad utf-8) + ixmlRelaxParser(1); + + return true; +} + +bool +UpnpGlobalInit(Error &error) +{ + const ScopeLock protect(upnp_init_mutex); + + if (upnp_ref == 0 && !DoInit(error)) + return false; + + ++upnp_ref; + return true; +} + +void +UpnpGlobalFinish() +{ + const ScopeLock protect(upnp_init_mutex); + + assert(upnp_ref > 0); + + if (--upnp_ref == 0) + UpnpFinish(); +} diff --git a/src/lib/upnp/Init.hxx b/src/lib/upnp/Init.hxx new file mode 100644 index 000000000..b23f8e2ab --- /dev/null +++ b/src/lib/upnp/Init.hxx @@ -0,0 +1,33 @@ +/* + * 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_UPNP_INIT_HXX +#define MPD_UPNP_INIT_HXX + +#include "check.h" + +class Error; + +bool +UpnpGlobalInit(Error &error); + +void +UpnpGlobalFinish(); + +#endif diff --git a/src/lib/upnp/Util.cxx b/src/lib/upnp/Util.cxx new file mode 100644 index 000000000..cf34a47d3 --- /dev/null +++ b/src/lib/upnp/Util.cxx @@ -0,0 +1,166 @@ +/* + * 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 "Util.hxx" + +#include <upnp/ixml.h> + +#include <assert.h> + +/** Get rid of white space at both ends */ +void +trimstring(std::string &s, const char *ws) +{ + auto pos = s.find_first_not_of(ws); + if (pos == std::string::npos) { + s.clear(); + return; + } + s.replace(0, pos, std::string()); + + pos = s.find_last_not_of(ws); + if (pos != std::string::npos && pos != s.length()-1) + s.replace(pos + 1, std::string::npos, std::string()); +} + +std::string +caturl(const std::string &s1, const std::string &s2) +{ + if (s2.front() == '/') { + /* absolute path: replace the whole URI path in s1 */ + + auto i = s1.find("://"); + if (i == s1.npos) + /* no scheme: override s1 completely */ + return s2; + + /* find the first slash after the host part */ + i = s1.find('/', i + 3); + if (i == s1.npos) + /* there's no URI path - simply append s2 */ + i = s1.length(); + + return s1.substr(0, i) + s2; + } + + std::string out(s1); + if (out.back() != '/') + out.push_back('/'); + + out += s2; + return out; +} + +static void +path_catslash(std::string &s) +{ + if (s.empty() || s.back() != '/') + s += '/'; +} + +std::string +path_getfather(const std::string &s) +{ + std::string father = s; + + // ?? + if (father.empty()) + return "./"; + + if (father.back() == '/') { + // Input ends with /. Strip it, handle special case for root + if (father.length() == 1) + return father; + father.erase(father.length()-1); + } + + auto slp = father.rfind('/'); + if (slp == std::string::npos) + return "./"; + + father.erase(slp); + path_catslash(father); + return father; +} + +std::list<std::string> +stringToTokens(const std::string &str, + const char *delims, bool skipinit) +{ + std::list<std::string> tokens; + + std::string::size_type startPos = 0; + + // Skip initial delims, return empty if this eats all. + if (skipinit && + (startPos = str.find_first_not_of(delims, 0)) == std::string::npos) + return tokens; + + while (startPos < str.size()) { + // Find next delimiter or end of string (end of token) + auto pos = str.find_first_of(delims, startPos); + + // Add token to the vector and adjust start + if (pos == std::string::npos) { + tokens.emplace_back(str, startPos); + break; + } else if (pos == startPos) { + // Dont' push empty tokens after first + if (tokens.empty()) + tokens.emplace_back(); + startPos = ++pos; + } else { + tokens.emplace_back(str, startPos, pos - startPos); + startPos = ++pos; + } + } + + return tokens; +} + +template <class T> +bool +csvToStrings(const char *s, T &tokens) +{ + assert(tokens.empty()); + + std::string current; + + while (true) { + char ch = *s++; + if (ch == 0) { + tokens.emplace_back(std::move(current)); + return true; + } + + if (ch == '\\') { + ch = *s++; + if (ch == 0) + return false; + } else if (ch == ',') { + tokens.emplace_back(std::move(current)); + current.clear(); + continue; + } + + current.push_back(ch); + } +} + +template bool csvToStrings<std::list<std::string>>(const char *, std::list<std::string> &); diff --git a/src/lib/upnp/Util.hxx b/src/lib/upnp/Util.hxx new file mode 100644 index 000000000..58e382faa --- /dev/null +++ b/src/lib/upnp/Util.hxx @@ -0,0 +1,46 @@ +/* + * 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_UPNP_UTIL_HXX +#define MPD_UPNP_UTIL_HXX + +#include "Compiler.h" + +#include <string> +#include <list> + +std::string +caturl(const std::string& s1, const std::string& s2); + +void +trimstring(std::string &s, const char *ws = " \t\n"); + +std::string +path_getfather(const std::string &s); + +gcc_pure +std::list<std::string> +stringToTokens(const std::string &str, + const char *delims = "/", bool skipinit = true); + +template <class T> +bool +csvToStrings(const char *s, T &tokens); + +#endif /* _UPNPP_H_X_INCLUDED_ */ diff --git a/src/lib/upnp/WorkQueue.hxx b/src/lib/upnp/WorkQueue.hxx new file mode 100644 index 000000000..fe8ce53f9 --- /dev/null +++ b/src/lib/upnp/WorkQueue.hxx @@ -0,0 +1,206 @@ +/* + * 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 _WORKQUEUE_H_INCLUDED_ +#define _WORKQUEUE_H_INCLUDED_ + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <assert.h> +#include <pthread.h> + +#include <string> +#include <queue> + +#define LOGINFO(X) +#define LOGERR(X) + +/** + * A WorkQueue manages the synchronisation around a queue of work items, + * where a number of client threads queue tasks and a number of worker + * threads take and execute them. The goal is to introduce some level + * of parallelism between the successive steps of a previously single + * threaded pipeline. For example data extraction / data preparation / index + * update, but this could have other uses. + * + * There is no individual task status return. In case of fatal error, + * the client or worker sets an end condition on the queue. A second + * queue could conceivably be used for returning individual task + * status. + */ +template <class T> +class WorkQueue { + // Configuration + const std::string name; + + // Status + // Worker threads having called exit + unsigned n_workers_exited; + bool ok; + + unsigned n_threads; + pthread_t *threads; + + // Synchronization + std::queue<T> queue; + Cond client_cond; + Cond worker_cond; + Mutex mutex; + +public: + /** Create a WorkQueue + * @param name for message printing + * @param hi number of tasks on queue before clients blocks. Default 0 + * meaning no limit. hi == -1 means that the queue is disabled. + * @param lo minimum count of tasks before worker starts. Default 1. + */ + WorkQueue(const char *_name) + :name(_name), + n_workers_exited(0), + ok(false), + n_threads(0), threads(nullptr) + { + } + + ~WorkQueue() { + setTerminateAndWait(); + } + + /** Start the worker threads. + * + * @param nworkers number of threads copies to start. + * @param start_routine thread function. It should loop + * taking (QueueWorker::take()) and executing tasks. + * @param arg initial parameter to thread function. + * @return true if ok. + */ + bool start(unsigned nworkers, void *(*workproc)(void *), void *arg) + { + const ScopeLock protect(mutex); + + assert(nworkers > 0); + assert(!ok); + assert(n_threads == 0); + assert(threads == nullptr); + + n_threads = nworkers; + threads = new pthread_t[n_threads]; + + for (unsigned i = 0; i < nworkers; i++) { + int err; + if ((err = pthread_create(&threads[i], 0, workproc, arg))) { + LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n", + name.c_str(), err)); + return false; + } + } + + ok = true; + return true; + } + + /** Add item to work queue, called from client. + * + * Sleeps if there are already too many. + */ + template<typename U> + bool put(U &&u) + { + const ScopeLock protect(mutex); + + queue.emplace(std::forward<U>(u)); + + // Just wake one worker, there is only one new task. + worker_cond.signal(); + + return true; + } + + + /** Tell the workers to exit, and wait for them. + */ + void setTerminateAndWait() + { + const ScopeLock protect(mutex); + + // Wait for all worker threads to have called workerExit() + ok = false; + while (n_workers_exited < n_threads) { + worker_cond.broadcast(); + client_cond.wait(mutex); + } + + // Perform the thread joins and compute overall status + // Workers return (void*)1 if ok + for (unsigned i = 0; i < n_threads; ++i) { + void *status; + pthread_join(threads[i], &status); + } + + delete[] threads; + threads = nullptr; + n_threads = 0; + + // Reset to start state. + n_workers_exited = 0; + } + + /** Take task from queue. Called from worker. + * + * Sleeps if there are not enough. Signal if we go to sleep on empty + * queue: client may be waiting for our going idle. + */ + bool take(T &tp) + { + const ScopeLock protect(mutex); + + if (!ok) + return false; + + while (queue.empty()) { + worker_cond.wait(mutex); + if (!ok) + return false; + } + + tp = std::move(queue.front()); + queue.pop(); + return true; + } + + /** Advertise exit and abort queue. Called from worker + * + * This would happen after an unrecoverable error, or when + * the queue is terminated by the client. Workers never exit normally, + * except when the queue is shut down (at which point ok is set to + * false by the shutdown code anyway). The thread must return/exit + * immediately after calling this. + */ + void workerExit() + { + const ScopeLock protect(mutex); + + n_workers_exited++; + ok = false; + client_cond.broadcast(); + } +}; + +#endif /* _WORKQUEUE_H_INCLUDED_ */ diff --git a/src/lib/upnp/ixmlwrap.cxx b/src/lib/upnp/ixmlwrap.cxx new file mode 100644 index 000000000..6a2829cf9 --- /dev/null +++ b/src/lib/upnp/ixmlwrap.cxx @@ -0,0 +1,44 @@ +/* Copyright (C) 2013 J.F.Dockes + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "ixmlwrap.hxx" + +namespace ixmlwrap { + +const char * +getFirstElementValue(IXML_Document *doc, const char *name) +{ + const char *ret = nullptr; + IXML_NodeList *nodes = + ixmlDocument_getElementsByTagName(doc, name); + + if (nodes) { + IXML_Node *first = ixmlNodeList_item(nodes, 0); + if (first) { + IXML_Node *dnode = ixmlNode_getFirstChild(first); + if (dnode) { + ret = ixmlNode_getNodeValue(dnode); + } + } + + ixmlNodeList_free(nodes); + } + + return ret; +} + +} diff --git a/src/lib/upnp/ixmlwrap.hxx b/src/lib/upnp/ixmlwrap.hxx new file mode 100644 index 000000000..0d519a323 --- /dev/null +++ b/src/lib/upnp/ixmlwrap.hxx @@ -0,0 +1,35 @@ +/* Copyright (C) 2013 J.F.Dockes + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _IXMLWRAP_H_INCLUDED_ +#define _IXMLWRAP_H_INCLUDED_ + +#include <upnp/ixml.h> + +#include <string> + +namespace ixmlwrap { + /** + * Retrieve the text content for the first element of given + * name. Returns nullptr if the element does not + * contain a text node + */ + const char *getFirstElementValue(IXML_Document *doc, + const char *name); + +}; + +#endif /* _IXMLWRAP_H_INCLUDED_ */ diff --git a/src/lib/zlib/Domain.cxx b/src/lib/zlib/Domain.cxx new file mode 100644 index 000000000..96aad1350 --- /dev/null +++ b/src/lib/zlib/Domain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain zlib_domain("zlib"); diff --git a/src/lib/zlib/Domain.hxx b/src/lib/zlib/Domain.hxx new file mode 100644 index 000000000..653ac0209 --- /dev/null +++ b/src/lib/zlib/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ZLIB_DOMAIN_HXX +#define MPD_ZLIB_DOMAIN_HXX + +class Domain; + +extern const Domain zlib_domain; + +#endif diff --git a/src/ls.cxx b/src/ls.cxx index b1a636416..96c9f60e5 100644 --- a/src/ls.cxx +++ b/src/ls.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,14 +19,11 @@ #include "config.h" #include "ls.hxx" +#include "util/StringUtil.hxx" #include "util/UriUtil.hxx" -#include "Client.hxx" - -#include <glib.h> +#include "client/Client.hxx" #include <assert.h> -#include <string.h> - /** * file:// is not included in remoteUrlPrefixes, the connection method @@ -52,12 +49,21 @@ static const char *remoteUrlPrefixes[] = { "rtmpt://", "rtmps://", #endif +#ifdef ENABLE_SMBCLIENT + "smb://", +#endif +#ifdef ENABLE_NFS + "nfs://", +#endif #ifdef ENABLE_CDIO_PARANOIA "cdda://", #endif #ifdef ENABLE_DESPOTIFY "spt://", #endif +#ifdef HAVE_ALSA + "alsa://", +#endif NULL }; @@ -92,7 +98,7 @@ bool uri_supported_scheme(const char *uri) assert(uri_has_scheme(uri)); while (*urlPrefixes) { - if (g_str_has_prefix(uri, *urlPrefixes)) + if (StringStartsWith(uri, *urlPrefixes)) return true; urlPrefixes++; } diff --git a/src/ls.hxx b/src/ls.hxx index 3879563ee..f4b9be967 100644 --- a/src/ls.hxx +++ b/src/ls.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 @@ -20,6 +20,8 @@ #ifndef MPD_LS_HXX #define MPD_LS_HXX +#include "Compiler.h" + #include <stdio.h> class Client; @@ -29,6 +31,7 @@ class Client; * It is not allowed to pass an URI without a scheme, check with * uri_has_scheme() first. */ +gcc_pure bool uri_supported_scheme(const char *url); /** diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx deleted file mode 100644 index 4a4ca433c..000000000 --- a/src/mixer/AlsaMixerPlugin.cxx +++ /dev/null @@ -1,404 +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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "GlobalEvents.hxx" -#include "Main.hxx" -#include "event/MultiSocketMonitor.hxx" -#include "event/Loop.hxx" -#include "event/Call.hxx" -#include "util/ASCII.hxx" -#include "util/ReusableArray.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <algorithm> - -#include <alsa/asoundlib.h> - -#define VOLUME_MIXER_ALSA_DEFAULT "default" -#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" -static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; - -class AlsaMixerMonitor final : private MultiSocketMonitor { - snd_mixer_t *mixer; - - ReusableArray<pollfd> pfd_buffer; - -public: - AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) - :MultiSocketMonitor(_loop), mixer(_mixer) { -#ifdef USE_EPOLL - _loop.AddCall([this](){ InvalidateSockets(); }); -#else - _loop.AddIdle(InitAlsaMixerMonitor, this); -#endif - } - -private: -#ifndef USE_EPOLL - static gboolean InitAlsaMixerMonitor(gpointer data) { - AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data; - amm.InvalidateSockets(); - return false; - } -#endif - - virtual int PrepareSockets() override; - virtual void DispatchSockets() override; -}; - -class AlsaMixer final : public Mixer { - const char *device; - const char *control; - unsigned int index; - - snd_mixer_t *handle; - snd_mixer_elem_t *elem; - long volume_min; - long volume_max; - int volume_set; - - AlsaMixerMonitor *monitor; - -public: - AlsaMixer():Mixer(alsa_mixer_plugin) {} - - void Configure(const config_param ¶m); - bool Setup(Error &error); - bool Open(Error &error); - void Close(); - - int GetVolume(Error &error); - bool SetVolume(unsigned volume, Error &error); -}; - -static constexpr Domain alsa_mixer_domain("alsa_mixer"); - -int -AlsaMixerMonitor::PrepareSockets() -{ - if (mixer == nullptr) - return -1; - - int count = snd_mixer_poll_descriptors_count(mixer); - if (count < 0) - count = 0; - - struct pollfd *pfds = pfd_buffer.Get(count); - - count = snd_mixer_poll_descriptors(mixer, pfds, count); - if (count < 0) - count = 0; - - struct pollfd *end = pfds + count; - - UpdateSocketList([pfds, end](int fd) -> unsigned { - auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){ - return pfd.fd == fd; - }); - if (i == end) - return 0; - - auto events = i->events; - i->events = 0; - return events; - }); - - for (auto i = pfds; i != end; ++i) - if (i->events != 0) - AddSocket(i->fd, i->events); - - return -1; -} - -void -AlsaMixerMonitor::DispatchSockets() -{ - assert(mixer != nullptr); - - int err = snd_mixer_handle_events(mixer); - if (err < 0) { - FormatError(alsa_mixer_domain, - "snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - - if (err == -ENODEV) { - /* the sound device was unplugged; disable - this GSource */ - mixer = nullptr; - InvalidateSockets(); - return; - } - } -} - -/* - * libasound callbacks - * - */ - -static int -alsa_mixer_elem_callback(gcc_unused snd_mixer_elem_t *elem, unsigned mask) -{ - if (mask & SND_CTL_EVENT_MASK_VALUE) - GlobalEvents::Emit(GlobalEvents::MIXER); - - return 0; -} - -/* - * mixer_plugin methods - * - */ - -inline void -AlsaMixer::Configure(const config_param ¶m) -{ - device = param.GetBlockValue("mixer_device", - VOLUME_MIXER_ALSA_DEFAULT); - control = param.GetBlockValue("mixer_control", - VOLUME_MIXER_ALSA_CONTROL_DEFAULT); - index = param.GetBlockValue("mixer_index", - VOLUME_MIXER_ALSA_INDEX_DEFAULT); -} - -static Mixer * -alsa_mixer_init(gcc_unused void *ao, const config_param ¶m, - gcc_unused Error &error) -{ - AlsaMixer *am = new AlsaMixer(); - am->Configure(param); - - return am; -} - -static void -alsa_mixer_finish(Mixer *data) -{ - AlsaMixer *am = (AlsaMixer *)data; - - delete am; - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -gcc_pure -static snd_mixer_elem_t * -alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) -{ - for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); - elem != nullptr; elem = snd_mixer_elem_next(elem)) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && - StringEqualsCaseASCII(snd_mixer_selem_get_name(elem), - name) && - snd_mixer_selem_get_index(elem) == idx) - return elem; - } - - return nullptr; -} - -inline bool -AlsaMixer::Setup(Error &error) -{ - int err; - - if ((err = snd_mixer_attach(handle, device)) < 0) { - error.Format(alsa_mixer_domain, err, - "failed to attach to %s: %s", - device, snd_strerror(err)); - return false; - } - - if ((err = snd_mixer_selem_register(handle, nullptr, - nullptr)) < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_selem_register() failed: %s", - snd_strerror(err)); - return false; - } - - if ((err = snd_mixer_load(handle)) < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_load() failed: %s\n", - snd_strerror(err)); - return false; - } - - elem = alsa_mixer_lookup_elem(handle, control, index); - if (elem == nullptr) { - error.Format(alsa_mixer_domain, 0, - "no such mixer control: %s", control); - return false; - } - - snd_mixer_selem_get_playback_volume_range(elem, &volume_min, - &volume_max); - - snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback); - - monitor = new AlsaMixerMonitor(*main_loop, handle); - - return true; -} - -inline bool -AlsaMixer::Open(Error &error) -{ - int err; - - volume_set = -1; - - err = snd_mixer_open(&handle, 0); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_open() failed: %s", snd_strerror(err)); - return false; - } - - if (!Setup(error)) { - snd_mixer_close(handle); - return false; - } - - return true; -} - -static bool -alsa_mixer_open(Mixer *data, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)data; - - return am->Open(error); -} - -inline void -AlsaMixer::Close() -{ - assert(handle != nullptr); - - delete monitor; - - snd_mixer_elem_set_callback(elem, nullptr); - snd_mixer_close(handle); -} - -static void -alsa_mixer_close(Mixer *data) -{ - AlsaMixer *am = (AlsaMixer *)data; - am->Close(); -} - -inline int -AlsaMixer::GetVolume(Error &error) -{ - int err; - int ret; - long level; - - assert(handle != nullptr); - - err = snd_mixer_handle_events(handle); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "snd_mixer_handle_events() failed: %s", - snd_strerror(err)); - return false; - } - - err = snd_mixer_selem_get_playback_volume(elem, - SND_MIXER_SCHN_FRONT_LEFT, - &level); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "failed to read ALSA volume: %s", - snd_strerror(err)); - return false; - } - - ret = ((volume_set / 100.0) * (volume_max - volume_min) - + volume_min) + 0.5; - if (volume_set > 0 && ret == level) { - ret = volume_set; - } else { - ret = (int)(100 * (((float)(level - volume_min)) / - (volume_max - volume_min)) + 0.5); - } - - return ret; -} - -static int -alsa_mixer_get_volume(Mixer *mixer, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)mixer; - return am->GetVolume(error); -} - -inline bool -AlsaMixer::SetVolume(unsigned volume, Error &error) -{ - float vol; - long level; - int err; - - assert(handle != nullptr); - - vol = volume; - - volume_set = vol + 0.5; - - level = (long)(((vol / 100.0) * (volume_max - volume_min) + - volume_min) + 0.5); - level = level > volume_max ? volume_max : level; - level = level < volume_min ? volume_min : level; - - err = snd_mixer_selem_set_playback_volume_all(elem, level); - if (err < 0) { - error.Format(alsa_mixer_domain, err, - "failed to set ALSA volume: %s", - snd_strerror(err)); - return false; - } - - return true; -} - -static bool -alsa_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - AlsaMixer *am = (AlsaMixer *)mixer; - return am->SetVolume(volume, error); -} - -const struct mixer_plugin alsa_mixer_plugin = { - alsa_mixer_init, - alsa_mixer_finish, - alsa_mixer_open, - alsa_mixer_close, - alsa_mixer_get_volume, - alsa_mixer_set_volume, - true, -}; diff --git a/src/mixer/Listener.hxx b/src/mixer/Listener.hxx new file mode 100644 index 000000000..6f48fbd4d --- /dev/null +++ b/src/mixer/Listener.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MIXER_LISTENER_HXX +#define MPD_MIXER_LISTENER_HXX + +class Mixer; + +/** + * An interface that listens on events from mixer plugins. The + * methods must be thread-safe and non-blocking. + */ +class MixerListener { +public: + virtual void OnMixerVolumeChanged(Mixer &mixer, int volume) = 0; +}; + +#endif diff --git a/src/mixer/MixerAll.cxx b/src/mixer/MixerAll.cxx new file mode 100644 index 000000000..5fef6a92f --- /dev/null +++ b/src/mixer/MixerAll.cxx @@ -0,0 +1,155 @@ +/* + * 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 "output/MultipleOutputs.hxx" +#include "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "MixerList.hxx" +#include "output/Internal.hxx" +#include "pcm/Volume.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +static constexpr Domain mixer_domain("mixer"); + +static int +output_mixer_get_volume(const AudioOutput &ao) +{ + if (!ao.enabled) + return -1; + + Mixer *mixer = ao.mixer; + if (mixer == nullptr) + return -1; + + Error error; + int volume = mixer_get_volume(mixer, error); + if (volume < 0 && error.IsDefined()) + FormatError(error, + "Failed to read mixer for '%s'", + ao.name); + + return volume; +} + +int +MultipleOutputs::GetVolume() const +{ + unsigned ok = 0; + int total = 0; + + for (auto ao : outputs) { + int volume = output_mixer_get_volume(*ao); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +static bool +output_mixer_set_volume(AudioOutput &ao, unsigned volume) +{ + assert(volume <= 100); + + if (!ao.enabled) + return false; + + Mixer *mixer = ao.mixer; + if (mixer == nullptr) + return false; + + Error error; + bool success = mixer_set_volume(mixer, volume, error); + if (!success && error.IsDefined()) + FormatError(error, + "Failed to set mixer for '%s'", + ao.name); + + return success; +} + +bool +MultipleOutputs::SetVolume(unsigned volume) +{ + assert(volume <= 100); + + bool success = false; + for (auto ao : outputs) + success = output_mixer_set_volume(*ao, volume) + || success; + + return success; +} + +static int +output_mixer_get_software_volume(const AudioOutput &ao) +{ + if (!ao.enabled) + return -1; + + Mixer *mixer = ao.mixer; + if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin)) + return -1; + + return mixer_get_volume(mixer, IgnoreError()); +} + +int +MultipleOutputs::GetSoftwareVolume() const +{ + unsigned ok = 0; + int total = 0; + + for (auto ao : outputs) { + int volume = output_mixer_get_software_volume(*ao); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +void +MultipleOutputs::SetSoftwareVolume(unsigned volume) +{ + assert(volume <= PCM_VOLUME_1); + + for (auto ao : outputs) { + const auto mixer = ao->mixer; + + if (mixer != nullptr && + &mixer->plugin == &software_mixer_plugin) + mixer_set_volume(mixer, volume, IgnoreError()); + } +} diff --git a/src/mixer/MixerControl.cxx b/src/mixer/MixerControl.cxx new file mode 100644 index 000000000..6d08140db --- /dev/null +++ b/src/mixer/MixerControl.cxx @@ -0,0 +1,147 @@ +/* + * 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 "MixerControl.hxx" +#include "MixerInternal.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +Mixer * +mixer_new(EventLoop &event_loop, + const MixerPlugin &plugin, AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error) +{ + Mixer *mixer = plugin.init(event_loop, ao, listener, param, error); + + assert(mixer == nullptr || mixer->IsPlugin(plugin)); + + return mixer; +} + +void +mixer_free(Mixer *mixer) +{ + assert(mixer != nullptr); + + /* mixers with the "global" flag set might still be open at + this point (see mixer_auto_close()) */ + mixer_close(mixer); + + delete mixer; +} + +bool +mixer_open(Mixer *mixer, Error &error) +{ + bool success; + + assert(mixer != nullptr); + + const ScopeLock protect(mixer->mutex); + + success = mixer->open || (mixer->open = mixer->Open(error)); + + mixer->failed = !success; + + return success; +} + +static void +mixer_close_internal(Mixer *mixer) +{ + assert(mixer != nullptr); + assert(mixer->open); + + mixer->Close(); + mixer->open = false; +} + +void +mixer_close(Mixer *mixer) +{ + assert(mixer != nullptr); + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) + mixer_close_internal(mixer); +} + +void +mixer_auto_close(Mixer *mixer) +{ + if (!mixer->plugin.global) + mixer_close(mixer); +} + +/* + * Close the mixer due to failure. The mutex must be locked before + * calling this function. + */ +static void +mixer_failed(Mixer *mixer) +{ + assert(mixer->open); + + mixer_close_internal(mixer); + + mixer->failed = true; +} + +int +mixer_get_volume(Mixer *mixer, Error &error) +{ + int volume; + + assert(mixer != nullptr); + + if (mixer->plugin.global && !mixer->failed && + !mixer_open(mixer, error)) + return -1; + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) { + volume = mixer->GetVolume(error); + if (volume < 0 && error.IsDefined()) + mixer_failed(mixer); + } else + volume = -1; + + return volume; +} + +bool +mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) +{ + assert(mixer != nullptr); + assert(volume <= 100); + + if (mixer->plugin.global && !mixer->failed && + !mixer_open(mixer, error)) + return false; + + const ScopeLock protect(mixer->mutex); + + return mixer->open && mixer->SetVolume(volume, error); +} diff --git a/src/mixer/MixerControl.hxx b/src/mixer/MixerControl.hxx new file mode 100644 index 000000000..75255d98c --- /dev/null +++ b/src/mixer/MixerControl.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Functions which manipulate a #mixer object. + */ + +#ifndef MPD_MIXER_CONTROL_HXX +#define MPD_MIXER_CONTROL_HXX + +class Error; +class Mixer; +class EventLoop; +struct AudioOutput; +struct MixerPlugin; +class MixerListener; +struct config_param; + +Mixer * +mixer_new(EventLoop &event_loop, const MixerPlugin &plugin, AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error); + +void +mixer_free(Mixer *mixer); + +bool +mixer_open(Mixer *mixer, Error &error); + +void +mixer_close(Mixer *mixer); + +/** + * Close the mixer unless the plugin's "global" flag is set. This is + * called when the #AudioOutput is closed. + */ +void +mixer_auto_close(Mixer *mixer); + +int +mixer_get_volume(Mixer *mixer, Error &error); + +bool +mixer_set_volume(Mixer *mixer, unsigned volume, Error &error); + +#endif diff --git a/src/mixer/MixerInternal.hxx b/src/mixer/MixerInternal.hxx new file mode 100644 index 000000000..7b2cf2b32 --- /dev/null +++ b/src/mixer/MixerInternal.hxx @@ -0,0 +1,97 @@ +/* + * 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_MIXER_INTERNAL_HXX +#define MPD_MIXER_INTERNAL_HXX + +#include "MixerPlugin.hxx" +#include "MixerList.hxx" +#include "thread/Mutex.hxx" +#include "Compiler.h" + +class MixerListener; + +class Mixer { +public: + const MixerPlugin &plugin; + + MixerListener &listener; + + /** + * This mutex protects all of the mixer struct, including its + * implementation, so plugins don't have to deal with that. + */ + Mutex mutex; + + /** + * Is the mixer device currently open? + */ + bool open; + + /** + * Has this mixer failed, and should not be reopened + * automatically? + */ + bool failed; + +public: + explicit Mixer(const MixerPlugin &_plugin, MixerListener &_listener) + :plugin(_plugin), listener(_listener), + open(false), + failed(false) {} + + Mixer(const Mixer &) = delete; + + virtual ~Mixer() {} + + bool IsPlugin(const MixerPlugin &other) const { + return &plugin == &other; + } + + /** + * Open mixer device + * + * @return true on success, false on error + */ + virtual bool Open(Error &error) = 0; + + /** + * Close mixer device + */ + virtual void Close() = 0; + + /** + * Reads the current volume. + * + * @return the current volume (0..100 including) or -1 if + * unavailable or on error (error set, mixer will be closed) + */ + gcc_pure + virtual int GetVolume(Error &error) = 0; + + /** + * Sets the volume. + * + * @param volume the new volume (0..100 including) @return + * true on success, false on error + */ + virtual bool SetVolume(unsigned volume, Error &error) = 0; +}; + +#endif diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx new file mode 100644 index 000000000..e75b2e6ff --- /dev/null +++ b/src/mixer/MixerList.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header provides "extern" declarations for all mixer plugins. + */ + +#ifndef MPD_MIXER_LIST_HXX +#define MPD_MIXER_LIST_HXX + +struct MixerPlugin; + +extern const MixerPlugin software_mixer_plugin; +extern const MixerPlugin alsa_mixer_plugin; +extern const MixerPlugin oss_mixer_plugin; +extern const MixerPlugin roar_mixer_plugin; +extern const MixerPlugin pulse_mixer_plugin; +extern const MixerPlugin winmm_mixer_plugin; + +#endif diff --git a/src/mixer/MixerPlugin.hxx b/src/mixer/MixerPlugin.hxx new file mode 100644 index 000000000..02bae844e --- /dev/null +++ b/src/mixer/MixerPlugin.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the mixer_plugin class. It should not be + * included directly; use MixerInternal.hxx instead in mixer + * implementations. + */ + +#ifndef MPD_MIXER_PLUGIN_HXX +#define MPD_MIXER_PLUGIN_HXX + +struct config_param; +struct AudioOutput; +class Mixer; +class MixerListener; +class EventLoop; +class Error; + +struct MixerPlugin { + /** + * Alocates and configures a mixer device. + * + * @param ao the associated AudioOutput + * @param param the configuration section + * @param error_r location to store the error occurring, or + * nullptr to ignore errors + * @return a mixer object, or nullptr on error + */ + Mixer *(*init)(EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error); + + /** + * If true, then the mixer is automatically opened, even if + * its audio output is not open. If false, then the mixer is + * disabled as long as its audio output is closed. + */ + bool global; +}; + +#endif diff --git a/src/mixer/MixerType.cxx b/src/mixer/MixerType.cxx new file mode 100644 index 000000000..cd45db0d9 --- /dev/null +++ b/src/mixer/MixerType.cxx @@ -0,0 +1,39 @@ +/* + * 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 "MixerType.hxx" + +#include <assert.h> +#include <string.h> + +enum mixer_type +mixer_type_parse(const char *input) +{ + assert(input != NULL); + + if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0) + return MIXER_TYPE_NONE; + else if (strcmp(input, "hardware") == 0) + return MIXER_TYPE_HARDWARE; + else if (strcmp(input, "software") == 0) + return MIXER_TYPE_SOFTWARE; + else + return MIXER_TYPE_UNKNOWN; +} diff --git a/src/mixer/MixerType.hxx b/src/mixer/MixerType.hxx new file mode 100644 index 000000000..bfa2637b7 --- /dev/null +++ b/src/mixer/MixerType.hxx @@ -0,0 +1,47 @@ +/* + * 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_MIXER_TYPE_HXX +#define MPD_MIXER_TYPE_HXX + +enum mixer_type { + /** parser error */ + MIXER_TYPE_UNKNOWN, + + /** mixer disabled */ + MIXER_TYPE_NONE, + + /** software mixer with pcm_volume() */ + MIXER_TYPE_SOFTWARE, + + /** hardware mixer (output's plugin) */ + MIXER_TYPE_HARDWARE, +}; + +/** + * Parses a "mixer_type" setting from the configuration file. + * + * @param input the configured string value; must not be NULL + * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could + * not be parsed + */ +enum mixer_type +mixer_type_parse(const char *input); + +#endif diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx deleted file mode 100644 index 0a459bc97..000000000 --- a/src/mixer/OssMixerPlugin.cxx +++ /dev/null @@ -1,243 +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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "system/fd_util.h" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" - -class OssMixer : public Mixer { - const char *device; - const char *control; - - int device_fd; - int volume_control; - -public: - OssMixer():Mixer(oss_mixer_plugin) {} - - bool Configure(const config_param ¶m, Error &error); - bool Open(Error &error); - void Close(); - - int GetVolume(Error &error); - bool SetVolume(unsigned volume, Error &error); -}; - -static constexpr Domain oss_mixer_domain("oss_mixer"); - -static int -oss_find_mixer(const char *name) -{ - const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; - size_t name_length = strlen(name); - - for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - if (StringEqualsCaseASCII(name, labels[i], name_length) && - (labels[i][name_length] == 0 || - labels[i][name_length] == ' ')) - return i; - } - return -1; -} - -inline bool -OssMixer::Configure(const config_param ¶m, Error &error) -{ - device = param.GetBlockValue("mixer_device", VOLUME_MIXER_OSS_DEFAULT); - control = param.GetBlockValue("mixer_control"); - - if (control != NULL) { - volume_control = oss_find_mixer(control); - if (volume_control < 0) { - error.Format(oss_mixer_domain, 0, - "no such mixer control: %s", control); - return false; - } - } else - volume_control = SOUND_MIXER_PCM; - - return true; -} - -static Mixer * -oss_mixer_init(gcc_unused void *ao, const config_param ¶m, - Error &error) -{ - OssMixer *om = new OssMixer(); - - if (!om->Configure(param, error)) { - delete om; - return nullptr; - } - - return om; -} - -static void -oss_mixer_finish(Mixer *data) -{ - OssMixer *om = (OssMixer *) data; - - delete om; -} - -void -OssMixer::Close() -{ - assert(device_fd >= 0); - - close(device_fd); -} - -static void -oss_mixer_close(Mixer *data) -{ - OssMixer *om = (OssMixer *) data; - om->Close(); -} - -inline bool -OssMixer::Open(Error &error) -{ - device_fd = open_cloexec(device, O_RDONLY, 0); - if (device_fd < 0) { - error.FormatErrno("failed to open %s", device); - return false; - } - - if (control) { - int devmask = 0; - - if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - error.SetErrno("READ_DEVMASK failed"); - Close(); - return false; - } - - if (((1 << volume_control) & devmask) == 0) { - error.Format(oss_mixer_domain, 0, - "mixer control \"%s\" not usable", - control); - Close(); - return false; - } - } - - return true; -} - -static bool -oss_mixer_open(Mixer *data, Error &error) -{ - OssMixer *om = (OssMixer *) data; - - return om->Open(error); -} - -inline int -OssMixer::GetVolume(Error &error) -{ - int left, right, level; - int ret; - - assert(device_fd >= 0); - - ret = ioctl(device_fd, MIXER_READ(volume_control), &level); - if (ret < 0) { - error.SetErrno("failed to read OSS volume"); - return false; - } - - left = level & 0xff; - right = (level & 0xff00) >> 8; - - if (left != right) { - FormatWarning(oss_mixer_domain, - "volume for left and right is not the same, \"%i\" and " - "\"%i\"\n", left, right); - } - - return left; -} - -static int -oss_mixer_get_volume(Mixer *mixer, Error &error) -{ - OssMixer *om = (OssMixer *)mixer; - return om->GetVolume(error); -} - -inline bool -OssMixer::SetVolume(unsigned volume, Error &error) -{ - int level; - int ret; - - assert(device_fd >= 0); - assert(volume <= 100); - - level = (volume << 8) + volume; - - ret = ioctl(device_fd, MIXER_WRITE(volume_control), &level); - if (ret < 0) { - error.SetErrno("failed to set OSS volume"); - return false; - } - - return true; -} - -static bool -oss_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - OssMixer *om = (OssMixer *)mixer; - return om->SetVolume(volume, error); -} - -const struct mixer_plugin oss_mixer_plugin = { - oss_mixer_init, - oss_mixer_finish, - oss_mixer_open, - oss_mixer_close, - oss_mixer_get_volume, - oss_mixer_set_volume, - true, -}; diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx deleted file mode 100644 index ff10256cb..000000000 --- a/src/mixer/PulseMixerPlugin.cxx +++ /dev/null @@ -1,227 +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 "PulseMixerPlugin.hxx" -#include "MixerInternal.hxx" -#include "output/PulseOutputPlugin.hxx" -#include "GlobalEvents.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <pulse/thread-mainloop.h> -#include <pulse/context.h> -#include <pulse/introspect.h> -#include <pulse/stream.h> -#include <pulse/subscribe.h> -#include <pulse/error.h> - -#include <assert.h> -#include <string.h> - -struct PulseMixer final : public Mixer { - PulseOutput *output; - - bool online; - struct pa_cvolume volume; - - PulseMixer(PulseOutput *_output) - :Mixer(pulse_mixer_plugin), - output(_output), online(false) - { - } -}; - -static constexpr Domain pulse_mixer_domain("pulse_mixer"); - -static void -pulse_mixer_offline(PulseMixer *pm) -{ - if (!pm->online) - return; - - pm->online = false; - - GlobalEvents::Emit(GlobalEvents::MIXER); -} - -/** - * Callback invoked by pulse_mixer_update(). Receives the new mixer - * value. - */ -static void -pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - PulseMixer *pm = (PulseMixer *)userdata; - - if (eol) - return; - - if (i == nullptr) { - pulse_mixer_offline(pm); - return; - } - - pm->online = true; - pm->volume = i->volume; - - GlobalEvents::Emit(GlobalEvents::MIXER); -} - -static void -pulse_mixer_update(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream) -{ - pa_operation *o; - - assert(context != nullptr); - assert(stream != nullptr); - assert(pa_stream_get_state(stream) == PA_STREAM_READY); - - o = pa_context_get_sink_input_info(context, - pa_stream_get_index(stream), - pulse_mixer_volume_cb, pm); - if (o == nullptr) { - FormatError(pulse_mixer_domain, - "pa_context_get_sink_input_info() failed: %s", - pa_strerror(pa_context_errno(context))); - pulse_mixer_offline(pm); - return; - } - - pa_operation_unref(o); -} - -void -pulse_mixer_on_connect(gcc_unused PulseMixer *pm, - struct pa_context *context) -{ - pa_operation *o; - - assert(context != nullptr); - - o = pa_context_subscribe(context, - (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, - nullptr, nullptr); - if (o == nullptr) { - FormatError(pulse_mixer_domain, - "pa_context_subscribe() failed: %s", - pa_strerror(pa_context_errno(context))); - return; - } - - pa_operation_unref(o); -} - -void -pulse_mixer_on_disconnect(PulseMixer *pm) -{ - pulse_mixer_offline(pm); -} - -void -pulse_mixer_on_change(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream) -{ - pulse_mixer_update(pm, context, stream); -} - -static Mixer * -pulse_mixer_init(void *ao, gcc_unused const config_param ¶m, - Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - - if (ao == nullptr) { - error.Set(pulse_mixer_domain, - "The pulse mixer cannot work without the audio output"); - return nullptr; - } - - PulseMixer *pm = new PulseMixer(po); - - pulse_output_set_mixer(po, pm); - - return pm; -} - -static void -pulse_mixer_finish(Mixer *data) -{ - PulseMixer *pm = (PulseMixer *) data; - - pulse_output_clear_mixer(pm->output, pm); - - delete pm; -} - -static int -pulse_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) -{ - PulseMixer *pm = (PulseMixer *) mixer; - int ret; - - pulse_output_lock(pm->output); - - ret = pm->online - ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) - : -1; - - pulse_output_unlock(pm->output); - - return ret; -} - -static bool -pulse_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - PulseMixer *pm = (PulseMixer *) mixer; - struct pa_cvolume cvolume; - bool success; - - pulse_output_lock(pm->output); - - if (!pm->online) { - pulse_output_unlock(pm->output); - error.Set(pulse_mixer_domain, "disconnected"); - return false; - } - - pa_cvolume_set(&cvolume, pm->volume.channels, - (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); - success = pulse_output_set_volume(pm->output, &cvolume, error); - if (success) - pm->volume = cvolume; - - pulse_output_unlock(pm->output); - - return success; -} - -const struct mixer_plugin pulse_mixer_plugin = { - pulse_mixer_init, - pulse_mixer_finish, - nullptr, - nullptr, - pulse_mixer_get_volume, - pulse_mixer_set_volume, - false, -}; diff --git a/src/mixer/PulseMixerPlugin.hxx b/src/mixer/PulseMixerPlugin.hxx deleted file mode 100644 index fa73e0f5e..000000000 --- a/src/mixer/PulseMixerPlugin.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PULSE_MIXER_PLUGIN_HXX -#define MPD_PULSE_MIXER_PLUGIN_HXX - -#include <pulse/def.h> - -struct PulseMixer; -struct pa_context; -struct pa_stream; - -void -pulse_mixer_on_connect(PulseMixer *pm, struct pa_context *context); - -void -pulse_mixer_on_disconnect(PulseMixer *pm); - -void -pulse_mixer_on_change(PulseMixer *pm, - struct pa_context *context, struct pa_stream *stream); - -#endif diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx deleted file mode 100644 index 6bd700551..000000000 --- a/src/mixer/RoarMixerPlugin.cxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * 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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "output/RoarOutputPlugin.hxx" - -struct RoarMixer final : public Mixer { - /** the base mixer class */ - RoarOutput *self; - - RoarMixer(RoarOutput *_output) - :Mixer(roar_mixer_plugin), - self(_output) {} -}; - -static Mixer * -roar_mixer_init(void *ao, gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new RoarMixer((RoarOutput *)ao); -} - -static void -roar_mixer_finish(Mixer *data) -{ - RoarMixer *self = (RoarMixer *) data; - - delete self; -} - -static int -roar_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) -{ - RoarMixer *self = (RoarMixer *)mixer; - return roar_output_get_volume(self->self); -} - -static bool -roar_mixer_set_volume(Mixer *mixer, unsigned volume, - gcc_unused Error &error) -{ - RoarMixer *self = (RoarMixer *)mixer; - return roar_output_set_volume(self->self, volume); -} - -const struct mixer_plugin roar_mixer_plugin = { - roar_mixer_init, - roar_mixer_finish, - nullptr, - nullptr, - roar_mixer_get_volume, - roar_mixer_set_volume, - false, -}; diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx deleted file mode 100644 index 193e68f23..000000000 --- a/src/mixer/SoftwareMixerPlugin.cxx +++ /dev/null @@ -1,132 +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 "SoftwareMixerPlugin.hxx" -#include "MixerInternal.hxx" -#include "FilterPlugin.hxx" -#include "FilterRegistry.hxx" -#include "FilterInternal.hxx" -#include "filter/VolumeFilterPlugin.hxx" -#include "pcm/PcmVolume.hxx" -#include "ConfigData.hxx" -#include "util/Error.hxx" - -#include <assert.h> -#include <math.h> - -static Filter * -CreateVolumeFilter() -{ - Error error; - return filter_new(&volume_filter_plugin, config_param(), error); -} - -struct SoftwareMixer final : public Mixer { - Filter *filter; - - /** - * If this is true, then this object "owns" the #Filter - * instance (see above). It will be set to false by - * software_mixer_get_filter(); after that, the caller will be - * responsible for the #Filter. - */ - bool owns_filter; - - unsigned volume; - - SoftwareMixer() - :Mixer(software_mixer_plugin), - filter(CreateVolumeFilter()), - owns_filter(true), - volume(100) - { - assert(filter != nullptr); - } - - ~SoftwareMixer() { - if (owns_filter) - delete filter; - } -}; - -static Mixer * -software_mixer_init(gcc_unused void *ao, - gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - return new SoftwareMixer(); -} - -static void -software_mixer_finish(Mixer *data) -{ - SoftwareMixer *sm = (SoftwareMixer *)data; - - delete sm; -} - -static int -software_mixer_get_volume(Mixer *mixer, gcc_unused Error &error) -{ - SoftwareMixer *sm = (SoftwareMixer *)mixer; - - return sm->volume; -} - -static bool -software_mixer_set_volume(Mixer *mixer, unsigned volume, - gcc_unused Error &error) -{ - SoftwareMixer *sm = (SoftwareMixer *)mixer; - - assert(volume <= 100); - - sm->volume = volume; - - if (volume >= 100) - volume = PCM_VOLUME_1; - else if (volume > 0) - volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / - (54.5981500331F - 1)); - - volume_filter_set(sm->filter, volume); - return true; -} - -const struct mixer_plugin software_mixer_plugin = { - software_mixer_init, - software_mixer_finish, - nullptr, - nullptr, - software_mixer_get_volume, - software_mixer_set_volume, - true, -}; - -Filter * -software_mixer_get_filter(Mixer *mixer) -{ - SoftwareMixer *sm = (SoftwareMixer *)mixer; - assert(sm->IsPlugin(software_mixer_plugin)); - assert(sm->owns_filter); - - sm->owns_filter = false; - return sm->filter; -} diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx deleted file mode 100644 index be59c08db..000000000 --- a/src/mixer/SoftwareMixerPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SOFTWARE_MIXER_PLUGIN_HXX -#define MPD_SOFTWARE_MIXER_PLUGIN_HXX - -class Mixer; -class Filter; - -/** - * Returns the (volume) filter associated with this mixer. All users - * of this mixer plugin should install this filter. - */ -Filter * -software_mixer_get_filter(Mixer *mixer); - -#endif diff --git a/src/mixer/Volume.cxx b/src/mixer/Volume.cxx new file mode 100644 index 000000000..abb01fb40 --- /dev/null +++ b/src/mixer/Volume.cxx @@ -0,0 +1,124 @@ +/* + * 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 "Volume.hxx" +#include "output/MultipleOutputs.hxx" +#include "Idle.hxx" +#include "util/StringUtil.hxx" +#include "util/Domain.hxx" +#include "system/PeriodClock.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdlib.h> + +#define SW_VOLUME_STATE "sw_volume: " + +static constexpr Domain volume_domain("volume"); + +static unsigned volume_software_set = 100; + +/** the cached hardware mixer value; invalid if negative */ +static int last_hardware_volume = -1; +/** the age of #last_hardware_volume */ +static PeriodClock hardware_volume_clock; + +void +InvalidateHardwareVolume() +{ + /* flush the hardware volume cache */ + last_hardware_volume = -1; +} + +int +volume_level_get(const MultipleOutputs &outputs) +{ + if (last_hardware_volume >= 0 && + !hardware_volume_clock.CheckUpdate(1000)) + /* throttle access to hardware mixers */ + return last_hardware_volume; + + last_hardware_volume = outputs.GetVolume(); + return last_hardware_volume; +} + +static bool +software_volume_change(MultipleOutputs &outputs, unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + outputs.SetSoftwareVolume(volume); + + return true; +} + +static bool +hardware_volume_change(MultipleOutputs &outputs, unsigned volume) +{ + /* reset the cache */ + last_hardware_volume = -1; + + return outputs.SetVolume(volume); +} + +bool +volume_level_change(MultipleOutputs &outputs, unsigned volume) +{ + assert(volume <= 100); + + volume_software_set = volume; + + idle_add(IDLE_MIXER); + + return hardware_volume_change(outputs, volume); +} + +bool +read_sw_volume_state(const char *line, MultipleOutputs &outputs) +{ + char *end = nullptr; + long int sv; + + if (!StringStartsWith(line, SW_VOLUME_STATE)) + return false; + + line += sizeof(SW_VOLUME_STATE) - 1; + sv = strtol(line, &end, 10); + if (*end == 0 && sv >= 0 && sv <= 100) + software_volume_change(outputs, sv); + else + FormatWarning(volume_domain, + "Can't parse software volume: %s", line); + return true; +} + +void +save_sw_volume_state(BufferedOutputStream &os) +{ + os.Format(SW_VOLUME_STATE "%u\n", volume_software_set); +} + +unsigned +sw_volume_state_get_hash(void) +{ + return volume_software_set; +} diff --git a/src/mixer/Volume.hxx b/src/mixer/Volume.hxx new file mode 100644 index 000000000..d787a6415 --- /dev/null +++ b/src/mixer/Volume.hxx @@ -0,0 +1,54 @@ +/* + * 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_VOLUME_HXX +#define MPD_VOLUME_HXX + +#include "Compiler.h" + +class MultipleOutputs; +class BufferedOutputStream; + +void +InvalidateHardwareVolume(); + +gcc_pure +int +volume_level_get(const MultipleOutputs &outputs); + +bool +volume_level_change(MultipleOutputs &outputs, unsigned volume); + +bool +read_sw_volume_state(const char *line, MultipleOutputs &outputs); + +void +save_sw_volume_state(BufferedOutputStream &os); + +/** + * Generates a hash number for the current state of the software + * volume control. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +gcc_pure +unsigned +sw_volume_state_get_hash(void); + +#endif diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx deleted file mode 100644 index dbb43dce4..000000000 --- a/src/mixer/WinmmMixerPlugin.cxx +++ /dev/null @@ -1,114 +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 "MixerInternal.hxx" -#include "OutputAPI.hxx" -#include "output/WinmmOutputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <mmsystem.h> - -#include <assert.h> -#include <math.h> -#include <windows.h> - -struct WinmmMixer final : public Mixer { - WinmmOutput *output; - - WinmmMixer(WinmmOutput *_output) - :Mixer(winmm_mixer_plugin), - output(_output) { - } -}; - -static constexpr Domain winmm_mixer_domain("winmm_mixer"); - -static inline int -winmm_volume_decode(DWORD volume) -{ - return lround((volume & 0xFFFF) / 655.35); -} - -static inline DWORD -winmm_volume_encode(int volume) -{ - int value = lround(volume * 655.35); - return MAKELONG(value, value); -} - -static Mixer * -winmm_mixer_init(void *ao, gcc_unused const config_param ¶m, - gcc_unused Error &error) -{ - assert(ao != nullptr); - - return new WinmmMixer((WinmmOutput *)ao); -} - -static void -winmm_mixer_finish(Mixer *data) -{ - WinmmMixer *wm = (WinmmMixer *)data; - - delete wm; -} - -static int -winmm_mixer_get_volume(Mixer *mixer, Error &error) -{ - WinmmMixer *wm = (WinmmMixer *) mixer; - DWORD volume; - HWAVEOUT handle = winmm_output_get_handle(wm->output); - MMRESULT result = waveOutGetVolume(handle, &volume); - - if (result != MMSYSERR_NOERROR) { - error.Set(winmm_mixer_domain, "Failed to get winmm volume"); - return -1; - } - - return winmm_volume_decode(volume); -} - -static bool -winmm_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error) -{ - WinmmMixer *wm = (WinmmMixer *) mixer; - DWORD value = winmm_volume_encode(volume); - HWAVEOUT handle = winmm_output_get_handle(wm->output); - MMRESULT result = waveOutSetVolume(handle, value); - - if (result != MMSYSERR_NOERROR) { - error.Set(winmm_mixer_domain, "Failed to set winmm volume"); - return false; - } - - return true; -} - -const struct mixer_plugin winmm_mixer_plugin = { - winmm_mixer_init, - winmm_mixer_finish, - nullptr, - nullptr, - winmm_mixer_get_volume, - winmm_mixer_set_volume, - false, -}; diff --git a/src/mixer/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx new file mode 100644 index 000000000..cd787182a --- /dev/null +++ b/src/mixer/plugins/AlsaMixerPlugin.cxx @@ -0,0 +1,357 @@ +/* + * 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 "mixer/MixerInternal.hxx" +#include "mixer/Listener.hxx" +#include "output/OutputAPI.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "event/Loop.hxx" +#include "util/ASCII.hxx" +#include "util/ReusableArray.hxx" +#include "util/Clamp.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <algorithm> + +#include <alsa/asoundlib.h> + +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" +static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; + +class AlsaMixerMonitor final : MultiSocketMonitor, DeferredMonitor { + snd_mixer_t *mixer; + + ReusableArray<pollfd> pfd_buffer; + +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), DeferredMonitor(_loop), + mixer(_mixer) { + DeferredMonitor::Schedule(); + } + +private: + virtual void RunDeferred() override { + InvalidateSockets(); + } + + virtual int PrepareSockets() override; + virtual void DispatchSockets() override; +}; + +class AlsaMixer final : public Mixer { + EventLoop &event_loop; + + const char *device; + const char *control; + unsigned int index; + + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long volume_min; + long volume_max; + int volume_set; + + AlsaMixerMonitor *monitor; + +public: + AlsaMixer(EventLoop &_event_loop, MixerListener &_listener) + :Mixer(alsa_mixer_plugin, _listener), + event_loop(_event_loop) {} + + virtual ~AlsaMixer(); + + void Configure(const config_param ¶m); + bool Setup(Error &error); + + /* virtual methods from class Mixer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static constexpr Domain alsa_mixer_domain("alsa_mixer"); + +int +AlsaMixerMonitor::PrepareSockets() +{ + if (mixer == nullptr) { + ClearSocketList(); + return -1; + } + + int count = snd_mixer_poll_descriptors_count(mixer); + if (count < 0) + count = 0; + + struct pollfd *pfds = pfd_buffer.Get(count); + + count = snd_mixer_poll_descriptors(mixer, pfds, count); + if (count < 0) + count = 0; + + ReplaceSocketList(pfds, count); + return -1; +} + +void +AlsaMixerMonitor::DispatchSockets() +{ + assert(mixer != nullptr); + + int err = snd_mixer_handle_events(mixer); + if (err < 0) { + FormatError(alsa_mixer_domain, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + + if (err == -ENODEV) { + /* the sound device was unplugged; disable + this GSource */ + mixer = nullptr; + InvalidateSockets(); + return; + } + } +} + +/* + * libasound callbacks + * + */ + +static int +alsa_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned mask) +{ + AlsaMixer &mixer = *(AlsaMixer *) + snd_mixer_elem_get_callback_private(elem); + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + int volume = mixer.GetVolume(IgnoreError()); + mixer.listener.OnMixerVolumeChanged(mixer, volume); + } + + return 0; +} + +/* + * mixer_plugin methods + * + */ + +inline void +AlsaMixer::Configure(const config_param ¶m) +{ + device = param.GetBlockValue("mixer_device", + VOLUME_MIXER_ALSA_DEFAULT); + control = param.GetBlockValue("mixer_control", + VOLUME_MIXER_ALSA_CONTROL_DEFAULT); + index = param.GetBlockValue("mixer_index", + VOLUME_MIXER_ALSA_INDEX_DEFAULT); +} + +static Mixer * +alsa_mixer_init(EventLoop &event_loop, gcc_unused AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + gcc_unused Error &error) +{ + AlsaMixer *am = new AlsaMixer(event_loop, listener); + am->Configure(param); + + return am; +} + +AlsaMixer::~AlsaMixer() +{ + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +gcc_pure +static snd_mixer_elem_t * +alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) +{ + for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); + elem != nullptr; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && + StringEqualsCaseASCII(snd_mixer_selem_get_name(elem), + name) && + snd_mixer_selem_get_index(elem) == idx) + return elem; + } + + return nullptr; +} + +inline bool +AlsaMixer::Setup(Error &error) +{ + int err; + + if ((err = snd_mixer_attach(handle, device)) < 0) { + error.Format(alsa_mixer_domain, err, + "failed to attach to %s: %s", + device, snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_selem_register(handle, nullptr, + nullptr)) < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_selem_register() failed: %s", + snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_load(handle)) < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_load() failed: %s\n", + snd_strerror(err)); + return false; + } + + elem = alsa_mixer_lookup_elem(handle, control, index); + if (elem == nullptr) { + error.Format(alsa_mixer_domain, 0, + "no such mixer control: %s", control); + return false; + } + + snd_mixer_selem_get_playback_volume_range(elem, &volume_min, + &volume_max); + + snd_mixer_elem_set_callback_private(elem, this); + snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback); + + monitor = new AlsaMixerMonitor(event_loop, handle); + + return true; +} + +inline bool +AlsaMixer::Open(Error &error) +{ + int err; + + volume_set = -1; + + err = snd_mixer_open(&handle, 0); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_open() failed: %s", snd_strerror(err)); + return false; + } + + if (!Setup(error)) { + snd_mixer_close(handle); + return false; + } + + return true; +} + +inline void +AlsaMixer::Close() +{ + assert(handle != nullptr); + + delete monitor; + + snd_mixer_elem_set_callback(elem, nullptr); + snd_mixer_close(handle); +} + +inline int +AlsaMixer::GetVolume(Error &error) +{ + int err; + int ret; + long level; + + assert(handle != nullptr); + + err = snd_mixer_handle_events(handle); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); + return false; + } + + err = snd_mixer_selem_get_playback_volume(elem, + SND_MIXER_SCHN_FRONT_LEFT, + &level); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "failed to read ALSA volume: %s", + snd_strerror(err)); + return false; + } + + ret = ((volume_set / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5; + if (volume_set > 0 && ret == level) { + ret = volume_set; + } else { + ret = (int)(100 * (((float)(level - volume_min)) / + (volume_max - volume_min)) + 0.5); + } + + return ret; +} + +inline bool +AlsaMixer::SetVolume(unsigned volume, Error &error) +{ + float vol; + long level; + int err; + + assert(handle != nullptr); + + vol = volume; + + volume_set = vol + 0.5; + + level = (long)(((vol / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5); + level = Clamp(level, volume_min, volume_max); + + err = snd_mixer_selem_set_playback_volume_all(elem, level); + if (err < 0) { + error.Format(alsa_mixer_domain, err, + "failed to set ALSA volume: %s", + snd_strerror(err)); + return false; + } + + return true; +} + +const MixerPlugin alsa_mixer_plugin = { + alsa_mixer_init, + true, +}; diff --git a/src/mixer/plugins/OssMixerPlugin.cxx b/src/mixer/plugins/OssMixerPlugin.cxx new file mode 100644 index 000000000..6615c7022 --- /dev/null +++ b/src/mixer/plugins/OssMixerPlugin.cxx @@ -0,0 +1,203 @@ +/* + * 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 "mixer/MixerInternal.hxx" +#include "config/ConfigData.hxx" +#include "system/fd_util.h" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +class OssMixer final : public Mixer { + const char *device; + const char *control; + + int device_fd; + int volume_control; + +public: + OssMixer(MixerListener &_listener) + :Mixer(oss_mixer_plugin, _listener) {} + + bool Configure(const config_param ¶m, Error &error); + + /* virtual methods from class Mixer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static constexpr Domain oss_mixer_domain("oss_mixer"); + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (StringEqualsCaseASCII(name, labels[i], name_length) && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +inline bool +OssMixer::Configure(const config_param ¶m, Error &error) +{ + device = param.GetBlockValue("mixer_device", VOLUME_MIXER_OSS_DEFAULT); + control = param.GetBlockValue("mixer_control"); + + if (control != NULL) { + volume_control = oss_find_mixer(control); + if (volume_control < 0) { + error.Format(oss_mixer_domain, 0, + "no such mixer control: %s", control); + return false; + } + } else + volume_control = SOUND_MIXER_PCM; + + return true; +} + +static Mixer * +oss_mixer_init(gcc_unused EventLoop &event_loop, gcc_unused AudioOutput &ao, + MixerListener &listener, + const config_param ¶m, + Error &error) +{ + OssMixer *om = new OssMixer(listener); + + if (!om->Configure(param, error)) { + delete om; + return nullptr; + } + + return om; +} + +void +OssMixer::Close() +{ + assert(device_fd >= 0); + + close(device_fd); +} + +bool +OssMixer::Open(Error &error) +{ + device_fd = open_cloexec(device, O_RDONLY, 0); + if (device_fd < 0) { + error.FormatErrno("failed to open %s", device); + return false; + } + + if (control) { + int devmask = 0; + + if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + error.SetErrno("READ_DEVMASK failed"); + Close(); + return false; + } + + if (((1 << volume_control) & devmask) == 0) { + error.Format(oss_mixer_domain, 0, + "mixer control \"%s\" not usable", + control); + Close(); + return false; + } + } + + return true; +} + +int +OssMixer::GetVolume(Error &error) +{ + int left, right, level; + int ret; + + assert(device_fd >= 0); + + ret = ioctl(device_fd, MIXER_READ(volume_control), &level); + if (ret < 0) { + error.SetErrno("failed to read OSS volume"); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + FormatWarning(oss_mixer_domain, + "volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +bool +OssMixer::SetVolume(unsigned volume, Error &error) +{ + int level; + int ret; + + assert(device_fd >= 0); + assert(volume <= 100); + + level = (volume << 8) + volume; + + ret = ioctl(device_fd, MIXER_WRITE(volume_control), &level); + if (ret < 0) { + error.SetErrno("failed to set OSS volume"); + return false; + } + + return true; +} + +const MixerPlugin oss_mixer_plugin = { + oss_mixer_init, + true, +}; diff --git a/src/mixer/plugins/PulseMixerPlugin.cxx b/src/mixer/plugins/PulseMixerPlugin.cxx new file mode 100644 index 000000000..c5f20723b --- /dev/null +++ b/src/mixer/plugins/PulseMixerPlugin.cxx @@ -0,0 +1,233 @@ +/* + * 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 "PulseMixerPlugin.hxx" +#include "mixer/MixerInternal.hxx" +#include "mixer/Listener.hxx" +#include "output/plugins/PulseOutputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> + +class PulseMixer final : public Mixer { + PulseOutput &output; + + bool online; + struct pa_cvolume volume; + +public: + PulseMixer(PulseOutput &_output, MixerListener &_listener) + :Mixer(pulse_mixer_plugin, _listener), + output(_output), online(false) + { + } + + virtual ~PulseMixer(); + + void Offline(); + void VolumeCallback(const pa_sink_input_info *i, int eol); + void Update(pa_context *context, pa_stream *stream); + int GetVolumeInternal(Error &error); + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static constexpr Domain pulse_mixer_domain("pulse_mixer"); + +void +PulseMixer::Offline() +{ + if (!online) + return; + + online = false; + + listener.OnMixerVolumeChanged(*this, -1); +} + +inline void +PulseMixer::VolumeCallback(const pa_sink_input_info *i, int eol) +{ + if (eol) + return; + + if (i == nullptr) { + Offline(); + return; + } + + online = true; + volume = i->volume; + + listener.OnMixerVolumeChanged(*this, GetVolumeInternal(IgnoreError())); +} + +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ +static void +pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) +{ + PulseMixer *pm = (PulseMixer *)userdata; + pm->VolumeCallback(i, eol); +} + +inline void +PulseMixer::Update(pa_context *context, pa_stream *stream) +{ + assert(context != nullptr); + assert(stream != nullptr); + assert(pa_stream_get_state(stream) == PA_STREAM_READY); + + pa_operation *o = + pa_context_get_sink_input_info(context, + pa_stream_get_index(stream), + pulse_mixer_volume_cb, this); + if (o == nullptr) { + FormatError(pulse_mixer_domain, + "pa_context_get_sink_input_info() failed: %s", + pa_strerror(pa_context_errno(context))); + Offline(); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_connect(gcc_unused PulseMixer &pm, + struct pa_context *context) +{ + pa_operation *o; + + assert(context != nullptr); + + o = pa_context_subscribe(context, + (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, + nullptr, nullptr); + if (o == nullptr) { + FormatError(pulse_mixer_domain, + "pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_disconnect(PulseMixer &pm) +{ + pm.Offline(); +} + +void +pulse_mixer_on_change(PulseMixer &pm, + struct pa_context *context, struct pa_stream *stream) +{ + pm.Update(context, stream); +} + +static Mixer * +pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + PulseOutput &po = (PulseOutput &)ao; + PulseMixer *pm = new PulseMixer(po, listener); + + pulse_output_set_mixer(po, *pm); + + return pm; +} + +PulseMixer::~PulseMixer() +{ + pulse_output_clear_mixer(output, *this); +} + +int +PulseMixer::GetVolume(gcc_unused Error &error) +{ + pulse_output_lock(output); + + int result = GetVolumeInternal(error); + pulse_output_unlock(output); + + return result; +} + +/** + * Pulse mainloop lock must be held by caller + */ +int +PulseMixer::GetVolumeInternal(gcc_unused Error &error) +{ + return online ? + (int)((100 * (pa_cvolume_avg(&volume) + 1)) / PA_VOLUME_NORM) + : -1; +} + +bool +PulseMixer::SetVolume(unsigned new_volume, Error &error) +{ + pulse_output_lock(output); + + if (!online) { + pulse_output_unlock(output); + error.Set(pulse_mixer_domain, "disconnected"); + return false; + } + + struct pa_cvolume cvolume; + pa_cvolume_set(&cvolume, volume.channels, + (pa_volume_t)new_volume * PA_VOLUME_NORM / 100 + 0.5); + bool success = pulse_output_set_volume(output, &cvolume, error); + if (success) + volume = cvolume; + + pulse_output_unlock(output); + return success; +} + +const MixerPlugin pulse_mixer_plugin = { + pulse_mixer_init, + false, +}; diff --git a/src/mixer/plugins/PulseMixerPlugin.hxx b/src/mixer/plugins/PulseMixerPlugin.hxx new file mode 100644 index 000000000..9b3a6daf1 --- /dev/null +++ b/src/mixer/plugins/PulseMixerPlugin.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_PULSE_MIXER_PLUGIN_HXX +#define MPD_PULSE_MIXER_PLUGIN_HXX + +class PulseMixer; +struct pa_context; +struct pa_stream; + +void +pulse_mixer_on_connect(PulseMixer &pm, pa_context *context); + +void +pulse_mixer_on_disconnect(PulseMixer &pm); + +void +pulse_mixer_on_change(PulseMixer &pm, pa_context *context, pa_stream *stream); + +#endif diff --git a/src/mixer/plugins/RoarMixerPlugin.cxx b/src/mixer/plugins/RoarMixerPlugin.cxx new file mode 100644 index 000000000..8e198478d --- /dev/null +++ b/src/mixer/plugins/RoarMixerPlugin.cxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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 "mixer/MixerInternal.hxx" +#include "output/plugins/RoarOutputPlugin.hxx" +#include "Compiler.h" + +class RoarMixer final : public Mixer { + /** the base mixer class */ + RoarOutput &self; + +public: + RoarMixer(RoarOutput &_output, MixerListener &_listener) + :Mixer(roar_mixer_plugin, _listener), + self(_output) {} + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static Mixer * +roar_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new RoarMixer((RoarOutput &)ao, listener); +} + +int +RoarMixer::GetVolume(gcc_unused Error &error) +{ + return roar_output_get_volume(self); +} + +bool +RoarMixer::SetVolume(unsigned volume, gcc_unused Error &error) +{ + return roar_output_set_volume(self, volume); +} + +const MixerPlugin roar_mixer_plugin = { + roar_mixer_init, + false, +}; diff --git a/src/mixer/plugins/SoftwareMixerPlugin.cxx b/src/mixer/plugins/SoftwareMixerPlugin.cxx new file mode 100644 index 000000000..f14766002 --- /dev/null +++ b/src/mixer/plugins/SoftwareMixerPlugin.cxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SoftwareMixerPlugin.hxx" +#include "mixer/MixerInternal.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterRegistry.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/plugins/VolumeFilterPlugin.hxx" +#include "pcm/Volume.hxx" +#include "config/ConfigData.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <math.h> + +static Filter * +CreateVolumeFilter() +{ + return filter_new(&volume_filter_plugin, config_param(), + IgnoreError()); +} + +class SoftwareMixer final : public Mixer { + Filter *filter; + + /** + * If this is true, then this object "owns" the #Filter + * instance (see above). It will be set to false by + * software_mixer_get_filter(); after that, the caller will be + * responsible for the #Filter. + */ + bool owns_filter; + + /** + * The current volume in percent (0..100). + */ + unsigned volume; + +public: + SoftwareMixer(MixerListener &_listener) + :Mixer(software_mixer_plugin, _listener), + filter(CreateVolumeFilter()), + owns_filter(true), + volume(100) + { + assert(filter != nullptr); + } + + virtual ~SoftwareMixer() { + if (owns_filter) + delete filter; + } + + Filter *GetFilter(); + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(gcc_unused Error &error) override { + return volume; + } + + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static Mixer * +software_mixer_init(gcc_unused EventLoop &event_loop, + gcc_unused AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new SoftwareMixer(listener); +} + +gcc_const +static unsigned +PercentVolumeToSoftwareVolume(unsigned volume) +{ + assert(volume <= 100); + + if (volume >= 100) + return PCM_VOLUME_1; + else if (volume > 0) + return pcm_float_to_volume((exp(volume / 25.0) - 1) / + (54.5981500331F - 1)); + else + return 0; +} + +bool +SoftwareMixer::SetVolume(unsigned new_volume, gcc_unused Error &error) +{ + assert(new_volume <= 100); + + volume = new_volume; + volume_filter_set(filter, PercentVolumeToSoftwareVolume(new_volume)); + return true; +} + +const MixerPlugin software_mixer_plugin = { + software_mixer_init, + true, +}; + +inline Filter * +SoftwareMixer::GetFilter() +{ + assert(owns_filter); + + owns_filter = false; + return filter; +} + +Filter * +software_mixer_get_filter(Mixer *mixer) +{ + SoftwareMixer *sm = (SoftwareMixer *)mixer; + assert(sm->IsPlugin(software_mixer_plugin)); + return sm->GetFilter(); +} diff --git a/src/mixer/plugins/SoftwareMixerPlugin.hxx b/src/mixer/plugins/SoftwareMixerPlugin.hxx new file mode 100644 index 000000000..581d2ac17 --- /dev/null +++ b/src/mixer/plugins/SoftwareMixerPlugin.hxx @@ -0,0 +1,33 @@ +/* + * 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_SOFTWARE_MIXER_PLUGIN_HXX +#define MPD_SOFTWARE_MIXER_PLUGIN_HXX + +class Mixer; +class Filter; + +/** + * Returns the (volume) filter associated with this mixer. All users + * of this mixer plugin should install this filter. + */ +Filter * +software_mixer_get_filter(Mixer *mixer); + +#endif diff --git a/src/mixer/plugins/WinmmMixerPlugin.cxx b/src/mixer/plugins/WinmmMixerPlugin.cxx new file mode 100644 index 000000000..e0436011a --- /dev/null +++ b/src/mixer/plugins/WinmmMixerPlugin.cxx @@ -0,0 +1,111 @@ +/* + * 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 "mixer/MixerInternal.hxx" +#include "output/OutputAPI.hxx" +#include "output/plugins/WinmmOutputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <mmsystem.h> + +#include <assert.h> +#include <math.h> +#include <windows.h> + +class WinmmMixer final : public Mixer { + WinmmOutput &output; + +public: + WinmmMixer(WinmmOutput &_output, MixerListener &_listener) + :Mixer(winmm_mixer_plugin, _listener), + output(_output) { + } + + /* virtual methods from class Mixer */ + virtual bool Open(gcc_unused Error &error) override { + return true; + } + + virtual void Close() override { + } + + virtual int GetVolume(Error &error) override; + virtual bool SetVolume(unsigned volume, Error &error) override; +}; + +static constexpr Domain winmm_mixer_domain("winmm_mixer"); + +static inline int +winmm_volume_decode(DWORD volume) +{ + return lround((volume & 0xFFFF) / 655.35); +} + +static inline DWORD +winmm_volume_encode(int volume) +{ + int value = lround(volume * 655.35); + return MAKELONG(value, value); +} + +static Mixer * +winmm_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new WinmmMixer((WinmmOutput &)ao, listener); +} + +int +WinmmMixer::GetVolume(Error &error) +{ + DWORD volume; + HWAVEOUT handle = winmm_output_get_handle(output); + MMRESULT result = waveOutGetVolume(handle, &volume); + + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_mixer_domain, "Failed to get winmm volume"); + return -1; + } + + return winmm_volume_decode(volume); +} + +bool +WinmmMixer::SetVolume(unsigned volume, Error &error) +{ + DWORD value = winmm_volume_encode(volume); + HWAVEOUT handle = winmm_output_get_handle(output); + MMRESULT result = waveOutSetVolume(handle, value); + + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_mixer_domain, "Failed to set winmm volume"); + return false; + } + + return true; +} + +const MixerPlugin winmm_mixer_plugin = { + winmm_mixer_init, + false, +}; diff --git a/src/neighbor/Explorer.hxx b/src/neighbor/Explorer.hxx new file mode 100644 index 000000000..84a54840c --- /dev/null +++ b/src/neighbor/Explorer.hxx @@ -0,0 +1,71 @@ +/* + * 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_NEIGHBOR_EXPLORER_HXX +#define MPD_NEIGHBOR_EXPLORER_HXX + +#include <forward_list> + +class Error; +class NeighborListener; +struct NeighborInfo; + +/** + * An object that explores the neighborhood for music servers. + * + * As soon as this object is opened, it will start exploring, and + * notify the #NeighborListener when it found or lost something. + * + * The implementation is supposed to be non-blocking. This can be + * implemented either using the #EventLoop instance that was passed to + * the NeighborPlugin or by moving the blocking parts in a dedicated + * thread. + */ +class NeighborExplorer { +protected: + NeighborListener &listener; + + explicit NeighborExplorer(NeighborListener &_listener) + :listener(_listener) {} + +public: + typedef std::forward_list<NeighborInfo> List; + + /** + * Free instance data. + */ + virtual ~NeighborExplorer() {} + + /** + * Start exploring the neighborhood. + */ + virtual bool Open(Error &error) = 0; + + /** + * Stop exploring. + */ + virtual void Close() = 0; + + /** + * Obtain a list of currently known neighbors. + */ + virtual List GetList() const = 0; +}; + +#endif diff --git a/src/neighbor/Glue.cxx b/src/neighbor/Glue.cxx new file mode 100644 index 000000000..fbf25cc8d --- /dev/null +++ b/src/neighbor/Glue.cxx @@ -0,0 +1,111 @@ +/* + * 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 "Glue.hxx" +#include "Registry.hxx" +#include "Explorer.hxx" +#include "NeighborPlugin.hxx" +#include "Info.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +NeighborGlue::Explorer::~Explorer() +{ + delete explorer; +} + +NeighborGlue::~NeighborGlue() {} + +static NeighborExplorer * +CreateNeighborExplorer(EventLoop &loop, NeighborListener &listener, + const config_param ¶m, Error &error) +{ + const char *plugin_name = param.GetBlockValue("plugin"); + if (plugin_name == nullptr) { + error.Set(config_domain, + "Missing \"plugin\" configuration"); + return nullptr; + } + + const NeighborPlugin *plugin = GetNeighborPluginByName(plugin_name); + if (plugin == nullptr) { + error.Format(config_domain, "No such neighbor plugin: %s", + plugin_name); + return nullptr; + } + + return plugin->create(loop, listener, param, error); +} + +bool +NeighborGlue::Init(EventLoop &loop, NeighborListener &listener, Error &error) +{ + for (const config_param *param = config_get_param(CONF_NEIGHBORS); + param != nullptr; param = param->next) { + NeighborExplorer *explorer = + CreateNeighborExplorer(loop, listener, *param, error); + if (explorer == nullptr) { + error.FormatPrefix("Line %i: ", param->line); + return false; + } + + explorers.emplace_front(explorer); + } + + return true; +} + +bool +NeighborGlue::Open(Error &error) +{ + for (auto i = explorers.begin(), end = explorers.end(); + i != end; ++i) { + if (!i->explorer->Open(error)) { + /* roll back */ + for (auto k = explorers.begin(); k != i; ++k) + k->explorer->Close(); + return false; + } + } + + return true; +} + +void +NeighborGlue::Close() +{ + for (auto i = explorers.begin(), end = explorers.end(); i != end; ++i) + i->explorer->Close(); +} + +NeighborGlue::List +NeighborGlue::GetList() const +{ + List result; + + for (const auto &i : explorers) + result.splice_after(result.before_begin(), + i.explorer->GetList()); + + return result; +} + diff --git a/src/neighbor/Glue.hxx b/src/neighbor/Glue.hxx new file mode 100644 index 000000000..92c612d22 --- /dev/null +++ b/src/neighbor/Glue.hxx @@ -0,0 +1,76 @@ +/* + * 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_NEIGHBOR_ALL_HXX +#define MPD_NEIGHBOR_ALL_HXX + +#include "check.h" +#include "Compiler.h" +#include "thread/Mutex.hxx" + +#include <forward_list> + +struct config_param; +class Error; +class EventLoop; +class NeighborExplorer; +class NeighborListener; +struct NeighborInfo; + +/** + * A class that initializes and opens all configured neighbor plugins. + */ +class NeighborGlue { + struct Explorer { + NeighborExplorer *const explorer; + + Explorer(NeighborExplorer *_explorer):explorer(_explorer) {} + Explorer(const Explorer &) = delete; + ~Explorer(); + }; + + Mutex mutex; + + std::forward_list<Explorer> explorers; + +public: + typedef std::forward_list<NeighborInfo> List; + + NeighborGlue() = default; + NeighborGlue(const NeighborGlue &) = delete; + ~NeighborGlue(); + + bool IsEmpty() const { + return explorers.empty(); + } + + bool Init(EventLoop &loop, NeighborListener &listener, Error &error); + + bool Open(Error &error); + void Close(); + + /** + * Get the combined list of all neighbors from all active + * plugins. + */ + gcc_pure + List GetList() const; +}; + +#endif diff --git a/src/neighbor/Info.hxx b/src/neighbor/Info.hxx new file mode 100644 index 000000000..ac4806f14 --- /dev/null +++ b/src/neighbor/Info.hxx @@ -0,0 +1,35 @@ +/* + * 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_NEIGHBOR_INFO_HXX +#define MPD_NEIGHBOR_INFO_HXX + +#include <string> + +struct NeighborInfo { + std::string uri; + std::string display_name; + + template<typename U, typename DN> + NeighborInfo(U &&_uri, DN &&_display_name) + :uri(std::forward<U>(_uri)), + display_name(std::forward<DN>(_display_name)) {} +}; + +#endif diff --git a/src/neighbor/Listener.hxx b/src/neighbor/Listener.hxx new file mode 100644 index 000000000..20295f5a9 --- /dev/null +++ b/src/neighbor/Listener.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_NEIGHBOR_LISTENER_HXX +#define MPD_NEIGHBOR_LISTENER_HXX + +struct NeighborInfo; +class NeighborExplorer; + +/** + * An interface that listens on events from neighbor plugins. The + * methods must be thread-safe and non-blocking. + */ +class NeighborListener { +public: + virtual void FoundNeighbor(const NeighborInfo &info) = 0; + virtual void LostNeighbor(const NeighborInfo &info) = 0; +}; + +#endif diff --git a/src/neighbor/NeighborPlugin.hxx b/src/neighbor/NeighborPlugin.hxx new file mode 100644 index 000000000..0d4ebaa7b --- /dev/null +++ b/src/neighbor/NeighborPlugin.hxx @@ -0,0 +1,40 @@ +/* + * 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_NEIGHBOR_PLUGIN_HXX +#define MPD_NEIGHBOR_PLUGIN_HXX + +struct config_param; +class Error; +class EventLoop; +class NeighborListener; +class NeighborExplorer; + +struct NeighborPlugin { + const char *name; + + /** + * Allocates and configures a #NeighborExplorer instance. + */ + NeighborExplorer *(*create)(EventLoop &loop, NeighborListener &listener, + const config_param ¶m, + Error &error); +}; + +#endif diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx new file mode 100644 index 000000000..f6d1f97b3 --- /dev/null +++ b/src/neighbor/Registry.cxx @@ -0,0 +1,46 @@ +/* + * 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 "NeighborPlugin.hxx" +#include "plugins/SmbclientNeighborPlugin.hxx" +#include "plugins/UpnpNeighborPlugin.hxx" + +#include <string.h> + +const NeighborPlugin *const neighbor_plugins[] = { +#ifdef ENABLE_SMBCLIENT + &smbclient_neighbor_plugin, +#endif +#ifdef HAVE_LIBUPNP + &upnp_neighbor_plugin, +#endif + nullptr +}; + +const NeighborPlugin * +GetNeighborPluginByName(const char *name) +{ + for (auto i = neighbor_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/neighbor/Registry.hxx b/src/neighbor/Registry.hxx new file mode 100644 index 000000000..0b89e537d --- /dev/null +++ b/src/neighbor/Registry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_REGISTRY_HXX +#define MPD_NEIGHBOR_REGISTRY_HXX + +#include "Compiler.h" + +struct NeighborPlugin; + +/** + * nullptr terminated list of all neighbor plugins which were enabled at + * compile time. + */ +extern const NeighborPlugin *const neighbor_plugins[]; + +gcc_pure +const NeighborPlugin * +GetNeighborPluginByName(const char *name); + +#endif diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx new file mode 100644 index 000000000..2701b0ccd --- /dev/null +++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx @@ -0,0 +1,288 @@ +/* + * 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 "SmbclientNeighborPlugin.hxx" +#include "lib/smbclient/Init.hxx" +#include "lib/smbclient/Domain.hxx" +#include "lib/smbclient/Mutex.hxx" +#include "neighbor/NeighborPlugin.hxx" +#include "neighbor/Explorer.hxx" +#include "neighbor/Listener.hxx" +#include "neighbor/Info.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "thread/Name.hxx" +#include "util/Macros.hxx" +#include "util/Domain.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <libsmbclient.h> + +#include <list> +#include <algorithm> + +class SmbclientNeighborExplorer final : public NeighborExplorer { + struct Server { + std::string name, comment; + + bool alive; + + Server(std::string &&_name, std::string &&_comment) + :name(std::move(_name)), comment(std::move(_comment)), + alive(true) {} + Server(const Server &) = delete; + + gcc_pure + bool operator==(const Server &other) const { + return name == other.name; + } + + gcc_pure + NeighborInfo Export() const { + return { "smb://" + name + "/", comment }; + } + }; + + Thread thread; + + mutable Mutex mutex; + Cond cond; + + List list; + + bool quit; + +public: + SmbclientNeighborExplorer(NeighborListener &_listener) + :NeighborExplorer(_listener) {} + + /* virtual methods from class NeighborExplorer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual List GetList() const override; + +private: + void Run(); + void ThreadFunc(); + static void ThreadFunc(void *ctx); +}; + +bool +SmbclientNeighborExplorer::Open(Error &error) +{ + quit = false; + return thread.Start(ThreadFunc, this, error); +} + +void +SmbclientNeighborExplorer::Close() +{ + mutex.lock(); + quit = true; + cond.signal(); + mutex.unlock(); + + thread.Join(); +} + +NeighborExplorer::List +SmbclientNeighborExplorer::GetList() const +{ + const ScopeLock protect(mutex); + /* + List list; + for (const auto &i : servers) + list.emplace_front(i.Export()); + */ + return list; +} + +static void +ReadServer(NeighborExplorer::List &list, const smbc_dirent &e) +{ + const std::string name(e.name, e.namelen); + const std::string comment(e.comment, e.commentlen); + + list.emplace_front("smb://" + name, name + " (" + comment + ")"); +} + +static void +ReadServers(NeighborExplorer::List &list, const char *uri); + +static void +ReadWorkgroup(NeighborExplorer::List &list, const std::string &name) +{ + std::string uri = "smb://" + name; + ReadServers(list, uri.c_str()); +} + +static void +ReadEntry(NeighborExplorer::List &list, const smbc_dirent &e) +{ + switch (e.smbc_type) { + case SMBC_WORKGROUP: + ReadWorkgroup(list, std::string(e.name, e.namelen)); + break; + + case SMBC_SERVER: + ReadServer(list, e); + break; + } +} + +static void +ReadServers(NeighborExplorer::List &list, int fd) +{ + smbc_dirent *e; + while ((e = smbc_readdir(fd)) != nullptr) + ReadEntry(list, *e); + + smbc_closedir(fd); +} + +static void +ReadServers(NeighborExplorer::List &list, const char *uri) +{ + int fd = smbc_opendir(uri); + if (fd >= 0) { + ReadServers(list, fd); + smbc_closedir(fd); + } else + FormatErrno(smbclient_domain, "smbc_opendir('%s') failed", + uri); +} + +gcc_pure +static NeighborExplorer::List +DetectServers() +{ + NeighborExplorer::List list; + const ScopeLock protect(smbclient_mutex); + ReadServers(list, "smb://"); + return list; +} + +gcc_pure +static NeighborExplorer::List::const_iterator +FindBeforeServerByURI(NeighborExplorer::List::const_iterator prev, + NeighborExplorer::List::const_iterator end, + const std::string &uri) +{ + for (auto i = std::next(prev); i != end; prev = i, i = std::next(prev)) + if (i->uri == uri) + return prev; + + return end; +} + +inline void +SmbclientNeighborExplorer::Run() +{ + List found = DetectServers(), lost; + + mutex.lock(); + + const auto found_before_begin = found.before_begin(); + const auto found_end = found.end(); + + for (auto prev = list.before_begin(), i = std::next(prev), end = list.end(); + i != end; i = std::next(prev)) { + auto f = FindBeforeServerByURI(found_before_begin, found_end, + i->uri); + if (f != found_end) { + /* still visible: remove from "found" so we + don't believe it's a new one */ + *i = std::move(*std::next(f)); + found.erase_after(f); + prev = i; + } else { + /* can't see it anymore: move to "lost" */ +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) + lost.splice_after(lost.before_begin(), list, prev); +#else + /* the forward_list::splice_after() lvalue + reference overload is missing in gcc 4.6 */ + lost.emplace_front(std::move(*i)); + list.erase_after(prev); +#endif + } + } + + for (auto prev = found_before_begin, i = std::next(prev); + i != found_end; prev = i, i = std::next(prev)) + list.push_front(*i); + + mutex.unlock(); + + for (auto &i : lost) + listener.LostNeighbor(i); + + for (auto &i : found) + listener.FoundNeighbor(i); +} + +inline void +SmbclientNeighborExplorer::ThreadFunc() +{ + mutex.lock(); + + while (!quit) { + mutex.unlock(); + + Run(); + + mutex.lock(); + if (quit) + break; + + // TODO: sleep for how long? + cond.timed_wait(mutex, 10000); + } + + mutex.unlock(); +} + +void +SmbclientNeighborExplorer::ThreadFunc(void *ctx) +{ + SetThreadName("smbclient"); + + SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx; + e.ThreadFunc(); +} + +static NeighborExplorer * +smbclient_neighbor_create(gcc_unused EventLoop &loop, + NeighborListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + if (!SmbclientInit(error)) + return nullptr; + + return new SmbclientNeighborExplorer(listener); +} + +const NeighborPlugin smbclient_neighbor_plugin = { + "smbclient", + smbclient_neighbor_create, +}; diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.hxx b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx new file mode 100644 index 000000000..12ec9c0cd --- /dev/null +++ b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_SMBCLIENT_HXX +#define MPD_NEIGHBOR_SMBCLIENT_HXX + +struct NeighborPlugin; + +extern const NeighborPlugin smbclient_neighbor_plugin; + +#endif diff --git a/src/neighbor/plugins/UpnpNeighborPlugin.cxx b/src/neighbor/plugins/UpnpNeighborPlugin.cxx new file mode 100644 index 000000000..253e4c13b --- /dev/null +++ b/src/neighbor/plugins/UpnpNeighborPlugin.cxx @@ -0,0 +1,139 @@ +/* + * 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 "UpnpNeighborPlugin.hxx" +#include "lib/upnp/Domain.hxx" +#include "lib/upnp/ClientInit.hxx" +#include "lib/upnp/Discovery.hxx" +#include "lib/upnp/ContentDirectoryService.hxx" +#include "neighbor/NeighborPlugin.hxx" +#include "neighbor/Explorer.hxx" +#include "neighbor/Listener.hxx" +#include "neighbor/Info.hxx" +#include "Log.hxx" + +class UpnpNeighborExplorer final + : public NeighborExplorer, UPnPDiscoveryListener { + struct Server { + std::string name, comment; + + bool alive; + + Server(std::string &&_name, std::string &&_comment) + :name(std::move(_name)), comment(std::move(_comment)), + alive(true) {} + Server(const Server &) = delete; + + gcc_pure + bool operator==(const Server &other) const { + return name == other.name; + } + + gcc_pure + NeighborInfo Export() const { + return { "smb://" + name + "/", comment }; + } + }; + + UPnPDeviceDirectory *discovery; + +public: + UpnpNeighborExplorer(NeighborListener &_listener) + :NeighborExplorer(_listener) {} + + /* virtual methods from class NeighborExplorer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual List GetList() const override; + +private: + /* virtual methods from class UPnPDiscoveryListener */ + virtual void FoundUPnP(const ContentDirectoryService &service) override; + virtual void LostUPnP(const ContentDirectoryService &service) override; +}; + +bool +UpnpNeighborExplorer::Open(Error &error) +{ + UpnpClient_Handle handle; + if (!UpnpClientGlobalInit(handle, error)) + return false; + + discovery = new UPnPDeviceDirectory(handle, this); + if (!discovery->Start(error)) { + delete discovery; + UpnpClientGlobalFinish(); + return false; + } + + return true; +} + +void +UpnpNeighborExplorer::Close() +{ + delete discovery; + UpnpClientGlobalFinish(); +} + +NeighborExplorer::List +UpnpNeighborExplorer::GetList() const +{ + std::vector<ContentDirectoryService> tmp; + + { + Error error; + if (!discovery->getDirServices(tmp, error)) + LogError(error); + } + + List result; + for (const auto &i : tmp) + result.emplace_front(i.GetURI(), i.getFriendlyName()); + return result; +} + +void +UpnpNeighborExplorer::FoundUPnP(const ContentDirectoryService &service) +{ + const NeighborInfo n(service.GetURI(), service.getFriendlyName()); + listener.FoundNeighbor(n); +} + +void +UpnpNeighborExplorer::LostUPnP(const ContentDirectoryService &service) +{ + const NeighborInfo n(service.GetURI(), service.getFriendlyName()); + listener.LostNeighbor(n); +} + +static NeighborExplorer * +upnp_neighbor_create(gcc_unused EventLoop &loop, + NeighborListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new UpnpNeighborExplorer(listener); +} + +const NeighborPlugin upnp_neighbor_plugin = { + "upnp", + upnp_neighbor_create, +}; diff --git a/src/neighbor/plugins/UpnpNeighborPlugin.hxx b/src/neighbor/plugins/UpnpNeighborPlugin.hxx new file mode 100644 index 000000000..78e4ccf13 --- /dev/null +++ b/src/neighbor/plugins/UpnpNeighborPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_UPNP_HXX +#define MPD_NEIGHBOR_UPNP_HXX + +struct NeighborPlugin; + +extern const NeighborPlugin upnp_neighbor_plugin; + +#endif diff --git a/src/notify.cxx b/src/notify.cxx index 64018968c..5b0b4baa0 100644 --- a/src/notify.cxx +++ b/src/notify.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 diff --git a/src/notify.hxx b/src/notify.hxx index 6b9e95368..ebd12e5c6 100644 --- a/src/notify.hxx +++ b/src/notify.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 @@ -28,7 +28,7 @@ struct notify { Cond cond; bool pending; -#ifndef WIN32 +#if !defined(WIN32) && !defined(__BIONIC__) constexpr #endif notify():pending(false) {} diff --git a/src/open.h b/src/open.h index 1fe245dfa..b05167188 100644 --- a/src/open.h +++ b/src/open.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 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/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx deleted file mode 100644 index 6668c920f..000000000 --- a/src/output/AlsaOutputPlugin.cxx +++ /dev/null @@ -1,858 +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 "AlsaOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" -#include "pcm/PcmExport.hxx" -#include "util/Manual.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <alsa/asoundlib.h> - -#include <string> - -#define ALSA_PCM_NEW_HW_PARAMS_API -#define ALSA_PCM_NEW_SW_PARAMS_API - -static const char default_device[] = "default"; - -static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000; - -#define MPD_ALSA_RETRY_NR 5 - -typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, - snd_pcm_uframes_t size); - -struct AlsaOutput { - struct audio_output base; - - Manual<PcmExport> pcm_export; - - /** - * The configured name of the ALSA device; empty for the - * default device - */ - std::string device; - - /** use memory mapped I/O? */ - bool use_mmap; - - /** - * Enable DSD over USB according to the dCS suggested - * standard? - * - * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf - */ - bool dsd_usb; - - /** libasound's buffer_time setting (in microseconds) */ - unsigned int buffer_time; - - /** libasound's period_time setting (in microseconds) */ - unsigned int period_time; - - /** the mode flags passed to snd_pcm_open */ - int mode; - - /** the libasound PCM device handle */ - snd_pcm_t *pcm; - - /** - * a pointer to the libasound writei() function, which is - * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the - * use_mmap configuration - */ - alsa_writei_t *writei; - - /** - * The size of one audio frame passed to method play(). - */ - size_t in_frame_size; - - /** - * The size of one audio frame passed to libasound. - */ - size_t out_frame_size; - - /** - * The size of one period, in number of frames. - */ - snd_pcm_uframes_t period_frames; - - /** - * The number of frames written in the current period. - */ - snd_pcm_uframes_t period_position; - - /** - * Do we need to call snd_pcm_prepare() before the next write? - * It means that we put the device to SND_PCM_STATE_SETUP by - * calling snd_pcm_drop(). - * - * Without this flag, we could easily recover after a failed - * optimistic write (returning -EBADFD), but the Raspberry Pi - * audio driver is infamous for generating ugly artefacts from - * this. - */ - bool must_prepare; - - /** - * This buffer gets allocated after opening the ALSA device. - * It contains silence samples, enough to fill one period (see - * #period_frames). - */ - void *silence; - - AlsaOutput():mode(0), writei(snd_pcm_writei) { - } - - bool Init(const config_param ¶m, Error &error) { - return ao_base_init(&base, &alsa_output_plugin, - param, error); - } - - void Deinit() { - ao_base_finish(&base); - } -}; - -static constexpr Domain alsa_output_domain("alsa_output"); - -static const char * -alsa_device(const AlsaOutput *ad) -{ - return ad->device.empty() ? default_device : ad->device.c_str(); -} - -static void -alsa_configure(AlsaOutput *ad, const config_param ¶m) -{ - ad->device = param.GetBlockValue("device", ""); - - ad->use_mmap = param.GetBlockValue("use_mmap", false); - - ad->dsd_usb = param.GetBlockValue("dsd_usb", false); - - ad->buffer_time = param.GetBlockValue("buffer_time", - MPD_ALSA_BUFFER_TIME_US); - ad->period_time = param.GetBlockValue("period_time", 0u); - -#ifdef SND_PCM_NO_AUTO_RESAMPLE - if (!param.GetBlockValue("auto_resample", true)) - ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; -#endif - -#ifdef SND_PCM_NO_AUTO_CHANNELS - if (!param.GetBlockValue("auto_channels", true)) - ad->mode |= SND_PCM_NO_AUTO_CHANNELS; -#endif - -#ifdef SND_PCM_NO_AUTO_FORMAT - if (!param.GetBlockValue("auto_format", true)) - ad->mode |= SND_PCM_NO_AUTO_FORMAT; -#endif -} - -static struct audio_output * -alsa_init(const config_param ¶m, Error &error) -{ - AlsaOutput *ad = new AlsaOutput(); - - if (!ad->Init(param, error)) { - delete ad; - return nullptr; - } - - alsa_configure(ad, param); - - return &ad->base; -} - -static void -alsa_finish(struct audio_output *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->Deinit(); - delete ad; - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_output_enable(struct audio_output *ao, gcc_unused Error &error) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->pcm_export.Construct(); - return true; -} - -static void -alsa_output_disable(struct audio_output *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->pcm_export.Destruct(); -} - -static bool -alsa_test_default_device(void) -{ - snd_pcm_t *handle; - - int ret = snd_pcm_open(&handle, default_device, - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (ret) { - FormatError(alsa_output_domain, - "Error opening default ALSA device: %s", - snd_strerror(-ret)); - return false; - } else - snd_pcm_close(handle); - - return true; -} - -static snd_pcm_format_t -get_bitformat(SampleFormat sample_format) -{ - switch (sample_format) { - case SampleFormat::UNDEFINED: - case SampleFormat::DSD: - return SND_PCM_FORMAT_UNKNOWN; - - case SampleFormat::S8: - return SND_PCM_FORMAT_S8; - - case SampleFormat::S16: - return SND_PCM_FORMAT_S16; - - case SampleFormat::S24_P32: - return SND_PCM_FORMAT_S24; - - case SampleFormat::S32: - return SND_PCM_FORMAT_S32; - - case SampleFormat::FLOAT: - return SND_PCM_FORMAT_FLOAT; - } - - assert(false); - gcc_unreachable(); -} - -static snd_pcm_format_t -byteswap_bitformat(snd_pcm_format_t fmt) -{ - switch(fmt) { - case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; - case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; - case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; - case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; - case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; - - case SND_PCM_FORMAT_S24_3BE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_3LE: - return SND_PCM_FORMAT_S24_3BE; - - case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; - default: return SND_PCM_FORMAT_UNKNOWN; - } -} - -static snd_pcm_format_t -alsa_to_packed_format(snd_pcm_format_t fmt) -{ - switch (fmt) { - case SND_PCM_FORMAT_S24_LE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_BE: - return SND_PCM_FORMAT_S24_3BE; - - default: - return SND_PCM_FORMAT_UNKNOWN; - } -} - -static int -alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - snd_pcm_format_t fmt, bool *packed_r) -{ - int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = false; - - if (err != -EINVAL) - return err; - - fmt = alsa_to_packed_format(fmt); - if (fmt == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = true; - - return err; -} - -/** - * Attempts to configure the specified sample format, and tries the - * reversed host byte order if was not supported. - */ -static int -alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - SampleFormat sample_format, - bool *packed_r, bool *reverse_endian_r) -{ - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, - packed_r); - if (err == 0) - *reverse_endian_r = false; - - if (err != -EINVAL) - return err; - - alsa_format = byteswap_bitformat(alsa_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); - if (err == 0) - *reverse_endian_r = true; - - return err; -} - -/** - * Configure a sample format, and probe other formats if that fails. - */ -static int -alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - AudioFormat &audio_format, - bool *packed_r, bool *reverse_endian_r) -{ - /* try the input format first */ - - int err = alsa_output_try_format(pcm, hwparams, - audio_format.format, - packed_r, reverse_endian_r); - - /* if unsupported by the hardware, try other formats */ - - static const SampleFormat probe_formats[] = { - SampleFormat::S24_P32, - SampleFormat::S32, - SampleFormat::S16, - SampleFormat::S8, - SampleFormat::UNDEFINED, - }; - - for (unsigned i = 0; - err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED; - ++i) { - const SampleFormat mpd_format = probe_formats[i]; - if (mpd_format == audio_format.format) - continue; - - err = alsa_output_try_format(pcm, hwparams, mpd_format, - packed_r, reverse_endian_r); - if (err == 0) - audio_format.format = mpd_format; - } - - return err; -} - -/** - * Set up the snd_pcm_t object which was opened by the caller. Set up - * the configured settings and the audio format. - */ -static bool -alsa_setup(AlsaOutput *ad, AudioFormat &audio_format, - bool *packed_r, bool *reverse_endian_r, Error &error) -{ - unsigned int sample_rate = audio_format.sample_rate; - unsigned int channels = audio_format.channels; - int err; - const char *cmd = nullptr; - int retry = MPD_ALSA_RETRY_NR; - unsigned int period_time, period_time_ro; - unsigned int buffer_time; - - period_time_ro = period_time = ad->period_time; -configure_hw: - /* configure HW params */ - snd_pcm_hw_params_t *hwparams; - snd_pcm_hw_params_alloca(&hwparams); - cmd = "snd_pcm_hw_params_any"; - err = snd_pcm_hw_params_any(ad->pcm, hwparams); - if (err < 0) - goto error; - - if (ad->use_mmap) { - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_MMAP_INTERLEAVED); - if (err < 0) { - FormatWarning(alsa_output_domain, - "Cannot set mmap'ed mode on ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(-err)); - LogWarning(alsa_output_domain, - "Falling back to direct write mode"); - ad->use_mmap = false; - } else - ad->writei = snd_pcm_mmap_writei; - } - - if (!ad->use_mmap) { - cmd = "snd_pcm_hw_params_set_access"; - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED); - if (err < 0) - goto error; - ad->writei = snd_pcm_writei; - } - - err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, - packed_r, reverse_endian_r); - if (err < 0) { - error.Format(alsa_output_domain, err, - "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), - sample_format_to_string(audio_format.format), - snd_strerror(-err)); - return false; - } - - snd_pcm_format_t format; - if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) - FormatDebug(alsa_output_domain, - "format=%s (%s)", snd_pcm_format_name(format), - snd_pcm_format_description(format)); - - err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, - &channels); - if (err < 0) { - error.Format(alsa_output_domain, err, - "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format.channels, - snd_strerror(-err)); - return false; - } - audio_format.channels = (int8_t)channels; - - err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, - &sample_rate, nullptr); - if (err < 0 || sample_rate == 0) { - error.Format(alsa_output_domain, err, - "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format.sample_rate); - return false; - } - audio_format.sample_rate = sample_rate; - - snd_pcm_uframes_t buffer_size_min, buffer_size_max; - snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); - snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); - unsigned buffer_time_min, buffer_time_max; - snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); - snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); - FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u", - (unsigned)buffer_size_min, (unsigned)buffer_size_max, - buffer_time_min, buffer_time_max); - - snd_pcm_uframes_t period_size_min, period_size_max; - snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); - snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); - unsigned period_time_min, period_time_max; - snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); - snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); - FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u", - (unsigned)period_size_min, (unsigned)period_size_max, - period_time_min, period_time_max); - - if (ad->buffer_time > 0) { - buffer_time = ad->buffer_time; - cmd = "snd_pcm_hw_params_set_buffer_time_near"; - err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, - &buffer_time, nullptr); - if (err < 0) - goto error; - } else { - err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, - nullptr); - if (err < 0) - buffer_time = 0; - } - - if (period_time_ro == 0 && buffer_time >= 10000) { - period_time_ro = period_time = buffer_time / 4; - - FormatDebug(alsa_output_domain, - "default period_time = buffer_time/4 = %u/4 = %u", - buffer_time, period_time); - } - - if (period_time_ro > 0) { - period_time = period_time_ro; - cmd = "snd_pcm_hw_params_set_period_time_near"; - err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, - &period_time, nullptr); - if (err < 0) - goto error; - } - - cmd = "snd_pcm_hw_params"; - err = snd_pcm_hw_params(ad->pcm, hwparams); - if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { - period_time_ro = period_time_ro >> 1; - goto configure_hw; - } else if (err < 0) - goto error; - if (retry != MPD_ALSA_RETRY_NR) - FormatDebug(alsa_output_domain, - "ALSA period_time set to %d", period_time); - - snd_pcm_uframes_t alsa_buffer_size; - cmd = "snd_pcm_hw_params_get_buffer_size"; - err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); - if (err < 0) - goto error; - - snd_pcm_uframes_t alsa_period_size; - cmd = "snd_pcm_hw_params_get_period_size"; - err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, - nullptr); - if (err < 0) - goto error; - - /* configure SW params */ - snd_pcm_sw_params_t *swparams; - snd_pcm_sw_params_alloca(&swparams); - - cmd = "snd_pcm_sw_params_current"; - err = snd_pcm_sw_params_current(ad->pcm, swparams); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_start_threshold"; - err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, - alsa_buffer_size - - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_avail_min"; - err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params"; - err = snd_pcm_sw_params(ad->pcm, swparams); - if (err < 0) - goto error; - - FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u", - (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); - - if (alsa_period_size == 0) - /* this works around a SIGFPE bug that occurred when - an ALSA driver indicated period_size==0; this - caused a division by zero in alsa_play(). By using - the fallback "1", we make sure that this won't - happen again. */ - alsa_period_size = 1; - - ad->period_frames = alsa_period_size; - ad->period_position = 0; - - ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, - alsa_period_size)); - snd_pcm_format_set_silence(format, ad->silence, - alsa_period_size * channels); - - return true; - -error: - error.Format(alsa_output_domain, err, - "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); - return false; -} - -static bool -alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, - bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - Error &error) -{ - assert(ad->dsd_usb); - assert(audio_format.format == SampleFormat::DSD); - - /* pass 24 bit to alsa_setup() */ - - AudioFormat usb_format = audio_format; - usb_format.format = SampleFormat::S24_P32; - usb_format.sample_rate /= 2; - - const AudioFormat check = usb_format; - - if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error)) - return false; - - /* if the device allows only 32 bit, shift all DSD-over-USB - samples left by 8 bit and leave the lower 8 bit cleared; - the DSD-over-USB documentation does not specify whether - this is legal, but there is anecdotical evidence that this - is possible (and the only option for some devices) */ - *shift8_r = usb_format.format == SampleFormat::S32; - if (usb_format.format == SampleFormat::S32) - usb_format.format = SampleFormat::S24_P32; - - if (usb_format != check) { - /* no bit-perfect playback, which is required - for DSD over USB */ - error.Format(alsa_output_domain, - "Failed to configure DSD-over-USB on ALSA device \"%s\"", - alsa_device(ad)); - g_free(ad->silence); - return false; - } - - return true; -} - -static bool -alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format, - Error &error) -{ - bool shift8 = false, packed, reverse_endian; - - const bool dsd_usb = ad->dsd_usb && - audio_format.format == SampleFormat::DSD; - const bool success = dsd_usb - ? alsa_setup_dsd(ad, audio_format, - &shift8, &packed, &reverse_endian, - error) - : alsa_setup(ad, audio_format, &packed, &reverse_endian, - error); - if (!success) - return false; - - ad->pcm_export->Open(audio_format.format, - audio_format.channels, - dsd_usb, shift8, packed, reverse_endian); - return true; -} - -static bool -alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - int err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); - if (err < 0) { - error.Format(alsa_output_domain, err, - "Failed to open ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(err)); - return false; - } - - FormatDebug(alsa_output_domain, "opened %s type=%s", - snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); - - if (!alsa_setup_or_dsd(ad, audio_format, error)) { - snd_pcm_close(ad->pcm); - return false; - } - - ad->in_frame_size = audio_format.GetFrameSize(); - ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format); - - ad->must_prepare = false; - - return true; -} - -/** - * Write silence to the ALSA device. - */ -static void -alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes) -{ - ad->writei(ad->pcm, ad->silence, nframes); -} - -static int -alsa_recover(AlsaOutput *ad, int err) -{ - if (err == -EPIPE) { - FormatDebug(alsa_output_domain, - "Underrun on ALSA device \"%s\"", alsa_device(ad)); - } else if (err == -ESTRPIPE) { - FormatDebug(alsa_output_domain, - "ALSA device \"%s\" was suspended", - alsa_device(ad)); - } - - switch (snd_pcm_state(ad->pcm)) { - case SND_PCM_STATE_PAUSED: - err = snd_pcm_pause(ad->pcm, /* disable */ 0); - break; - case SND_PCM_STATE_SUSPENDED: - err = snd_pcm_resume(ad->pcm); - if (err == -EAGAIN) - return 0; - /* fall-through to snd_pcm_prepare: */ - case SND_PCM_STATE_SETUP: - case SND_PCM_STATE_XRUN: - ad->period_position = 0; - err = snd_pcm_prepare(ad->pcm); - break; - case SND_PCM_STATE_DISCONNECTED: - break; - /* this is no error, so just keep running */ - case SND_PCM_STATE_RUNNING: - err = 0; - break; - default: - /* unknown state, do nothing */ - break; - } - - return err; -} - -static void -alsa_drain(struct audio_output *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) - return; - - if (ad->period_position > 0) { - /* generate some silence to finish the partial - period */ - snd_pcm_uframes_t nframes = - ad->period_frames - ad->period_position; - alsa_write_silence(ad, nframes); - } - - snd_pcm_drain(ad->pcm); - - ad->period_position = 0; -} - -static void -alsa_cancel(struct audio_output *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - ad->period_position = 0; - ad->must_prepare = true; - - snd_pcm_drop(ad->pcm); -} - -static void -alsa_close(struct audio_output *ao) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - snd_pcm_close(ad->pcm); - g_free(ad->silence); -} - -static size_t -alsa_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - AlsaOutput *ad = (AlsaOutput *)ao; - - assert(size % ad->in_frame_size == 0); - - if (ad->must_prepare) { - ad->must_prepare = false; - - int err = snd_pcm_prepare(ad->pcm); - if (err < 0) { - error.Set(alsa_output_domain, err, snd_strerror(-err)); - return 0; - } - } - - chunk = ad->pcm_export->Export(chunk, size, size); - - assert(size % ad->out_frame_size == 0); - - size /= ad->out_frame_size; - - while (true) { - snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) { - ad->period_position = (ad->period_position + ret) - % ad->period_frames; - - size_t bytes_written = ret * ad->out_frame_size; - return ad->pcm_export->CalcSourceSize(bytes_written); - } - - if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { - error.Set(alsa_output_domain, ret, snd_strerror(-ret)); - return 0; - } - } -} - -const struct audio_output_plugin alsa_output_plugin = { - "alsa", - alsa_test_default_device, - alsa_init, - alsa_finish, - alsa_output_enable, - alsa_output_disable, - alsa_open, - alsa_close, - nullptr, - nullptr, - alsa_play, - alsa_drain, - alsa_cancel, - nullptr, - - &alsa_mixer_plugin, -}; diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx deleted file mode 100644 index dc7e639a8..000000000 --- a/src/output/AlsaOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX -#define MPD_ALSA_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin alsa_output_plugin; - -#endif diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx deleted file mode 100644 index e66969e20..000000000 --- a/src/output/AoOutputPlugin.cxx +++ /dev/null @@ -1,286 +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 "AoOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <ao/ao.h> -#include <glib.h> - -#include <string.h> - -/* An ao_sample_format, with all fields set to zero: */ -static ao_sample_format OUR_AO_FORMAT_INITIALIZER; - -static unsigned ao_output_ref; - -struct AoOutput { - struct audio_output base; - - size_t write_size; - int driver; - ao_option *options; - ao_device *device; - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &ao_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - bool Configure(const config_param ¶m, Error &error); -}; - -static constexpr Domain ao_output_domain("ao_output"); - -static void -ao_output_error(Error &error_r) -{ - const char *error; - - switch (errno) { - case AO_ENODRIVER: - error = "No such libao driver"; - break; - - case AO_ENOTLIVE: - error = "This driver is not a libao live device"; - break; - - case AO_EBADOPTION: - error = "Invalid libao option"; - break; - - case AO_EOPENDEVICE: - error = "Cannot open the libao device"; - break; - - case AO_EFAIL: - error = "Generic libao failure"; - break; - - default: - error_r.SetErrno(); - return; - } - - error_r.Set(ao_output_domain, errno, error); -} - -inline bool -AoOutput::Configure(const config_param ¶m, Error &error) -{ - const char *value; - - options = nullptr; - - write_size = param.GetBlockValue("write_size", 1024u); - - if (ao_output_ref == 0) { - ao_initialize(); - } - ao_output_ref++; - - value = param.GetBlockValue("driver", "default"); - if (0 == strcmp(value, "default")) - driver = ao_default_driver_id(); - else - driver = ao_driver_id(value); - - if (driver < 0) { - error.Format(ao_output_domain, - "\"%s\" is not a valid ao driver", - value); - return false; - } - - ao_info *ai = ao_driver_info(driver); - if (ai == nullptr) { - error.Set(ao_output_domain, "problems getting driver info"); - return false; - } - - FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n", - ai->short_name, param.GetBlockValue("name", nullptr)); - - value = param.GetBlockValue("options", nullptr); - if (value != nullptr) { - gchar **_options = g_strsplit(value, ";", 0); - - for (unsigned i = 0; _options[i] != nullptr; ++i) { - gchar **key_value = g_strsplit(_options[i], "=", 2); - - if (key_value[0] == nullptr || key_value[1] == nullptr) { - error.Format(ao_output_domain, - "problems parsing options \"%s\"", - _options[i]); - return false; - } - - ao_append_option(&options, key_value[0], - key_value[1]); - - g_strfreev(key_value); - } - - g_strfreev(_options); - } - - return true; -} - -static struct audio_output * -ao_output_init(const config_param ¶m, Error &error) -{ - AoOutput *ad = new AoOutput(); - - if (!ad->Initialize(param, error)) { - delete ad; - return nullptr; - } - - if (!ad->Configure(param, error)) { - ad->Deinitialize(); - delete ad; - return nullptr; - } - - return &ad->base; -} - -static void -ao_output_finish(struct audio_output *ao) -{ - AoOutput *ad = (AoOutput *)ao; - - ao_free_options(ad->options); - ad->Deinitialize(); - delete ad; - - ao_output_ref--; - - if (ao_output_ref == 0) - ao_shutdown(); -} - -static void -ao_output_close(struct audio_output *ao) -{ - AoOutput *ad = (AoOutput *)ao; - - ao_close(ad->device); -} - -static bool -ao_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; - AoOutput *ad = (AoOutput *)ao; - - switch (audio_format.format) { - case SampleFormat::S8: - format.bits = 8; - break; - - case SampleFormat::S16: - format.bits = 16; - break; - - default: - /* support for 24 bit samples in libao is currently - dubious, and until we have sorted that out, - convert everything to 16 bit */ - audio_format.format = SampleFormat::S16; - format.bits = 16; - break; - } - - format.rate = audio_format.sample_rate; - format.byte_format = AO_FMT_NATIVE; - format.channels = audio_format.channels; - - ad->device = ao_open_live(ad->driver, &format, ad->options); - - if (ad->device == nullptr) { - ao_output_error(error); - return false; - } - - return true; -} - -/** - * For whatever reason, libao wants a non-const pointer. Let's hope - * it does not write to the buffer, and use the union deconst hack to - * work around this API misdesign. - */ -static int ao_play_deconst(ao_device *device, const void *output_samples, - uint_32 num_bytes) -{ - union { - const void *in; - char *out; - } u; - - u.in = output_samples; - return ao_play(device, u.out, num_bytes); -} - -static size_t -ao_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - AoOutput *ad = (AoOutput *)ao; - - if (size > ad->write_size) - size = ad->write_size; - - if (ao_play_deconst(ad->device, chunk, size) == 0) { - ao_output_error(error); - return 0; - } - - return size; -} - -const struct audio_output_plugin ao_output_plugin = { - "ao", - nullptr, - ao_output_init, - ao_output_finish, - nullptr, - nullptr, - ao_output_open, - ao_output_close, - nullptr, - nullptr, - ao_output_play, - nullptr, - nullptr, - nullptr, - nullptr, -}; diff --git a/src/output/AoOutputPlugin.hxx b/src/output/AoOutputPlugin.hxx deleted file mode 100644 index a44885e56..000000000 --- a/src/output/AoOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AO_OUTPUT_PLUGIN_HXX -#define MPD_AO_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin ao_output_plugin; - -#endif diff --git a/src/output/Domain.cxx b/src/output/Domain.cxx new file mode 100644 index 000000000..878e5f3c5 --- /dev/null +++ b/src/output/Domain.cxx @@ -0,0 +1,23 @@ +/* + * 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 "Domain.hxx" +#include "util/Domain.hxx" + +const Domain output_domain("output"); diff --git a/src/output/Domain.hxx b/src/output/Domain.hxx new file mode 100644 index 000000000..e3a20142f --- /dev/null +++ b/src/output/Domain.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_OUTPUT_ERROR_HXX +#define MPD_OUTPUT_ERROR_HXX + +extern const class Domain output_domain; + +#endif diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx deleted file mode 100644 index aeb9a6a87..000000000 --- a/src/output/FifoOutputPlugin.cxx +++ /dev/null @@ -1,316 +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 "FifoOutputPlugin.hxx" -#include "ConfigError.hxx" -#include "OutputAPI.hxx" -#include "Timer.hxx" -#include "system/fd_util.h" -#include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" -#include "open.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <errno.h> -#include <string.h> -#include <unistd.h> - -#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ - -struct FifoOutput { - struct audio_output base; - - AllocatedPath path; - std::string path_utf8; - - int input; - int output; - bool created; - Timer *timer; - - FifoOutput() - :path(AllocatedPath::Null()), input(-1), output(-1), - created(false) {} - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &fifo_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - bool Create(Error &error); - bool Check(Error &error); - void Delete(); - - bool Open(Error &error); - void Close(); -}; - -static constexpr Domain fifo_output_domain("fifo_output"); - -inline void -FifoOutput::Delete() -{ - FormatDebug(fifo_output_domain, - "Removing FIFO \"%s\"", path_utf8.c_str()); - - if (!RemoveFile(path)) { - FormatErrno(fifo_output_domain, - "Could not remove FIFO \"%s\"", - path_utf8.c_str()); - return; - } - - created = false; -} - -void -FifoOutput::Close() -{ - if (input >= 0) { - close(input); - input = -1; - } - - if (output >= 0) { - close(output); - output = -1; - } - - struct stat st; - if (created && StatFile(path, st)) - Delete(); -} - -inline bool -FifoOutput::Create(Error &error) -{ - if (!MakeFifo(path, 0666)) { - error.FormatErrno("Couldn't create FIFO \"%s\"", - path_utf8.c_str()); - return false; - } - - created = true; - return true; -} - -inline bool -FifoOutput::Check(Error &error) -{ - struct stat st; - if (!StatFile(path, st)) { - if (errno == ENOENT) { - /* Path doesn't exist */ - return Create(error); - } - - error.FormatErrno("Failed to stat FIFO \"%s\"", - path_utf8.c_str()); - return false; - } - - if (!S_ISFIFO(st.st_mode)) { - error.Format(fifo_output_domain, - "\"%s\" already exists, but is not a FIFO", - path_utf8.c_str()); - return false; - } - - return true; -} - -inline bool -FifoOutput::Open(Error &error) -{ - if (!Check(error)) - return false; - - input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0); - if (input < 0) { - error.FormatErrno("Could not open FIFO \"%s\" for reading", - path_utf8.c_str()); - Close(); - return false; - } - - output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0); - if (output < 0) { - error.FormatErrno("Could not open FIFO \"%s\" for writing", - path_utf8.c_str()); - Close(); - return false; - } - - return true; -} - -static bool -fifo_open(FifoOutput *fd, Error &error) -{ - return fd->Open(error); -} - -static struct audio_output * -fifo_output_init(const config_param ¶m, Error &error) -{ - FifoOutput *fd = new FifoOutput(); - - fd->path = param.GetBlockPath("path", error); - if (fd->path.IsNull()) { - delete fd; - - if (!error.IsDefined()) - error.Set(config_domain, - "No \"path\" parameter specified"); - return nullptr; - } - - fd->path_utf8 = fd->path.ToUTF8(); - - if (!fd->Initialize(param, error)) { - delete fd; - return nullptr; - } - - if (!fifo_open(fd, error)) { - fd->Deinitialize(); - delete fd; - return nullptr; - } - - return &fd->base; -} - -static void -fifo_output_finish(struct audio_output *ao) -{ - FifoOutput *fd = (FifoOutput *)ao; - - fd->Close(); - fd->Deinitialize(); - delete fd; -} - -static bool -fifo_output_open(struct audio_output *ao, AudioFormat &audio_format, - gcc_unused Error &error) -{ - FifoOutput *fd = (FifoOutput *)ao; - - fd->timer = new Timer(audio_format); - - return true; -} - -static void -fifo_output_close(struct audio_output *ao) -{ - FifoOutput *fd = (FifoOutput *)ao; - - delete fd->timer; -} - -static void -fifo_output_cancel(struct audio_output *ao) -{ - FifoOutput *fd = (FifoOutput *)ao; - char buf[FIFO_BUFFER_SIZE]; - int bytes = 1; - - fd->timer->Reset(); - - while (bytes > 0 && errno != EINTR) - bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); - - if (bytes < 0 && errno != EAGAIN) { - FormatErrno(fifo_output_domain, - "Flush of FIFO \"%s\" failed", - fd->path_utf8.c_str()); - } -} - -static unsigned -fifo_output_delay(struct audio_output *ao) -{ - FifoOutput *fd = (FifoOutput *)ao; - - return fd->timer->IsStarted() - ? fd->timer->GetDelay() - : 0; -} - -static size_t -fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - FifoOutput *fd = (FifoOutput *)ao; - ssize_t bytes; - - if (!fd->timer->IsStarted()) - fd->timer->Start(); - fd->timer->Add(size); - - while (true) { - bytes = write(fd->output, chunk, size); - if (bytes > 0) - return (size_t)bytes; - - if (bytes < 0) { - switch (errno) { - case EAGAIN: - /* The pipe is full, so empty it */ - fifo_output_cancel(&fd->base); - continue; - case EINTR: - continue; - } - - error.FormatErrno("Failed to write to FIFO %s", - fd->path_utf8.c_str()); - return 0; - } - } -} - -const struct audio_output_plugin fifo_output_plugin = { - "fifo", - nullptr, - fifo_output_init, - fifo_output_finish, - nullptr, - nullptr, - fifo_output_open, - fifo_output_close, - fifo_output_delay, - nullptr, - fifo_output_play, - nullptr, - fifo_output_cancel, - nullptr, - nullptr, -}; diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/FifoOutputPlugin.hxx deleted file mode 100644 index dca2886d8..000000000 --- a/src/output/FifoOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX -#define MPD_FIFO_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin fifo_output_plugin; - -#endif diff --git a/src/output/Finish.cxx b/src/output/Finish.cxx new file mode 100644 index 000000000..be2ca463e --- /dev/null +++ b/src/output/Finish.cxx @@ -0,0 +1,50 @@ +/* + * 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 "Internal.hxx" +#include "OutputPlugin.hxx" +#include "mixer/MixerControl.hxx" +#include "filter/FilterInternal.hxx" + +#include <assert.h> + +AudioOutput::~AudioOutput() +{ + assert(!open); + assert(!fail_timer.IsDefined()); + assert(!thread.IsDefined()); + + if (mixer != nullptr) + mixer_free(mixer); + + delete replay_gain_filter; + delete other_replay_gain_filter; + delete filter; +} + +void +audio_output_free(AudioOutput *ao) +{ + assert(!ao->open); + assert(!ao->fail_timer.IsDefined()); + assert(!ao->thread.IsDefined()); + + ao_plugin_finish(ao); +} diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx deleted file mode 100644 index dc337053d..000000000 --- a/src/output/HttpdClient.cxx +++ /dev/null @@ -1,467 +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 "HttpdClient.hxx" -#include "HttpdInternal.hxx" -#include "util/ASCII.hxx" -#include "Page.hxx" -#include "IcyMetaDataServer.hxx" -#include "system/SocketError.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -HttpdClient::~HttpdClient() -{ - if (state == RESPONSE) { - if (current_page != nullptr) - current_page->Unref(); - - for (auto page : pages) - page->Unref(); - } - - if (metadata) - metadata->Unref(); -} - -void -HttpdClient::Close() -{ - httpd->RemoveClient(*this); -} - -void -HttpdClient::LockClose() -{ - const ScopeLock protect(httpd->mutex); - Close(); -} - -void -HttpdClient::BeginResponse() -{ - assert(state != RESPONSE); - - state = RESPONSE; - current_page = nullptr; - - if (!head_method) - httpd->SendHeader(*this); -} - -/** - * Handle a line of the HTTP request. - */ -bool -HttpdClient::HandleLine(const char *line) -{ - assert(state != RESPONSE); - - if (state == REQUEST) { - if (memcmp(line, "HEAD /", 6) == 0) { - line += 6; - head_method = true; - } else if (memcmp(line, "GET /", 5) == 0) { - line += 5; - } else { - /* only GET is supported */ - LogWarning(httpd_output_domain, - "malformed request line from client"); - return false; - } - - line = strchr(line, ' '); - if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - - if (head_method) - return false; - - BeginResponse(); - return true; - } - - /* after the request line, request headers follow */ - state = HEADERS; - return true; - } else { - if (*line == 0) { - /* empty line: request is finished */ - - BeginResponse(); - return true; - } - - if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) || - StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) { - /* Send icy metadata */ - metadata_requested = metadata_supported; - return true; - } - - if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) { - /* Send as dlna */ - dlna_streaming_requested = true; - /* metadata is not supported by dlna streaming, so disable it */ - metadata_supported = false; - metadata_requested = false; - return true; - } - - /* expect more request headers */ - return true; - } -} - -/** - * Sends the status line and response headers to the client. - */ -bool -HttpdClient::SendResponse() -{ - char buffer[1024]; - assert(state == RESPONSE); - - if (dlna_streaming_requested) { - snprintf(buffer, sizeof(buffer), - "HTTP/1.1 206 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: 10000\r\n" - "Content-RangeX: 0-1000000/1000000\r\n" - "transferMode.dlna.org: Streaming\r\n" - "Accept-Ranges: bytes\r\n" - "Connection: close\r\n" - "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" - "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" - "\r\n", - httpd->content_type); - - } else if (metadata_requested) { - char *metadata_header = - icy_server_metadata_header(httpd->name, httpd->genre, - httpd->website, - httpd->content_type, - metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - delete[] metadata_header; - - } else { /* revert to a normal HTTP request */ - snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" - "\r\n", - httpd->content_type); - } - - ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); - if (gcc_unlikely(nbytes < 0)) { - const SocketErrorMessage msg; - FormatWarning(httpd_output_domain, - "failed to write to client: %s", - (const char *)msg); - Close(); - return false; - } - - return true; -} - -HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, - bool _metadata_supported) - :BufferedSocket(_fd, _loop), - httpd(_httpd), - state(REQUEST), - head_method(false), - dlna_streaming_requested(false), - metadata_supported(_metadata_supported), - metadata_requested(false), metadata_sent(true), - metaint(8192), /*TODO: just a std value */ - metadata(nullptr), - metadata_current_position(0), metadata_fill(0) -{ -} - -size_t -HttpdClient::GetQueueSize() const -{ - if (state != RESPONSE) - return 0; - - size_t size = 0; - for (auto page : pages) - size += page->size; - return size; -} - -void -HttpdClient::CancelQueue() -{ - if (state != RESPONSE) - return; - - for (auto page : pages) - page->Unref(); - pages.clear(); - - if (current_page == nullptr) - CancelWrite(); -} - -ssize_t -HttpdClient::TryWritePage(const Page &page, size_t position) -{ - assert(position < page.size); - - return Write(page.data + position, page.size - position); -} - -ssize_t -HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n) -{ - return n >= 0 - ? Write(page.data + position, n) - : TryWritePage(page, position); -} - -ssize_t -HttpdClient::GetBytesTillMetaData() const -{ - if (metadata_requested && - current_page->size - current_position > metaint - metadata_fill) - return metaint - metadata_fill; - - return -1; -} - -inline bool -HttpdClient::TryWrite() -{ - const ScopeLock protect(httpd->mutex); - - assert(state == RESPONSE); - - if (current_page == nullptr) { - if (pages.empty()) { - /* another thread has removed the event source - while this thread was waiting for - httpd->mutex */ - CancelWrite(); - return true; - } - - current_page = pages.front(); - pages.pop_front(); - current_position = 0; - } - - const ssize_t bytes_to_write = GetBytesTillMetaData(); - if (bytes_to_write == 0) { - if (!metadata_sent) { - ssize_t nbytes = TryWritePage(*metadata, - metadata_current_position); - if (nbytes < 0) { - auto e = GetSocketError(); - if (IsSocketErrorAgain(e)) - return true; - - if (!IsSocketErrorClosed(e)) { - SocketErrorMessage msg(e); - FormatWarning(httpd_output_domain, - "failed to write to client: %s", - (const char *)msg); - } - - Close(); - return false; - } - - metadata_current_position += nbytes; - - if (metadata->size - metadata_current_position == 0) { - metadata_fill = 0; - metadata_current_position = 0; - metadata_sent = true; - } - } else { - guchar empty_data = 0; - - ssize_t nbytes = Write(&empty_data, 1); - if (nbytes < 0) { - auto e = GetSocketError(); - if (IsSocketErrorAgain(e)) - return true; - - if (!IsSocketErrorClosed(e)) { - SocketErrorMessage msg(e); - FormatWarning(httpd_output_domain, - "failed to write to client: %s", - (const char *)msg); - } - - Close(); - return false; - } - - metadata_fill = 0; - metadata_current_position = 0; - } - } else { - ssize_t nbytes = - TryWritePageN(*current_page, current_position, - bytes_to_write); - if (nbytes < 0) { - auto e = GetSocketError(); - if (IsSocketErrorAgain(e)) - return true; - - if (!IsSocketErrorClosed(e)) { - SocketErrorMessage msg(e); - FormatWarning(httpd_output_domain, - "failed to write to client: %s", - (const char *)msg); - } - - Close(); - return false; - } - - current_position += nbytes; - assert(current_position <= current_page->size); - - if (metadata_requested) - metadata_fill += nbytes; - - if (current_position >= current_page->size) { - current_page->Unref(); - current_page = nullptr; - - if (pages.empty()) - /* all pages are sent: remove the - event source */ - CancelWrite(); - } - } - - return true; -} - -void -HttpdClient::PushPage(Page *page) -{ - if (state != RESPONSE) - /* the client is still writing the HTTP request */ - return; - - page->Ref(); - pages.push_back(page); - - ScheduleWrite(); -} - -void -HttpdClient::PushMetaData(Page *page) -{ - if (metadata) { - metadata->Unref(); - metadata = nullptr; - } - - g_return_if_fail (page); - - page->Ref(); - metadata = page; - metadata_sent = false; -} - -bool -HttpdClient::OnSocketReady(unsigned flags) -{ - if (!BufferedSocket::OnSocketReady(flags)) - return false; - - if (flags & WRITE) - if (!TryWrite()) - return false; - - return true; -} - -BufferedSocket::InputResult -HttpdClient::OnSocketInput(void *data, size_t length) -{ - if (state == RESPONSE) { - LogWarning(httpd_output_domain, - "unexpected input from client"); - LockClose(); - return InputResult::CLOSED; - } - - char *line = (char *)data; - char *newline = (char *)memchr(line, '\n', length); - if (newline == nullptr) - return InputResult::MORE; - - ConsumeInput(newline + 1 - line); - - if (newline > line && newline[-1] == '\r') - --newline; - - /* terminate the string at the end of the line */ - *newline = 0; - - if (!HandleLine(line)) { - LockClose(); - return InputResult::CLOSED; - } - - if (state == RESPONSE) { - if (!SendResponse()) - return InputResult::CLOSED; - - if (head_method) { - LockClose(); - return InputResult::CLOSED; - } - } - - return InputResult::AGAIN; -} - -void -HttpdClient::OnSocketError(Error &&error) -{ - LogError(error); -} - -void -HttpdClient::OnSocketClosed() -{ - LockClose(); -} diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx deleted file mode 100644 index 66a819232..000000000 --- a/src/output/HttpdClient.hxx +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX -#define MPD_OUTPUT_HTTPD_CLIENT_HXX - -#include "event/BufferedSocket.hxx" -#include "Compiler.h" - -#include <list> - -#include <stddef.h> - -struct HttpdOutput; -class Page; - -class HttpdClient final : public BufferedSocket { - /** - * The httpd output object this client is connected to. - */ - HttpdOutput *const httpd; - - /** - * The current state of the client. - */ - enum { - /** reading the request line */ - REQUEST, - - /** reading the request headers */ - HEADERS, - - /** sending the HTTP response */ - RESPONSE, - } state; - - /** - * A queue of #Page objects to be sent to the client. - */ - std::list<Page *> pages; - - /** - * The #page which is currently being sent to the client. - */ - Page *current_page; - - /** - * The amount of bytes which were already sent from - * #current_page. - */ - size_t current_position; - - /** - * Is this a HEAD request? - */ - bool head_method; - - /** - * If DLNA streaming was an option. - */ - bool dlna_streaming_requested; - - /* ICY */ - - /** - * Do we support sending Icy-Metadata to the client? This is - * disabled if the httpd audio output uses encoder tags. - */ - bool metadata_supported; - - /** - * If we should sent icy metadata. - */ - bool metadata_requested; - - /** - * If the current metadata was already sent to the client. - */ - bool metadata_sent; - - /** - * The amount of streaming data between each metadata block - */ - unsigned metaint; - - /** - * The metadata as #Page which is currently being sent to the client. - */ - Page *metadata; - - /* - * The amount of bytes which were already sent from the metadata. - */ - size_t metadata_current_position; - - /** - * The amount of streaming data sent to the client - * since the last icy information was sent. - */ - unsigned metadata_fill; - -public: - /** - * @param httpd the HTTP output device - * @param fd the socket file descriptor - */ - HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, - bool _metadata_supported); - - /** - * Note: this does not remove the client from the - * #HttpdOutput object. - */ - ~HttpdClient(); - - /** - * Frees the client and removes it from the server's client list. - */ - void Close(); - - void LockClose(); - - /** - * Returns the total size of this client's page queue. - */ - gcc_pure - size_t GetQueueSize() const; - - /** - * Clears the page queue. - */ - void CancelQueue(); - - /** - * Handle a line of the HTTP request. - */ - bool HandleLine(const char *line); - - /** - * Switch the client to the "RESPONSE" state. - */ - void BeginResponse(); - - /** - * Sends the status line and response headers to the client. - */ - bool SendResponse(); - - gcc_pure - ssize_t GetBytesTillMetaData() const; - - ssize_t TryWritePage(const Page &page, size_t position); - ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n); - - bool TryWrite(); - - /** - * Appends a page to the client's queue. - */ - void PushPage(Page *page); - - /** - * Sends the passed metadata. - */ - void PushMetaData(Page *page); - -protected: - virtual bool OnSocketReady(unsigned flags) override; - virtual InputResult OnSocketInput(void *data, size_t length) override; - virtual void OnSocketError(Error &&error) override; - virtual void OnSocketClosed() override; -}; - -#endif diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx deleted file mode 100644 index b76493a44..000000000 --- a/src/output/HttpdInternal.hxx +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal declarations for the "httpd" audio output plugin. - */ - -#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H -#define MPD_OUTPUT_HTTPD_INTERNAL_H - -#include "OutputInternal.hxx" -#include "Timer.hxx" -#include "thread/Mutex.hxx" -#include "event/ServerSocket.hxx" - -#ifdef _LIBCPP_VERSION -/* can't use incomplete template arguments with libc++ */ -#include "HttpdClient.hxx" -#endif - -#include <forward_list> - -struct config_param; -class Error; -class EventLoop; -class ServerSocket; -class HttpdClient; -class Page; -struct Encoder; -struct Tag; - -struct HttpdOutput final : private ServerSocket { - struct audio_output base; - - /** - * True if the audio output is open and accepts client - * connections. - */ - bool open; - - /** - * The configured encoder plugin. - */ - Encoder *encoder; - - /** - * Number of bytes which were fed into the encoder, without - * ever receiving new output. This is used to estimate - * whether MPD should manually flush the encoder, to avoid - * buffer underruns in the client. - */ - size_t unflushed_input; - - /** - * The MIME type produced by the #encoder. - */ - const char *content_type; - - /** - * This mutex protects the listener socket and the client - * list. - */ - mutable Mutex mutex; - - /** - * A #Timer object to synchronize this output with the - * wallclock. - */ - Timer *timer; - - /** - * The header page, which is sent to every client on connect. - */ - Page *header; - - /** - * The metadata, which is sent to every client. - */ - Page *metadata; - - /** - * The configured name. - */ - char const *name; - /** - * The configured genre. - */ - char const *genre; - /** - * The configured website address. - */ - char const *website; - - /** - * A linked list containing all clients which are currently - * connected. - */ - std::forward_list<HttpdClient> clients; - - /** - * A temporary buffer for the httpd_output_read_page() - * function. - */ - char buffer[32768]; - - /** - * The maximum and current number of clients connected - * at the same time. - */ - unsigned clients_max, clients_cnt; - - HttpdOutput(EventLoop &_loop); - ~HttpdOutput(); - - bool Configure(const config_param ¶m, Error &error); - - bool Bind(Error &error); - void Unbind(); - - /** - * Caller must lock the mutex. - */ - bool OpenEncoder(AudioFormat &audio_format, Error &error); - - /** - * Caller must lock the mutex. - */ - bool Open(AudioFormat &audio_format, Error &error); - - /** - * Caller must lock the mutex. - */ - void Close(); - - /** - * Check whether there is at least one client. - * - * Caller must lock the mutex. - */ - gcc_pure - bool HasClients() const { - return !clients.empty(); - } - - /** - * Check whether there is at least one client. - */ - gcc_pure - bool LockHasClients() const { - const ScopeLock protect(mutex); - return HasClients(); - } - - void AddClient(int fd); - - /** - * Removes a client from the httpd_output.clients linked list. - */ - void RemoveClient(HttpdClient &client); - - /** - * Sends the encoder header to the client. This is called - * right after the response headers have been sent. - */ - void SendHeader(HttpdClient &client) const; - - /** - * Reads data from the encoder (as much as available) and - * returns it as a new #page object. - */ - Page *ReadPage(); - - /** - * Broadcasts a page struct to all clients. - * - * Mutext must not be locked. - */ - void BroadcastPage(Page *page); - - /** - * Broadcasts data from the encoder to all clients. - */ - void BroadcastFromEncoder(); - - bool EncodeAndPlay(const void *chunk, size_t size, Error &error); - - void SendTag(const Tag *tag); - -private: - virtual void OnAccept(int fd, const sockaddr &address, - size_t address_length, int uid) override; -}; - -extern const class Domain httpd_output_domain; - -#endif diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx deleted file mode 100644 index 369c06937..000000000 --- a/src/output/HttpdOutputPlugin.cxx +++ /dev/null @@ -1,565 +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 "HttpdOutputPlugin.hxx" -#include "HttpdInternal.hxx" -#include "HttpdClient.hxx" -#include "OutputAPI.hxx" -#include "EncoderPlugin.hxx" -#include "EncoderList.hxx" -#include "system/Resolver.hxx" -#include "Page.hxx" -#include "IcyMetaDataServer.hxx" -#include "system/fd_util.h" -#include "Main.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> - -#include <sys/types.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> - -#ifdef HAVE_LIBWRAP -#include <sys/socket.h> /* needed for AF_UNIX */ -#include <tcpd.h> -#endif - -const Domain httpd_output_domain("httpd_output"); - -inline -HttpdOutput::HttpdOutput(EventLoop &_loop) - :ServerSocket(_loop), - encoder(nullptr), unflushed_input(0), - metadata(nullptr) -{ -} - -HttpdOutput::~HttpdOutput() -{ - if (metadata != nullptr) - metadata->Unref(); - - if (encoder != nullptr) - encoder_finish(encoder); - -} - -inline bool -HttpdOutput::Bind(Error &error) -{ - open = false; - - const ScopeLock protect(mutex); - return ServerSocket::Open(error); -} - -inline void -HttpdOutput::Unbind() -{ - assert(!open); - - const ScopeLock protect(mutex); - ServerSocket::Close(); -} - -inline bool -HttpdOutput::Configure(const config_param ¶m, Error &error) -{ - /* read configuration */ - name = param.GetBlockValue("name", "Set name in config"); - genre = param.GetBlockValue("genre", "Set genre in config"); - website = param.GetBlockValue("website", "Set website in config"); - - unsigned port = param.GetBlockValue("port", 8000u); - - const char *encoder_name = - param.GetBlockValue("encoder", "vorbis"); - const auto encoder_plugin = encoder_plugin_get(encoder_name); - if (encoder_plugin == nullptr) { - error.Format(httpd_output_domain, - "No such encoder: %s", encoder_name); - return false; - } - - clients_max = param.GetBlockValue("max_clients", 0u); - - /* set up bind_to_address */ - - const char *bind_to_address = param.GetBlockValue("bind_to_address"); - bool success = bind_to_address != nullptr && - strcmp(bind_to_address, "any") != 0 - ? AddHost(bind_to_address, port, error) - : AddPort(port, error); - if (!success) - return false; - - /* initialize encoder */ - - encoder = encoder_init(*encoder_plugin, param, error); - if (encoder == nullptr) - return false; - - /* determine content type */ - content_type = encoder_get_mime_type(encoder); - if (content_type == nullptr) - content_type = "application/octet-stream"; - - return true; -} - -static struct audio_output * -httpd_output_init(const config_param ¶m, Error &error) -{ - HttpdOutput *httpd = new HttpdOutput(*main_loop); - - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, - error)) { - delete httpd; - return nullptr; - } - - if (!httpd->Configure(param, error)) { - ao_base_finish(&httpd->base); - delete httpd; - return nullptr; - } - - return &httpd->base; -} - -#if GCC_CHECK_VERSION(4,6) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#endif - -static inline constexpr HttpdOutput * -Cast(audio_output *ao) -{ - return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base)); -} - -#if GCC_CHECK_VERSION(4,6) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -static void -httpd_output_finish(struct audio_output *ao) -{ - HttpdOutput *httpd = Cast(ao); - - ao_base_finish(&httpd->base); - delete httpd; -} - -/** - * Creates a new #HttpdClient object and adds it into the - * HttpdOutput.clients linked list. - */ -inline void -HttpdOutput::AddClient(int fd) -{ - clients.emplace_front(this, fd, GetEventLoop(), - encoder->plugin.tag == nullptr); - ++clients_cnt; - - /* pass metadata to client */ - if (metadata != nullptr) - clients.front().PushMetaData(metadata); -} - -void -HttpdOutput::OnAccept(int fd, const sockaddr &address, - size_t address_length, gcc_unused int uid) -{ - /* the listener socket has become readable - a client has - connected */ - -#ifdef HAVE_LIBWRAP - if (address.sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(&address, address_length, - IgnoreError()); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - FormatWarning(httpd_output_domain, - "libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); - close_socket(fd); - return; - } - - g_free(hostaddr); - } -#else - (void)address; - (void)address_length; -#endif /* HAVE_WRAP */ - - const ScopeLock protect(mutex); - - if (fd >= 0) { - /* can we allow additional client */ - if (open && (clients_max == 0 || clients_cnt < clients_max)) - AddClient(fd); - else - close_socket(fd); - } else if (fd < 0 && errno != EINTR) { - LogErrno(httpd_output_domain, "accept() failed"); - } -} - -Page * -HttpdOutput::ReadPage() -{ - if (unflushed_input >= 65536) { - /* we have fed a lot of input into the encoder, but it - didn't give anything back yet - flush now to avoid - buffer underruns */ - encoder_flush(encoder, IgnoreError()); - unflushed_input = 0; - } - - size_t size = 0; - do { - size_t nbytes = encoder_read(encoder, - buffer + size, - sizeof(buffer) - size); - if (nbytes == 0) - break; - - unflushed_input = 0; - - size += nbytes; - } while (size < sizeof(buffer)); - - if (size == 0) - return nullptr; - - return Page::Copy(buffer, size); -} - -static bool -httpd_output_enable(struct audio_output *ao, Error &error) -{ - HttpdOutput *httpd = Cast(ao); - - return httpd->Bind(error); -} - -static void -httpd_output_disable(struct audio_output *ao) -{ - HttpdOutput *httpd = Cast(ao); - - httpd->Unbind(); -} - -inline bool -HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error) -{ - if (!encoder_open(encoder, audio_format, error)) - return false; - - /* we have to remember the encoder header, i.e. the first - bytes of encoder output after opening it, because it has to - be sent to every new client */ - header = ReadPage(); - - unflushed_input = 0; - - return true; -} - -inline bool -HttpdOutput::Open(AudioFormat &audio_format, Error &error) -{ - assert(!open); - assert(clients.empty()); - - /* open the encoder */ - - if (!OpenEncoder(audio_format, error)) - return false; - - /* initialize other attributes */ - - clients_cnt = 0; - timer = new Timer(audio_format); - - open = true; - - return true; -} - -static bool -httpd_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - HttpdOutput *httpd = Cast(ao); - - assert(httpd->clients.empty()); - - const ScopeLock protect(httpd->mutex); - return httpd->Open(audio_format, error); -} - -inline void -HttpdOutput::Close() -{ - assert(open); - - open = false; - - delete timer; - - clients.clear(); - - if (header != nullptr) - header->Unref(); - - encoder_close(encoder); -} - -static void -httpd_output_close(struct audio_output *ao) -{ - HttpdOutput *httpd = Cast(ao); - - const ScopeLock protect(httpd->mutex); - httpd->Close(); -} - -void -HttpdOutput::RemoveClient(HttpdClient &client) -{ - assert(clients_cnt > 0); - - for (auto prev = clients.before_begin(), i = std::next(prev);; - prev = i, i = std::next(prev)) { - assert(i != clients.end()); - if (&*i == &client) { - clients.erase_after(prev); - clients_cnt--; - break; - } - } -} - -void -HttpdOutput::SendHeader(HttpdClient &client) const -{ - if (header != nullptr) - client.PushPage(header); -} - -static unsigned -httpd_output_delay(struct audio_output *ao) -{ - HttpdOutput *httpd = Cast(ao); - - if (!httpd->LockHasClients() && httpd->base.pause) { - /* if there's no client and this output is paused, - then httpd_output_pause() will not do anything, it - will not fill the buffer and it will not update the - timer; therefore, we reset the timer here */ - httpd->timer->Reset(); - - /* some arbitrary delay that is long enough to avoid - consuming too much CPU, and short enough to notice - new clients quickly enough */ - return 1000; - } - - return httpd->timer->IsStarted() - ? httpd->timer->GetDelay() - : 0; -} - -void -HttpdOutput::BroadcastPage(Page *page) -{ - assert(page != nullptr); - - const ScopeLock protect(mutex); - for (auto &client : clients) - client.PushPage(page); -} - -void -HttpdOutput::BroadcastFromEncoder() -{ - mutex.lock(); - for (auto &client : clients) { - if (client.GetQueueSize() > 256 * 1024) { - FormatDebug(httpd_output_domain, - "client is too slow, flushing its queue"); - client.CancelQueue(); - } - } - mutex.unlock(); - - Page *page; - while ((page = ReadPage()) != nullptr) { - BroadcastPage(page); - page->Unref(); - } -} - -inline bool -HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error) -{ - if (!encoder_write(encoder, chunk, size, error)) - return false; - - unflushed_input += size; - - BroadcastFromEncoder(); - return true; -} - -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - HttpdOutput *httpd = Cast(ao); - - if (httpd->LockHasClients()) { - if (!httpd->EncodeAndPlay(chunk, size, error)) - return 0; - } - - if (!httpd->timer->IsStarted()) - httpd->timer->Start(); - httpd->timer->Add(size); - - return size; -} - -static bool -httpd_output_pause(struct audio_output *ao) -{ - HttpdOutput *httpd = Cast(ao); - - if (httpd->LockHasClients()) { - static const char silence[1020] = { 0 }; - return httpd_output_play(ao, silence, sizeof(silence), - IgnoreError()) > 0; - } else { - return true; - } -} - -inline void -HttpdOutput::SendTag(const Tag *tag) -{ - assert(tag != nullptr); - - if (encoder->plugin.tag != nullptr) { - /* embed encoder tags */ - - /* flush the current stream, and end it */ - - encoder_pre_tag(encoder, IgnoreError()); - BroadcastFromEncoder(); - - /* send the tag to the encoder - which starts a new - stream now */ - - encoder_tag(encoder, tag, IgnoreError()); - - /* the first page generated by the encoder will now be - used as the new "header" page, which is sent to all - new clients */ - - Page *page = ReadPage(); - if (page != nullptr) { - if (header != nullptr) - header->Unref(); - header = page; - BroadcastPage(page); - } - } else { - /* use Icy-Metadata */ - - if (metadata != nullptr) - metadata->Unref(); - - static constexpr TagType types[] = { - TAG_ALBUM, TAG_ARTIST, TAG_TITLE, - TAG_NUM_OF_ITEM_TYPES - }; - - metadata = icy_server_metadata_page(*tag, &types[0]); - if (metadata != nullptr) { - const ScopeLock protect(mutex); - for (auto &client : clients) - client.PushMetaData(metadata); - } - } -} - -static void -httpd_output_tag(struct audio_output *ao, const Tag *tag) -{ - HttpdOutput *httpd = Cast(ao); - - httpd->SendTag(tag); -} - -static void -httpd_output_cancel(struct audio_output *ao) -{ - HttpdOutput *httpd = Cast(ao); - - const ScopeLock protect(httpd->mutex); - for (auto &client : httpd->clients) - client.CancelQueue(); -} - -const struct audio_output_plugin httpd_output_plugin = { - "httpd", - nullptr, - httpd_output_init, - httpd_output_finish, - httpd_output_enable, - httpd_output_disable, - httpd_output_open, - httpd_output_close, - httpd_output_delay, - httpd_output_tag, - httpd_output_play, - nullptr, - httpd_output_cancel, - httpd_output_pause, - nullptr, -}; diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx deleted file mode 100644 index c74d2bd4a..000000000 --- a/src/output/HttpdOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX -#define MPD_HTTPD_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin httpd_output_plugin; - -#endif diff --git a/src/output/Init.cxx b/src/output/Init.cxx new file mode 100644 index 000000000..eafcec432 --- /dev/null +++ b/src/output/Init.cxx @@ -0,0 +1,335 @@ +/* + * 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 "Internal.hxx" +#include "Registry.hxx" +#include "Domain.hxx" +#include "OutputAPI.hxx" +#include "filter/FilterConfig.hxx" +#include "AudioParser.hxx" +#include "mixer/MixerList.hxx" +#include "mixer/MixerType.hxx" +#include "mixer/MixerControl.hxx" +#include "mixer/plugins/SoftwareMixerPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterRegistry.hxx" +#include "filter/plugins/AutoConvertFilterPlugin.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "filter/plugins/ChainFilterPlugin.hxx" +#include "config/ConfigError.hxx" +#include "config/ConfigGlobal.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +#define AUDIO_OUTPUT_TYPE "type" +#define AUDIO_OUTPUT_NAME "name" +#define AUDIO_OUTPUT_FORMAT "format" +#define AUDIO_FILTERS "filters" + +AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin) + :plugin(_plugin), + enabled(true), really_enabled(false), + open(false), + pause(false), + allow_play(true), + in_playback_loop(false), + woken_for_play(false), + filter(nullptr), + replay_gain_filter(nullptr), + other_replay_gain_filter(nullptr), + command(AO_COMMAND_NONE) +{ + assert(plugin.finish != nullptr); + assert(plugin.open != nullptr); + assert(plugin.close != nullptr); + assert(plugin.play != nullptr); +} + +static const AudioOutputPlugin * +audio_output_detect(Error &error) +{ + LogDefault(output_domain, "Attempt to detect audio output device"); + + audio_output_plugins_for_each(plugin) { + if (plugin->test_default_device == nullptr) + continue; + + FormatDefault(output_domain, + "Attempting to detect a %s audio device", + plugin->name); + if (ao_plugin_test_default_device(plugin)) + return plugin; + } + + error.Set(output_domain, "Unable to detect an audio device"); + return nullptr; +} + +/** + * Determines the mixer type which should be used for the specified + * configuration block. + * + * This handles the deprecated options mixer_type (global) and + * mixer_enabled, if the mixer_type setting is not configured. + */ +gcc_pure +static enum mixer_type +audio_output_mixer_type(const config_param ¶m) +{ + /* read the local "mixer_type" setting */ + const char *p = param.GetBlockValue("mixer_type"); + if (p != nullptr) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!param.GetBlockValue("mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string(CONF_MIXER_TYPE, + "hardware")); +} + +static Mixer * +audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao, + const config_param ¶m, + const MixerPlugin *plugin, + Filter &filter_chain, + MixerListener &listener, + Error &error) +{ + Mixer *mixer; + + switch (audio_output_mixer_type(param)) { + case MIXER_TYPE_NONE: + case MIXER_TYPE_UNKNOWN: + return nullptr; + + case MIXER_TYPE_HARDWARE: + if (plugin == nullptr) + return nullptr; + + return mixer_new(event_loop, *plugin, ao, listener, + param, error); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(event_loop, software_mixer_plugin, ao, + listener, + config_param(), + IgnoreError()); + assert(mixer != nullptr); + + filter_chain_append(filter_chain, "software_mixer", + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + gcc_unreachable(); +} + +bool +AudioOutput::Configure(const config_param ¶m, Error &error) +{ + if (!param.IsNull()) { + name = param.GetBlockValue(AUDIO_OUTPUT_NAME); + if (name == nullptr) { + error.Set(config_domain, + "Missing \"name\" configuration"); + return false; + } + + const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); + if (p != nullptr) { + bool success = + audio_format_parse(config_audio_format, + p, true, error); + if (!success) + return false; + } else + config_audio_format.Clear(); + } else { + name = "default detected output"; + + config_audio_format.Clear(); + } + + tags = param.GetBlockValue("tags", true); + always_on = param.GetBlockValue("always_on", false); + enabled = param.GetBlockValue("enabled", true); + + /* set up the filter chain */ + + filter = filter_chain_new(); + assert(filter != nullptr); + + /* create the normalization filter (if configured) */ + + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + Filter *normalize_filter = + filter_new(&normalize_filter_plugin, config_param(), + IgnoreError()); + assert(normalize_filter != nullptr); + + filter_chain_append(*filter, "normalize", + autoconvert_filter_new(normalize_filter)); + } + + Error filter_error; + filter_chain_parse(*filter, + param.GetBlockValue(AUDIO_FILTERS, ""), + filter_error); + + // It's not really fatal - Part of the filter chain has been set up already + // and even an empty one will work (if only with unexpected behaviour) + if (filter_error.IsDefined()) + FormatError(filter_error, + "Failed to initialize filter chain for '%s'", + name); + + /* done */ + + return true; +} + +static bool +audio_output_setup(EventLoop &event_loop, AudioOutput &ao, + MixerListener &mixer_listener, + const config_param ¶m, + Error &error) +{ + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + param.GetBlockValue("replay_gain_handler", "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao.replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, IgnoreError()); + assert(ao.replay_gain_filter != nullptr); + + ao.replay_gain_serial = 0; + + ao.other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, + IgnoreError()); + assert(ao.other_replay_gain_filter != nullptr); + + ao.other_replay_gain_serial = 0; + } else { + ao.replay_gain_filter = nullptr; + ao.other_replay_gain_filter = nullptr; + } + + /* set up the mixer */ + + Error mixer_error; + ao.mixer = audio_output_load_mixer(event_loop, ao, param, + ao.plugin.mixer_plugin, + *ao.filter, + mixer_listener, + mixer_error); + if (ao.mixer == nullptr && mixer_error.IsDefined()) + FormatError(mixer_error, + "Failed to initialize hardware mixer for '%s'", + ao.name); + + /* use the hardware mixer for replay gain? */ + + if (strcmp(replay_gain_handler, "mixer") == 0) { + if (ao.mixer != nullptr) + replay_gain_filter_set_mixer(ao.replay_gain_filter, + ao.mixer, 100); + else + FormatError(output_domain, + "No such mixer for output '%s'", ao.name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao.replay_gain_filter != nullptr) { + error.Set(config_domain, + "Invalid \"replay_gain_handler\" value"); + return false; + } + + /* the "convert" filter must be the last one in the chain */ + + ao.convert_filter = filter_new(&convert_filter_plugin, config_param(), + IgnoreError()); + assert(ao.convert_filter != nullptr); + + filter_chain_append(*ao.filter, "convert", ao.convert_filter); + + return true; +} + +AudioOutput * +audio_output_new(EventLoop &event_loop, const config_param ¶m, + MixerListener &mixer_listener, + PlayerControl &pc, + Error &error) +{ + const AudioOutputPlugin *plugin; + + if (!param.IsNull()) { + const char *p; + + p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); + if (p == nullptr) { + error.Set(config_domain, + "Missing \"type\" configuration"); + return nullptr; + } + + plugin = AudioOutputPlugin_get(p); + if (plugin == nullptr) { + error.Format(config_domain, + "No such audio output plugin: %s", p); + return nullptr; + } + } else { + LogWarning(output_domain, + "No 'AudioOutput' defined in config file"); + + plugin = audio_output_detect(error); + if (plugin == nullptr) + return nullptr; + + FormatDefault(output_domain, + "Successfully detected a %s audio device", + plugin->name); + } + + AudioOutput *ao = ao_plugin_init(plugin, param, error); + if (ao == nullptr) + return nullptr; + + if (!audio_output_setup(event_loop, *ao, mixer_listener, + param, error)) { + ao_plugin_finish(ao); + return nullptr; + } + + ao->player_control = &pc; + return ao; +} diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx new file mode 100644 index 000000000..658ebd4e1 --- /dev/null +++ b/src/output/Internal.hxx @@ -0,0 +1,436 @@ +/* + * 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_OUTPUT_INTERNAL_HXX +#define MPD_OUTPUT_INTERNAL_HXX + +#include "AudioFormat.hxx" +#include "pcm/PcmBuffer.hxx" +#include "pcm/PcmDither.hxx" +#include "ReplayGainInfo.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "system/PeriodClock.hxx" + +class Error; +class Filter; +class MusicPipe; +class EventLoop; +class Mixer; +class MixerListener; +struct MusicChunk; +struct config_param; +struct PlayerControl; +struct AudioOutputPlugin; + +enum audio_output_command { + AO_COMMAND_NONE = 0, + AO_COMMAND_ENABLE, + AO_COMMAND_DISABLE, + AO_COMMAND_OPEN, + + /** + * This command is invoked when the input audio format + * changes. + */ + AO_COMMAND_REOPEN, + + AO_COMMAND_CLOSE, + AO_COMMAND_PAUSE, + + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + AO_COMMAND_DRAIN, + + AO_COMMAND_CANCEL, + AO_COMMAND_KILL +}; + +struct AudioOutput { + /** + * The device's configured display name. + */ + const char *name; + + /** + * The plugin which implements this output device. + */ + const AudioOutputPlugin &plugin; + + /** + * The #mixer object associated with this audio output device. + * May be nullptr if none is available, or if software volume is + * configured. + */ + Mixer *mixer; + + /** + * Will this output receive tags from the decoder? The + * default is true, but it may be configured to false to + * suppress sending tags to the output. + */ + bool tags; + + /** + * Shall this output always play something (i.e. silence), + * even when playback is stopped? + */ + bool always_on; + + /** + * Has the user enabled this device? + */ + bool enabled; + + /** + * Is this device actually enabled, i.e. the "enable" method + * has succeeded? + */ + bool really_enabled; + + /** + * Is the device (already) open and functional? + * + * This attribute may only be modified by the output thread. + * It is protected with #mutex: write accesses inside the + * output thread and read accesses outside of it may only be + * performed while the lock is held. + */ + bool open; + + /** + * Is the device paused? i.e. the output thread is in the + * ao_pause() loop. + */ + bool pause; + + /** + * When this flag is set, the output thread will not do any + * playback. It will wait until the flag is cleared. + * + * This is used to synchronize the "clear" operation on the + * shared music pipe during the CANCEL command. + */ + bool allow_play; + + /** + * True while the OutputThread is inside ao_play(). This + * means the PlayerThread does not need to wake up the + * OutputThread when new chunks are added to the MusicPipe, + * because the OutputThread is already watching that. + */ + bool in_playback_loop; + + /** + * Has the OutputThread been woken up to play more chunks? + * This is set by audio_output_play() and reset by ao_play() + * to reduce the number of duplicate wakeups. + */ + bool woken_for_play; + + /** + * If not nullptr, the device has failed, and this timer is used + * to estimate how long it should stay disabled (unless + * explicitly reopened with "play"). + */ + PeriodClock fail_timer; + + /** + * The configured audio format. + */ + AudioFormat config_audio_format; + + /** + * The audio_format in which audio data is received from the + * player thread (which in turn receives it from the decoder). + */ + AudioFormat in_audio_format; + + /** + * The audio_format which is really sent to the device. This + * is basically config_audio_format (if configured) or + * in_audio_format, but may have been modified by + * plugin->open(). + */ + AudioFormat out_audio_format; + + /** + * The buffer used to allocate the cross-fading result. + */ + PcmBuffer cross_fade_buffer; + + /** + * The dithering state for cross-fading two streams. + */ + PcmDither cross_fade_dither; + + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + Filter *filter; + + /** + * The replay_gain_filter_plugin instance of this audio + * output. + */ + Filter *replay_gain_filter; + + /** + * The serial number of the last replay gain info. 0 means no + * replay gain info was available. + */ + unsigned replay_gain_serial; + + /** + * The replay_gain_filter_plugin instance of this audio + * output, to be applied to the second chunk during + * cross-fading. + */ + Filter *other_replay_gain_filter; + + /** + * The serial number of the last replay gain info by the + * "other" chunk during cross-fading. + */ + unsigned other_replay_gain_serial; + + /** + * The convert_filter_plugin instance of this audio output. + * It is the last item in the filter chain, and is responsible + * for converting the input data into the appropriate format + * for this audio output. + */ + Filter *convert_filter; + + /** + * The thread handle, or nullptr if the output thread isn't + * running. + */ + Thread thread; + + /** + * The next command to be performed by the output thread. + */ + enum audio_output_command command; + + /** + * The music pipe which provides music chunks to be played. + */ + const MusicPipe *pipe; + + /** + * This mutex protects #open, #fail_timer, #current_chunk and + * #current_chunk_finished. + */ + Mutex mutex; + + /** + * This condition object wakes up the output thread after + * #command has been set. + */ + Cond cond; + + /** + * The PlayerControl object which "owns" this output. This + * object is needed to signal command completion. + */ + PlayerControl *player_control; + + /** + * The #MusicChunk which is currently being played. All + * chunks before this one may be returned to the + * #music_buffer, because they are not going to be used by + * this output anymore. + */ + const MusicChunk *current_chunk; + + /** + * Has the output finished playing #current_chunk? + */ + bool current_chunk_finished; + + AudioOutput(const AudioOutputPlugin &_plugin); + ~AudioOutput(); + + bool Configure(const config_param ¶m, Error &error); + + void StartThread(); + void StopThread(); + + void Finish(); + + bool IsOpen() const { + return open; + } + + bool IsCommandFinished() const { + return command == AO_COMMAND_NONE; + } + + /** + * Waits for command completion. + * + * Caller must lock the mutex. + */ + void WaitForCommand(); + + /** + * Sends a command, but does not wait for completion. + * + * Caller must lock the mutex. + */ + void CommandAsync(audio_output_command cmd); + + /** + * Sends a command to the #AudioOutput object and waits for + * completion. + * + * Caller must lock the mutex. + */ + void CommandWait(audio_output_command cmd); + + /** + * Lock the #AudioOutput object and execute the command + * synchronously. + */ + void LockCommandWait(audio_output_command cmd); + + /** + * Enables the device. + */ + void LockEnableWait(); + + /** + * Disables the device. + */ + void LockDisableWait(); + + void LockPauseAsync(); + + /** + * Same LockCloseWait(), but expects the lock to be + * held by the caller. + */ + void CloseWait(); + void LockCloseWait(); + + /** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ + void LockRelease(); + + void SetReplayGainMode(ReplayGainMode mode); + + /** + * Caller must lock the mutex. + */ + bool Open(const AudioFormat audio_format, const MusicPipe &mp); + + /** + * Opens or closes the device, depending on the "enabled" + * flag. + * + * @return true if the device is open + */ + bool LockUpdate(const AudioFormat audio_format, + const MusicPipe &mp); + + void LockPlay(); + + void LockDrainAsync(); + + /** + * Clear the "allow_play" flag and send the "CANCEL" command + * asynchronously. To finish the operation, the caller has to + * call LockAllowPlay(). + */ + void LockCancelAsync(); + + /** + * Set the "allow_play" and signal the thread. + */ + void LockAllowPlay(); + +private: + void CommandFinished(); + + bool Enable(); + void Disable(); + + void Open(); + void Close(bool drain); + void Reopen(); + + AudioFormat OpenFilter(AudioFormat &format, Error &error_r); + void CloseFilter(); + void ReopenFilter(); + + /** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a + * command was issued + */ + bool WaitForDelay(); + + gcc_pure + const MusicChunk *GetNextChunk() const; + + bool PlayChunk(const MusicChunk *chunk); + + /** + * Plays all remaining chunks, until the tail of the pipe has + * been reached (and no more chunks are queued), or until a + * command is received. + * + * @return true if at least one chunk has been available, + * false if the tail of the pipe was already reached + */ + bool Play(); + + void Pause(); + + /** + * The OutputThread. + */ + void Task(); + static void Task(void *arg); +}; + +/** + * Notify object used by the thread's client, i.e. we will send a + * notify signal to this object, expecting the caller to wait on it. + */ +extern struct notify audio_output_client_notify; + +AudioOutput * +audio_output_new(EventLoop &event_loop, const config_param ¶m, + MixerListener &mixer_listener, + PlayerControl &pc, + Error &error); + +void +audio_output_free(AudioOutput *ao); + +#endif diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx deleted file mode 100644 index 7ed672f95..000000000 --- a/src/output/JackOutputPlugin.cxx +++ /dev/null @@ -1,769 +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 "JackOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> - -#include <glib.h> -#include <jack/jack.h> -#include <jack/types.h> -#include <jack/ringbuffer.h> - -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -enum { - MAX_PORTS = 16, -}; - -static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); - -struct JackOutput { - struct audio_output base; - - /** - * libjack options passed to jack_client_open(). - */ - jack_options_t options; - - const char *name; - - const char *server_name; - - /* configuration */ - - char *source_ports[MAX_PORTS]; - unsigned num_source_ports; - - char *destination_ports[MAX_PORTS]; - unsigned num_destination_ports; - - size_t ringbuffer_size; - - /* the current audio format */ - AudioFormat audio_format; - - /* jack library stuff */ - jack_port_t *ports[MAX_PORTS]; - jack_client_t *client; - jack_ringbuffer_t *ringbuffer[MAX_PORTS]; - - bool shutdown; - - /** - * While this flag is set, the "process" callback generates - * silence. - */ - bool pause; - - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &jack_output_plugin, param, - error_r); - } - - void Deinitialize() { - ao_base_finish(&base); - } -}; - -static constexpr Domain jack_output_domain("jack_output"); - -/** - * Determine the number of frames guaranteed to be available on all - * channels. - */ -static jack_nframes_t -mpd_jack_available(const JackOutput *jd) -{ - size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); - - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); - if (current < min) - min = current; - } - - assert(min % jack_sample_size == 0); - - return min / jack_sample_size; -} - -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) -{ - JackOutput *jd = (JackOutput *) arg; - - if (nframes <= 0) - return 0; - - if (jd->pause) { - /* empty the ring buffers */ - - const jack_nframes_t available = mpd_jack_available(jd); - for (unsigned i = 0; i < jd->audio_format.channels; ++i) - jack_ringbuffer_read_advance(jd->ringbuffer[i], - available * jack_sample_size); - - /* generate silence while MPD is paused */ - - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } - - return 0; - } - - jack_nframes_t available = mpd_jack_available(jd); - if (available > nframes) - available = nframes; - - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - if (out == nullptr) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * jack_sample_size); - - for (jack_nframes_t f = available; f < nframes; ++f) - /* ringbuffer underrun, fill with silence */ - out[f] = 0.0; - } - - /* generate silence for the unused source ports */ - - for (unsigned i = jd->audio_format.channels; - i < jd->num_source_ports; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - if (out == nullptr) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } - - return 0; -} - -static void -mpd_jack_shutdown(void *arg) -{ - JackOutput *jd = (JackOutput *) arg; - jd->shutdown = true; -} - -static void -set_audioformat(JackOutput *jd, AudioFormat &audio_format) -{ - audio_format.sample_rate = jack_get_sample_rate(jd->client); - - if (jd->num_source_ports == 1) - audio_format.channels = 1; - else if (audio_format.channels > jd->num_source_ports) - audio_format.channels = 2; - - if (audio_format.format != SampleFormat::S16 && - audio_format.format != SampleFormat::S24_P32) - audio_format.format = SampleFormat::S24_P32; -} - -static void -mpd_jack_error(const char *msg) -{ - LogError(jack_output_domain, msg); -} - -#ifdef HAVE_JACK_SET_INFO_FUNCTION -static void -mpd_jack_info(const char *msg) -{ - LogDefault(jack_output_domain, msg); -} -#endif - -/** - * Disconnect the JACK client. - */ -static void -mpd_jack_disconnect(JackOutput *jd) -{ - assert(jd != nullptr); - assert(jd->client != nullptr); - - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = nullptr; -} - -/** - * Connect the JACK client and performs some basic setup - * (e.g. register callbacks). - */ -static bool -mpd_jack_connect(JackOutput *jd, Error &error) -{ - jack_status_t status; - - assert(jd != nullptr); - - jd->shutdown = false; - - jd->client = jack_client_open(jd->name, jd->options, &status, - jd->server_name); - if (jd->client == nullptr) { - error.Format(jack_output_domain, status, - "Failed to connect to JACK server, status=%d", - status); - return false; - } - - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); - - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - jd->ports[i] = jack_port_register(jd->client, - jd->source_ports[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == nullptr) { - error.Format(jack_output_domain, - "Cannot register output port \"%s\"", - jd->source_ports[i]); - mpd_jack_disconnect(jd); - return false; - } - } - - return true; -} - -static bool -mpd_jack_test_default_device(void) -{ - return true; -} - -static unsigned -parse_port_list(const char *source, char **dest, Error &error) -{ - char **list = g_strsplit(source, ",", 0); - unsigned n = 0; - - for (n = 0; list[n] != nullptr; ++n) { - if (n >= MAX_PORTS) { - error.Set(config_domain, - "too many port names"); - return 0; - } - - dest[n] = list[n]; - } - - g_free(list); - - if (n == 0) { - error.Format(config_domain, - "at least one port name expected"); - return 0; - } - - return n; -} - -static struct audio_output * -mpd_jack_init(const config_param ¶m, Error &error) -{ - JackOutput *jd = new JackOutput(); - - if (!jd->Initialize(param, error)) { - delete jd; - return nullptr; - } - - const char *value; - - jd->options = JackNullOption; - - jd->name = param.GetBlockValue("client_name", nullptr); - if (jd->name != nullptr) - jd->options = jack_options_t(jd->options | JackUseExactName); - else - /* if there's a no configured client name, we don't - care about the JackUseExactName option */ - jd->name = "Music Player Daemon"; - - jd->server_name = param.GetBlockValue("server_name", nullptr); - if (jd->server_name != nullptr) - jd->options = jack_options_t(jd->options | JackServerName); - - if (!param.GetBlockValue("autostart", false)) - jd->options = jack_options_t(jd->options | JackNoStartServer); - - /* configure the source ports */ - - value = param.GetBlockValue("source_ports", "left,right"); - jd->num_source_ports = parse_port_list(value, - jd->source_ports, error); - if (jd->num_source_ports == 0) - return nullptr; - - /* configure the destination ports */ - - value = param.GetBlockValue("destination_ports", nullptr); - if (value == nullptr) { - /* compatibility with MPD < 0.16 */ - value = param.GetBlockValue("ports", nullptr); - if (value != nullptr) - FormatWarning(jack_output_domain, - "deprecated option 'ports' in line %d", - param.line); - } - - if (value != nullptr) { - jd->num_destination_ports = - parse_port_list(value, - jd->destination_ports, error); - if (jd->num_destination_ports == 0) - return nullptr; - } else { - jd->num_destination_ports = 0; - } - - if (jd->num_destination_ports > 0 && - jd->num_destination_ports != jd->num_source_ports) - FormatWarning(jack_output_domain, - "number of source ports (%u) mismatches the " - "number of destination ports (%u) in line %d", - jd->num_source_ports, jd->num_destination_ports, - param.line); - - jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); - - jack_set_error_function(mpd_jack_error); - -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return &jd->base; -} - -static void -mpd_jack_finish(struct audio_output *ao) -{ - JackOutput *jd = (JackOutput *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - g_free(jd->source_ports[i]); - - for (unsigned i = 0; i < jd->num_destination_ports; ++i) - g_free(jd->destination_ports[i]); - - jd->Deinitialize(); - delete jd; -} - -static bool -mpd_jack_enable(struct audio_output *ao, Error &error) -{ - JackOutput *jd = (JackOutput *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - jd->ringbuffer[i] = nullptr; - - return mpd_jack_connect(jd, error); -} - -static void -mpd_jack_disable(struct audio_output *ao) -{ - JackOutput *jd = (JackOutput *)ao; - - if (jd->client != nullptr) - mpd_jack_disconnect(jd); - - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] != nullptr) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = nullptr; - } - } -} - -/** - * Stops the playback on the JACK connection. - */ -static void -mpd_jack_stop(JackOutput *jd) -{ - assert(jd != nullptr); - - if (jd->client == nullptr) - return; - - if (jd->shutdown) - /* the connection has failed; close it */ - mpd_jack_disconnect(jd); - else - /* the connection is alive: just stop playback */ - jack_deactivate(jd->client); -} - -static bool -mpd_jack_start(JackOutput *jd, Error &error) -{ - const char *destination_ports[MAX_PORTS], **jports; - const char *duplicate_port = nullptr; - unsigned num_destination_ports; - - assert(jd->client != nullptr); - assert(jd->audio_format.channels <= jd->num_source_ports); - - /* allocate the ring buffers on the first open(); these - persist until MPD exits. It's too unsafe to delete them - because we can never know when mpd_jack_process() gets - called */ - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] == nullptr) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); - - /* clear the ring buffer to be sure that data from - previous playbacks are gone */ - jack_ringbuffer_reset(jd->ringbuffer[i]); - } - - if ( jack_activate(jd->client) ) { - error.Set(jack_output_domain, "cannot activate client"); - mpd_jack_stop(jd); - return false; - } - - if (jd->num_destination_ports == 0) { - /* no output ports were configured - ask libjack for - defaults */ - jports = jack_get_ports(jd->client, nullptr, nullptr, - JackPortIsPhysical | JackPortIsInput); - if (jports == nullptr) { - error.Set(jack_output_domain, "no ports found"); - mpd_jack_stop(jd); - return false; - } - - assert(*jports != nullptr); - - for (num_destination_ports = 0; - num_destination_ports < MAX_PORTS && - jports[num_destination_ports] != nullptr; - ++num_destination_ports) { - FormatDebug(jack_output_domain, - "destination_port[%u] = '%s'\n", - num_destination_ports, - jports[num_destination_ports]); - destination_ports[num_destination_ports] = - jports[num_destination_ports]; - } - } else { - /* use the configured output ports */ - - num_destination_ports = jd->num_destination_ports; - memcpy(destination_ports, jd->destination_ports, - num_destination_ports * sizeof(*destination_ports)); - - jports = nullptr; - } - - assert(num_destination_ports > 0); - - if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { - /* mix stereo signal on one speaker */ - - while (num_destination_ports < jd->audio_format.channels) - destination_ports[num_destination_ports++] = - destination_ports[0]; - } else if (num_destination_ports > jd->audio_format.channels) { - if (jd->audio_format.channels == 1 && num_destination_ports > 2) { - /* mono input file: connect the one source - channel to the both destination channels */ - duplicate_port = destination_ports[1]; - num_destination_ports = 1; - } else - /* connect only as many ports as we need */ - num_destination_ports = jd->audio_format.channels; - } - - assert(num_destination_ports <= jd->num_source_ports); - - for (unsigned i = 0; i < num_destination_ports; ++i) { - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - destination_ports[i]); - if (ret != 0) { - error.Format(jack_output_domain, - "Not a valid JACK port: %s", - destination_ports[i]); - - if (jports != nullptr) - free(jports); - - mpd_jack_stop(jd); - return false; - } - } - - if (duplicate_port != nullptr) { - /* mono input file: connect the one source channel to - the both destination channels */ - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), - duplicate_port); - if (ret != 0) { - error.Format(jack_output_domain, - "Not a valid JACK port: %s", - duplicate_port); - - if (jports != nullptr) - free(jports); - - mpd_jack_stop(jd); - return false; - } - } - - if (jports != nullptr) - free(jports); - - return true; -} - -static bool -mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - JackOutput *jd = (JackOutput *)ao; - - assert(jd != nullptr); - - jd->pause = false; - - if (jd->client != nullptr && jd->shutdown) - mpd_jack_disconnect(jd); - - if (jd->client == nullptr && !mpd_jack_connect(jd, error)) - return false; - - set_audioformat(jd, audio_format); - jd->audio_format = audio_format; - - if (!mpd_jack_start(jd, error)) - return false; - - return true; -} - -static void -mpd_jack_close(gcc_unused struct audio_output *ao) -{ - JackOutput *jd = (JackOutput *)ao; - - mpd_jack_stop(jd); -} - -static unsigned -mpd_jack_delay(struct audio_output *ao) -{ - JackOutput *jd = (JackOutput *)ao; - - return jd->base.pause && jd->pause && !jd->shutdown - ? 1000 - : 0; -} - -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} - -static void -mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - while (num_samples-- > 0) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], - (const char *)&sample, - sizeof(sample)); - } - } -} - -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} - -static void -mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - while (num_samples-- > 0) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], - (const char *)&sample, - sizeof(sample)); - } - } -} - -static void -mpd_jack_write_samples(JackOutput *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.format) { - case SampleFormat::S16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case SampleFormat::S24_P32: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - gcc_unreachable(); - } -} - -static size_t -mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - JackOutput *jd = (JackOutput *)ao; - const size_t frame_size = jd->audio_format.GetFrameSize(); - size_t space = 0, space1; - - jd->pause = false; - - assert(size % frame_size == 0); - size /= frame_size; - - while (true) { - if (jd->shutdown) { - error.Set(jack_output_domain, - "Refusing to play, because " - "there is no client thread"); - return 0; - } - - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); - if (space > space1) - /* send data symmetrically */ - space = space1; - } - - if (space >= jack_sample_size) - break; - - /* XXX do something more intelligent to - synchronize */ - g_usleep(1000); - } - - space /= jack_sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; -} - -static bool -mpd_jack_pause(struct audio_output *ao) -{ - JackOutput *jd = (JackOutput *)ao; - - if (jd->shutdown) - return false; - - jd->pause = true; - - return true; -} - -const struct audio_output_plugin jack_output_plugin = { - "jack", - mpd_jack_test_default_device, - mpd_jack_init, - mpd_jack_finish, - mpd_jack_enable, - mpd_jack_disable, - mpd_jack_open, - mpd_jack_close, - mpd_jack_delay, - nullptr, - mpd_jack_play, - nullptr, - nullptr, - mpd_jack_pause, - nullptr, -}; diff --git a/src/output/JackOutputPlugin.hxx b/src/output/JackOutputPlugin.hxx deleted file mode 100644 index 908105ad2..000000000 --- a/src/output/JackOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX -#define MPD_JACK_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin jack_output_plugin; - -#endif diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx new file mode 100644 index 000000000..fe51c6100 --- /dev/null +++ b/src/output/MultipleOutputs.cxx @@ -0,0 +1,482 @@ +/* + * 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 "MultipleOutputs.hxx" +#include "PlayerControl.hxx" +#include "Internal.hxx" +#include "Domain.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "notify.hxx" + +#include <assert.h> +#include <string.h> + +MultipleOutputs::MultipleOutputs(MixerListener &_mixer_listener) + :mixer_listener(_mixer_listener), + input_audio_format(AudioFormat::Undefined()), + buffer(nullptr), pipe(nullptr), + elapsed_time(-1) +{ +} + +MultipleOutputs::~MultipleOutputs() +{ + for (auto i : outputs) { + i->LockDisableWait(); + i->Finish(); + } +} + +static AudioOutput * +LoadOutput(EventLoop &event_loop, MixerListener &mixer_listener, + PlayerControl &pc, const config_param ¶m) +{ + Error error; + AudioOutput *output = audio_output_new(event_loop, param, + mixer_listener, + pc, error); + if (output == nullptr) { + if (param.line > 0) + FormatFatalError("line %i: %s", + param.line, + error.GetMessage()); + else + FatalError(error); + } + + return output; +} + +void +MultipleOutputs::Configure(EventLoop &event_loop, PlayerControl &pc) +{ + for (const config_param *param = config_get_param(CONF_AUDIO_OUTPUT); + param != nullptr; param = param->next) { + auto output = LoadOutput(event_loop, mixer_listener, + pc, *param); + if (FindByName(output->name) != nullptr) + FormatFatalError("output devices with identical " + "names: %s", output->name); + + outputs.push_back(output); + } + + if (outputs.empty()) { + /* auto-detect device */ + const config_param empty; + auto output = LoadOutput(event_loop, mixer_listener, + pc, empty); + outputs.push_back(output); + } +} + +AudioOutput * +MultipleOutputs::FindByName(const char *name) const +{ + for (auto i : outputs) + if (strcmp(i->name, name) == 0) + return i; + + return nullptr; +} + +void +MultipleOutputs::EnableDisable() +{ + for (auto ao : outputs) { + bool enabled; + + ao->mutex.lock(); + enabled = ao->really_enabled; + ao->mutex.unlock(); + + if (ao->enabled != enabled) { + if (ao->enabled) + ao->LockEnableWait(); + else + ao->LockDisableWait(); + } + } +} + +bool +MultipleOutputs::AllFinished() const +{ + for (auto ao : outputs) { + const ScopeLock protect(ao->mutex); + if (ao->IsOpen() && !ao->IsCommandFinished()) + return false; + } + + return true; +} + +void +MultipleOutputs::WaitAll() +{ + while (!AllFinished()) + audio_output_client_notify.Wait(); +} + +void +MultipleOutputs::AllowPlay() +{ + for (auto ao : outputs) + ao->LockAllowPlay(); +} + +static void +audio_output_reset_reopen(AudioOutput *ao) +{ + const ScopeLock protect(ao->mutex); + + ao->fail_timer.Reset(); +} + +void +MultipleOutputs::ResetReopen() +{ + for (auto ao : outputs) + audio_output_reset_reopen(ao); +} + +bool +MultipleOutputs::Update() +{ + bool ret = false; + + if (!input_audio_format.IsDefined()) + return false; + + for (auto ao : outputs) + ret = ao->LockUpdate(input_audio_format, *pipe) + || ret; + + return ret; +} + +void +MultipleOutputs::SetReplayGainMode(ReplayGainMode mode) +{ + for (auto ao : outputs) + ao->SetReplayGainMode(mode); +} + +bool +MultipleOutputs::Play(MusicChunk *chunk, Error &error) +{ + assert(buffer != nullptr); + assert(pipe != nullptr); + assert(chunk != nullptr); + assert(chunk->CheckFormat(input_audio_format)); + + if (!Update()) { + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + return false; + } + + pipe->Push(chunk); + + for (auto ao : outputs) + ao->LockPlay(); + + return true; +} + +bool +MultipleOutputs::Open(const AudioFormat audio_format, + MusicBuffer &_buffer, + Error &error) +{ + bool ret = false, enabled = false; + + assert(buffer == nullptr || buffer == &_buffer); + assert((pipe == nullptr) == (buffer == nullptr)); + + buffer = &_buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(pipe == nullptr || pipe->CheckFormat(audio_format)); + + if (pipe == nullptr) + pipe = new MusicPipe(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(pipe->IsEmpty() || audio_format == input_audio_format); + + input_audio_format = audio_format; + + ResetReopen(); + EnableDisable(); + Update(); + + for (auto ao : outputs) { + if (ao->enabled) + enabled = true; + + if (ao->open) + ret = true; + } + + if (!enabled) + error.Set(output_domain, "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + + if (!ret) + /* close all devices if there was an error */ + Close(); + + return ret; +} + +/** + * Has the specified audio output already consumed this chunk? + */ +gcc_pure +static bool +chunk_is_consumed_in(const AudioOutput *ao, + gcc_unused const MusicPipe *pipe, + const MusicChunk *chunk) +{ + if (!ao->open) + return true; + + if (ao->current_chunk == nullptr) + return false; + + assert(chunk == ao->current_chunk || + pipe->Contains(ao->current_chunk)); + + if (chunk != ao->current_chunk) { + assert(chunk->next != nullptr); + return true; + } + + return ao->current_chunk_finished && chunk->next == nullptr; +} + +bool +MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const +{ + for (auto ao : outputs) { + const ScopeLock protect(ao->mutex); + if (!chunk_is_consumed_in(ao, pipe, chunk)) + return false; + } + + return true; +} + +inline void +MultipleOutputs::ClearTailChunk(gcc_unused const MusicChunk *chunk, + bool *locked) +{ + assert(chunk->next == nullptr); + assert(pipe->Contains(chunk)); + + for (unsigned i = 0, n = outputs.size(); i != n; ++i) { + AudioOutput *ao = outputs[i]; + + /* this mutex will be unlocked by the caller when it's + ready */ + ao->mutex.lock(); + locked[i] = ao->open; + + if (!locked[i]) { + ao->mutex.unlock(); + continue; + } + + assert(ao->current_chunk == chunk); + assert(ao->current_chunk_finished); + ao->current_chunk = nullptr; + } +} + +unsigned +MultipleOutputs::Check() +{ + const MusicChunk *chunk; + bool is_tail; + MusicChunk *shifted; + bool locked[outputs.size()]; + + assert(buffer != nullptr); + assert(pipe != nullptr); + + while ((chunk = pipe->Peek()) != nullptr) { + assert(!pipe->IsEmpty()); + + if (!IsChunkConsumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return pipe->GetSize(); + + if (chunk->length > 0 && chunk->times >= 0.0) + /* only update elapsed_time if the chunk + provides a defined value */ + elapsed_time = chunk->times; + + is_tail = chunk->next == nullptr; + if (is_tail) + /* this is the tail of the pipe - clear the + chunk reference in all outputs */ + ClearTailChunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = pipe->Shift(); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0, n = outputs.size(); i != n; ++i) + if (locked[i]) + outputs[i]->mutex.unlock(); + + /* return the chunk to the buffer */ + buffer->Return(shifted); + } + + return 0; +} + +bool +MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold) +{ + pc.Lock(); + + if (Check() < threshold) { + pc.Unlock(); + return true; + } + + pc.Wait(); + pc.Unlock(); + + return Check() < threshold; +} + +void +MultipleOutputs::Pause() +{ + Update(); + + for (auto ao : outputs) + ao->LockPauseAsync(); + + WaitAll(); +} + +void +MultipleOutputs::Drain() +{ + for (auto ao : outputs) + ao->LockDrainAsync(); + + WaitAll(); +} + +void +MultipleOutputs::Cancel() +{ + /* send the cancel() command to all audio outputs */ + + for (auto ao : outputs) + ao->LockCancelAsync(); + + WaitAll(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (pipe != nullptr) + pipe->Clear(*buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + AllowPlay(); + + /* invalidate elapsed_time */ + + elapsed_time = -1.0; +} + +void +MultipleOutputs::Close() +{ + for (auto ao : outputs) + ao->LockCloseWait(); + + if (pipe != nullptr) { + assert(buffer != nullptr); + + pipe->Clear(*buffer); + delete pipe; + pipe = nullptr; + } + + buffer = nullptr; + + input_audio_format.Clear(); + + elapsed_time = -1.0; +} + +void +MultipleOutputs::Release() +{ + for (auto ao : outputs) + ao->LockRelease(); + + if (pipe != nullptr) { + assert(buffer != nullptr); + + pipe->Clear(*buffer); + delete pipe; + pipe = nullptr; + } + + buffer = nullptr; + + input_audio_format.Clear(); + + elapsed_time = -1.0; +} + +void +MultipleOutputs::SongBorder() +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + elapsed_time = 0.0; +} diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx new file mode 100644 index 000000000..f8482037c --- /dev/null +++ b/src/output/MultipleOutputs.hxx @@ -0,0 +1,275 @@ +/* + * 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. + */ + +/* + * Functions for dealing with all configured (enabled) audion outputs + * at once. + * + */ + +#ifndef OUTPUT_ALL_H +#define OUTPUT_ALL_H + +#include "AudioFormat.hxx" +#include "ReplayGainInfo.hxx" +#include "Compiler.h" + +#include <vector> + +#include <assert.h> + +struct AudioFormat; +class MusicBuffer; +class MusicPipe; +class EventLoop; +class MixerListener; +struct MusicChunk; +struct PlayerControl; +struct AudioOutput; +class Error; + +class MultipleOutputs { + MixerListener &mixer_listener; + + std::vector<AudioOutput *> outputs; + + AudioFormat input_audio_format; + + /** + * The #MusicBuffer object where consumed chunks are returned. + */ + MusicBuffer *buffer; + + /** + * The #MusicPipe object which feeds all audio outputs. It is + * filled by audio_output_all_play(). + */ + MusicPipe *pipe; + + /** + * The "elapsed_time" stamp of the most recently finished + * chunk. + */ + float elapsed_time; + +public: + /** + * Load audio outputs from the configuration file and + * initialize them. + */ + MultipleOutputs(MixerListener &_mixer_listener); + ~MultipleOutputs(); + + void Configure(EventLoop &event_loop, PlayerControl &pc); + + /** + * Returns the total number of audio output devices, including + * those which are disabled right now. + */ + gcc_pure + unsigned Size() const { + return outputs.size(); + } + + /** + * Returns the "i"th audio output device. + */ + const AudioOutput &Get(unsigned i) const { + assert(i < Size()); + + return *outputs[i]; + } + + AudioOutput &Get(unsigned i) { + assert(i < Size()); + + return *outputs[i]; + } + + /** + * Returns the audio output device with the specified name. + * Returns nullptr if the name does not exist. + */ + gcc_pure + AudioOutput *FindByName(const char *name) const; + + /** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ + void EnableDisable(); + + /** + * Opens all audio outputs which are not disabled. + * + * @param audio_format the preferred audio format + * @param buffer the #music_buffer where consumed #MusicChunk objects + * should be returned + * @return true on success, false on failure + */ + bool Open(const AudioFormat audio_format, MusicBuffer &_buffer, + Error &error); + + /** + * Closes all audio outputs. + */ + void Close(); + + /** + * Closes all audio outputs. Outputs with the "always_on" + * flag are put into pause mode. + */ + void Release(); + + void SetReplayGainMode(ReplayGainMode mode); + + /** + * Enqueue a #MusicChunk object for playing, i.e. pushes it to a + * #MusicPipe. + * + * @param chunk the #MusicChunk object to be played + * @return true on success, false if no audio output was able to play + * (all closed then) + */ + bool Play(MusicChunk *chunk, Error &error); + + /** + * Checks if the output devices have drained their music pipe, and + * returns the consumed music chunks to the #music_buffer. + * + * @return the number of chunks to play left in the #MusicPipe + */ + unsigned Check(); + + /** + * Checks if the size of the #MusicPipe is below the #threshold. If + * not, it attempts to synchronize with all output threads, and waits + * until another #MusicChunk is finished. + * + * @param threshold the maximum number of chunks in the pipe + * @return true if there are less than #threshold chunks in the pipe + */ + bool Wait(PlayerControl &pc, unsigned threshold); + + /** + * Puts all audio outputs into pause mode. Most implementations will + * simply close it then. + */ + void Pause(); + + /** + * Drain all audio outputs. + */ + void Drain(); + + /** + * Try to cancel data which may still be in the device's buffers. + */ + void Cancel(); + + /** + * Indicate that a new song will begin now. + */ + void SongBorder(); + + /** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. A negative value is returned when no chunk has been + * finished yet. + */ + gcc_pure + float GetElapsedTime() const { + return elapsed_time; + } + + /** + * Returns the average volume of all available mixers (range + * 0..100). Returns -1 if no mixer can be queried. + */ + gcc_pure + int GetVolume() const; + + /** + * Sets the volume on all available mixers. + * + * @param volume the volume (range 0..100) + * @return true on success, false on failure + */ + bool SetVolume(unsigned volume); + + /** + * Similar to GetVolume(), but gets the volume only for + * software mixers. See #software_mixer_plugin. This + * function fails if no software mixer is configured. + */ + gcc_pure + int GetSoftwareVolume() const; + + /** + * Similar to SetVolume(), but sets the volume only for + * software mixers. See #software_mixer_plugin. This + * function cannot fail, because the underlying software + * mixers cannot fail either. + */ + void SetSoftwareVolume(unsigned volume); + +private: + /** + * Determine if all (active) outputs have finished the current + * command. + */ + gcc_pure + bool AllFinished() const; + + void WaitAll(); + + /** + * Signals all audio outputs which are open. + */ + void AllowPlay(); + + /** + * Resets the "reopen" flag on all audio devices. MPD should + * immediately retry to open the device instead of waiting for + * the timeout when the user wants to start playback. + */ + void ResetReopen(); + + /** + * Opens all output devices which are enabled, but closed. + * + * @return true if there is at least open output device which + * is open + */ + bool Update(); + + /** + * Has this chunk been consumed by all audio outputs? + */ + bool IsChunkConsumed(const MusicChunk *chunk) const; + + /** + * There's only one chunk left in the pipe (#pipe), and all + * audio outputs have consumed it already. Clear the + * reference. + */ + void ClearTailChunk(const MusicChunk *chunk, bool *locked); +}; + +#endif diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx deleted file mode 100644 index e2eec9dbc..000000000 --- a/src/output/NullOutputPlugin.cxx +++ /dev/null @@ -1,143 +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 "NullOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "Timer.hxx" - -#include <assert.h> - -struct NullOutput { - struct audio_output base; - - bool sync; - - Timer *timer; - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &null_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } -}; - -static struct audio_output * -null_init(const config_param ¶m, Error &error) -{ - NullOutput *nd = new NullOutput(); - - if (!nd->Initialize(param, error)) { - delete nd; - return nullptr; - } - - nd->sync = param.GetBlockValue("sync", true); - - return &nd->base; -} - -static void -null_finish(struct audio_output *ao) -{ - NullOutput *nd = (NullOutput *)ao; - - nd->Deinitialize(); - delete nd; -} - -static bool -null_open(struct audio_output *ao, AudioFormat &audio_format, - gcc_unused Error &error) -{ - NullOutput *nd = (NullOutput *)ao; - - if (nd->sync) - nd->timer = new Timer(audio_format); - - return true; -} - -static void -null_close(struct audio_output *ao) -{ - NullOutput *nd = (NullOutput *)ao; - - if (nd->sync) - delete nd->timer; -} - -static unsigned -null_delay(struct audio_output *ao) -{ - NullOutput *nd = (NullOutput *)ao; - - return nd->sync && nd->timer->IsStarted() - ? nd->timer->GetDelay() - : 0; -} - -static size_t -null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size, - gcc_unused Error &error) -{ - NullOutput *nd = (NullOutput *)ao; - Timer *timer = nd->timer; - - if (!nd->sync) - return size; - - if (!timer->IsStarted()) - timer->Start(); - timer->Add(size); - - return size; -} - -static void -null_cancel(struct audio_output *ao) -{ - NullOutput *nd = (NullOutput *)ao; - - if (!nd->sync) - return; - - nd->timer->Reset(); -} - -const struct audio_output_plugin null_output_plugin = { - "null", - nullptr, - null_init, - null_finish, - nullptr, - nullptr, - null_open, - null_close, - null_delay, - nullptr, - null_play, - nullptr, - null_cancel, - nullptr, - nullptr, -}; diff --git a/src/output/NullOutputPlugin.hxx b/src/output/NullOutputPlugin.hxx deleted file mode 100644 index a58f1cb13..000000000 --- a/src/output/NullOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX -#define MPD_NULL_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin null_output_plugin; - -#endif diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx deleted file mode 100644 index 97ebae056..000000000 --- a/src/output/OSXOutputPlugin.cxx +++ /dev/null @@ -1,433 +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 "OSXOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "util/fifo_buffer.h" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "system/ByteOrder.hxx" -#include "Log.hxx" - -#include <CoreAudio/AudioHardware.h> -#include <AudioUnit/AudioUnit.h> -#include <CoreServices/CoreServices.h> - -struct OSXOutput { - struct audio_output base; - - /* configuration settings */ - OSType component_subtype; - /* only applicable with kAudioUnitSubType_HALOutput */ - const char *device_name; - - AudioUnit au; - Mutex mutex; - Cond condition; - - struct fifo_buffer *buffer; -}; - -static constexpr Domain osx_output_domain("osx_output"); - -static bool -osx_output_test_default_device(void) -{ - /* on a Mac, this is always the default plugin, if nothing - else is configured */ - return true; -} - -static void -osx_output_configure(OSXOutput *oo, const config_param ¶m) -{ - const char *device = param.GetBlockValue("device"); - - if (device == NULL || 0 == strcmp(device, "default")) { - oo->component_subtype = kAudioUnitSubType_DefaultOutput; - oo->device_name = NULL; - } - else if (0 == strcmp(device, "system")) { - oo->component_subtype = kAudioUnitSubType_SystemOutput; - oo->device_name = NULL; - } - else { - oo->component_subtype = kAudioUnitSubType_HALOutput; - /* XXX am I supposed to g_strdup() this? */ - oo->device_name = device; - } -} - -static struct audio_output * -osx_output_init(const config_param ¶m, Error &error) -{ - OSXOutput *oo = new OSXOutput(); - if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) { - delete oo; - return NULL; - } - - osx_output_configure(oo, param); - - return &oo->base; -} - -static void -osx_output_finish(struct audio_output *ao) -{ - OSXOutput *oo = (OSXOutput *)ao; - - delete oo; -} - -static bool -osx_output_set_device(OSXOutput *oo, Error &error) -{ - bool ret = true; - OSStatus status; - UInt32 size, numdevices; - AudioDeviceID *deviceids = NULL; - char name[256]; - unsigned int i; - - if (oo->component_subtype != kAudioUnitSubType_HALOutput) - goto done; - - /* how many audio devices are there? */ - status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, - &size, - NULL); - if (status != noErr) { - error.Format(osx_output_domain, status, - "Unable to determine number of OS X audio devices: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - /* what are the available audio device IDs? */ - numdevices = size / sizeof(AudioDeviceID); - deviceids = new AudioDeviceID[numdevices]; - status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, - &size, - deviceids); - if (status != noErr) { - error.Format(osx_output_domain, status, - "Unable to determine OS X audio device IDs: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - /* which audio device matches oo->device_name? */ - for (i = 0; i < numdevices; i++) { - size = sizeof(name); - status = AudioDeviceGetProperty(deviceids[i], 0, false, - kAudioDevicePropertyDeviceName, - &size, name); - if (status != noErr) { - error.Format(osx_output_domain, status, - "Unable to determine OS X device name " - "(device %u): %s", - (unsigned int) deviceids[i], - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - if (strcmp(oo->device_name, name) == 0) { - FormatDebug(osx_output_domain, - "found matching device: ID=%u, name=%s", - (unsigned)deviceids[i], name); - break; - } - } - if (i == numdevices) { - FormatWarning(osx_output_domain, - "Found no audio device with name '%s' " - "(will use default audio device)", - oo->device_name); - goto done; - } - - status = AudioUnitSetProperty(oo->au, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &(deviceids[i]), - sizeof(AudioDeviceID)); - if (status != noErr) { - error.Format(osx_output_domain, status, - "Unable to set OS X audio output device: %s", - GetMacOSStatusCommentString(status)); - ret = false; - goto done; - } - - FormatDebug(osx_output_domain, - "set OS X audio output device ID=%u, name=%s", - (unsigned)deviceids[i], name); - -done: - delete[] deviceids; - return ret; -} - -static OSStatus -osx_render(void *vdata, - gcc_unused AudioUnitRenderActionFlags *io_action_flags, - gcc_unused const AudioTimeStamp *in_timestamp, - gcc_unused UInt32 in_bus_number, - gcc_unused UInt32 in_number_frames, - AudioBufferList *buffer_list) -{ - OSXOutput *od = (OSXOutput *) vdata; - AudioBuffer *buffer = &buffer_list->mBuffers[0]; - size_t buffer_size = buffer->mDataByteSize; - - assert(od->buffer != NULL); - - od->mutex.lock(); - - size_t nbytes; - const void *src = fifo_buffer_read(od->buffer, &nbytes); - - if (src != NULL) { - if (nbytes > buffer_size) - nbytes = buffer_size; - - memcpy(buffer->mData, src, nbytes); - fifo_buffer_consume(od->buffer, nbytes); - } else - nbytes = 0; - - od->condition.signal(); - od->mutex.unlock(); - - buffer->mDataByteSize = nbytes; - - unsigned i; - for (i = 1; i < buffer_list->mNumberBuffers; ++i) { - buffer = &buffer_list->mBuffers[i]; - buffer->mDataByteSize = 0; - } - - return 0; -} - -static bool -osx_output_enable(struct audio_output *ao, Error &error) -{ - OSXOutput *oo = (OSXOutput *)ao; - - ComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = oo->component_subtype; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - Component comp = FindNextComponent(NULL, &desc); - if (comp == 0) { - error.Set(osx_output_domain, - "Error finding OS X component"); - return false; - } - - OSStatus status = OpenAComponent(comp, &oo->au); - if (status != noErr) { - error.Format(osx_output_domain, status, - "Unable to open OS X component: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - if (!osx_output_set_device(oo, error)) { - CloseComponent(oo->au); - return false; - } - - AURenderCallbackStruct callback; - callback.inputProc = osx_render; - callback.inputProcRefCon = oo; - - ComponentResult result = - AudioUnitSetProperty(oo->au, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, - &callback, sizeof(callback)); - if (result != noErr) { - CloseComponent(oo->au); - error.Set(osx_output_domain, result, - "unable to set callback for OS X audio unit"); - return false; - } - - return true; -} - -static void -osx_output_disable(struct audio_output *ao) -{ - OSXOutput *oo = (OSXOutput *)ao; - - CloseComponent(oo->au); -} - -static void -osx_output_cancel(struct audio_output *ao) -{ - OSXOutput *od = (OSXOutput *)ao; - - const ScopeLock protect(od->mutex); - fifo_buffer_clear(od->buffer); -} - -static void -osx_output_close(struct audio_output *ao) -{ - OSXOutput *od = (OSXOutput *)ao; - - AudioOutputUnitStop(od->au); - AudioUnitUninitialize(od->au); - - fifo_buffer_free(od->buffer); -} - -static bool -osx_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - OSXOutput *od = (OSXOutput *)ao; - - AudioStreamBasicDescription stream_description; - stream_description.mSampleRate = audio_format.sample_rate; - stream_description.mFormatID = kAudioFormatLinearPCM; - stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - - switch (audio_format.format) { - case SampleFormat::S8: - stream_description.mBitsPerChannel = 8; - break; - - case SampleFormat::S16: - stream_description.mBitsPerChannel = 16; - break; - - case SampleFormat::S32: - stream_description.mBitsPerChannel = 32; - break; - - default: - audio_format.format = SampleFormat::S32; - stream_description.mBitsPerChannel = 32; - break; - } - - if (IsBigEndian()) - stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; - - stream_description.mBytesPerPacket = audio_format.GetFrameSize(); - stream_description.mFramesPerPacket = 1; - stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; - stream_description.mChannelsPerFrame = audio_format.channels; - - ComponentResult result = - AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, 0, - &stream_description, - sizeof(stream_description)); - if (result != noErr) { - error.Set(osx_output_domain, result, - "Unable to set format on OS X device"); - return false; - } - - OSStatus status = AudioUnitInitialize(od->au); - if (status != noErr) { - error.Format(osx_output_domain, status, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format.sample_rate * - audio_format.GetFrameSize()); - - status = AudioOutputUnitStart(od->au); - if (status != 0) { - AudioUnitUninitialize(od->au); - error.Format(osx_output_domain, status, - "unable to start audio output: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - return true; -} - -static size_t -osx_output_play(struct audio_output *ao, const void *chunk, size_t size, - gcc_unused Error &error) -{ - OSXOutput *od = (OSXOutput *)ao; - - const ScopeLock protect(od->mutex); - - void *dest; - size_t max_length; - - while (true) { - dest = fifo_buffer_write(od->buffer, &max_length); - if (dest != NULL) - break; - - /* wait for some free space in the buffer */ - od->condition.wait(od->mutex); - } - - if (size > max_length) - size = max_length; - - memcpy(dest, chunk, size); - fifo_buffer_append(od->buffer, size); - - return size; -} - -const struct audio_output_plugin osx_output_plugin = { - "osx", - osx_output_test_default_device, - osx_output_init, - osx_output_finish, - osx_output_enable, - osx_output_disable, - osx_output_open, - osx_output_close, - nullptr, - nullptr, - osx_output_play, - nullptr, - osx_output_cancel, - nullptr, - nullptr, -}; diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/OSXOutputPlugin.hxx deleted file mode 100644 index 2a4172880..000000000 --- a/src/output/OSXOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX -#define MPD_OSX_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin osx_output_plugin; - -#endif diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx deleted file mode 100644 index 268cf17cc..000000000 --- a/src/output/OpenALOutputPlugin.cxx +++ /dev/null @@ -1,285 +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 "OpenALOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <glib.h> - -#ifndef __APPLE__ -#include <AL/al.h> -#include <AL/alc.h> -#else -#include <OpenAL/al.h> -#include <OpenAL/alc.h> -#endif - -/* should be enough for buffer size = 2048 */ -#define NUM_BUFFERS 16 - -struct OpenALOutput { - struct audio_output base; - - const char *device_name; - ALCdevice *device; - ALCcontext *context; - ALuint buffers[NUM_BUFFERS]; - unsigned filled; - ALuint source; - ALenum format; - ALuint frequency; - - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &openal_output_plugin, param, - error_r); - } - - void Deinitialize() { - ao_base_finish(&base); - } -}; - -static constexpr Domain openal_output_domain("openal_output"); - -static ALenum -openal_audio_format(AudioFormat &audio_format) -{ - /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or - AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit - samples, while MPD uses signed samples */ - - switch (audio_format.format) { - case SampleFormat::S16: - if (audio_format.channels == 2) - return AL_FORMAT_STEREO16; - if (audio_format.channels == 1) - return AL_FORMAT_MONO16; - - /* fall back to mono */ - audio_format.channels = 1; - return openal_audio_format(audio_format); - - default: - /* fall back to 16 bit */ - audio_format.format = SampleFormat::S16; - return openal_audio_format(audio_format); - } -} - -gcc_pure -static inline ALint -openal_get_source_i(const OpenALOutput *od, ALenum param) -{ - ALint value; - alGetSourcei(od->source, param, &value); - return value; -} - -gcc_pure -static inline bool -openal_has_processed(const OpenALOutput *od) -{ - return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; -} - -gcc_pure -static inline ALint -openal_is_playing(const OpenALOutput *od) -{ - return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; -} - -static bool -openal_setup_context(OpenALOutput *od, Error &error) -{ - od->device = alcOpenDevice(od->device_name); - - if (od->device == nullptr) { - error.Format(openal_output_domain, - "Error opening OpenAL device \"%s\"", - od->device_name); - return false; - } - - od->context = alcCreateContext(od->device, nullptr); - - if (od->context == nullptr) { - error.Format(openal_output_domain, - "Error creating context for \"%s\"", - od->device_name); - alcCloseDevice(od->device); - return false; - } - - return true; -} - -static struct audio_output * -openal_init(const config_param ¶m, Error &error) -{ - const char *device_name = param.GetBlockValue("device"); - if (device_name == nullptr) { - device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); - } - - OpenALOutput *od = new OpenALOutput(); - if (!od->Initialize(param, error)) { - delete od; - return nullptr; - } - - od->device_name = device_name; - - return &od->base; -} - -static void -openal_finish(struct audio_output *ao) -{ - OpenALOutput *od = (OpenALOutput *)ao; - - od->Deinitialize(); - delete od; -} - -static bool -openal_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - OpenALOutput *od = (OpenALOutput *)ao; - - od->format = openal_audio_format(audio_format); - - if (!openal_setup_context(od, error)) { - return false; - } - - alcMakeContextCurrent(od->context); - alGenBuffers(NUM_BUFFERS, od->buffers); - - if (alGetError() != AL_NO_ERROR) { - error.Set(openal_output_domain, "Failed to generate buffers"); - return false; - } - - alGenSources(1, &od->source); - - if (alGetError() != AL_NO_ERROR) { - error.Set(openal_output_domain, "Failed to generate source"); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - return false; - } - - od->filled = 0; - od->frequency = audio_format.sample_rate; - - return true; -} - -static void -openal_close(struct audio_output *ao) -{ - OpenALOutput *od = (OpenALOutput *)ao; - - alcMakeContextCurrent(od->context); - alDeleteSources(1, &od->source); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - alcDestroyContext(od->context); - alcCloseDevice(od->device); -} - -static unsigned -openal_delay(struct audio_output *ao) -{ - OpenALOutput *od = (OpenALOutput *)ao; - - return od->filled < NUM_BUFFERS || openal_has_processed(od) - ? 0 - /* we don't know exactly how long we must wait for the - next buffer to finish, so this is a random - guess: */ - : 50; -} - -static size_t -openal_play(struct audio_output *ao, const void *chunk, size_t size, - gcc_unused Error &error) -{ - OpenALOutput *od = (OpenALOutput *)ao; - ALuint buffer; - - if (alcGetCurrentContext() != od->context) { - alcMakeContextCurrent(od->context); - } - - if (od->filled < NUM_BUFFERS) { - /* fill all buffers */ - buffer = od->buffers[od->filled]; - od->filled++; - } else { - /* wait for processed buffer */ - while (!openal_has_processed(od)) - g_usleep(10); - - alSourceUnqueueBuffers(od->source, 1, &buffer); - } - - alBufferData(buffer, od->format, chunk, size, od->frequency); - alSourceQueueBuffers(od->source, 1, &buffer); - - if (!openal_is_playing(od)) - alSourcePlay(od->source); - - return size; -} - -static void -openal_cancel(struct audio_output *ao) -{ - OpenALOutput *od = (OpenALOutput *)ao; - - od->filled = 0; - alcMakeContextCurrent(od->context); - alSourceStop(od->source); - - /* force-unqueue all buffers */ - alSourcei(od->source, AL_BUFFER, 0); - od->filled = 0; -} - -const struct audio_output_plugin openal_output_plugin = { - "openal", - nullptr, - openal_init, - openal_finish, - nullptr, - nullptr, - openal_open, - openal_close, - openal_delay, - nullptr, - openal_play, - nullptr, - openal_cancel, - nullptr, - nullptr, -}; diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/OpenALOutputPlugin.hxx deleted file mode 100644 index e1ebf3d4f..000000000 --- a/src/output/OpenALOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX -#define MPD_OPENAL_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin openal_output_plugin; - -#endif diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx deleted file mode 100644 index 68f2a38aa..000000000 --- a/src/output/OssOutputPlugin.cxx +++ /dev/null @@ -1,776 +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 "OssOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" -#include "system/fd_util.h" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" -#include "system/ByteOrder.hxx" -#include "Log.hxx" - -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> -#include <assert.h> - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include <soundcard.h> -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include <sys/soundcard.h> -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -/* We got bug reports from FreeBSD users who said that the two 24 bit - formats generate white noise on FreeBSD, but 32 bit works. This is - a workaround until we know what exactly is expected by the kernel - audio drivers. */ -#ifndef __linux__ -#undef AFMT_S24_PACKED -#undef AFMT_S24_NE -#endif - -#ifdef AFMT_S24_PACKED -#include "pcm/PcmExport.hxx" -#include "util/Manual.hxx" -#endif - -struct OssOutput { - struct audio_output base; - -#ifdef AFMT_S24_PACKED - Manual<PcmExport> pcm_export; -#endif - - int fd; - const char *device; - - /** - * The current input audio format. This is needed to reopen - * the device after cancel(). - */ - AudioFormat audio_format; - - /** - * The current OSS audio format. This is needed to reopen the - * device after cancel(). - */ - int oss_format; - - OssOutput():fd(-1), device(nullptr) {} - - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &oss_output_plugin, param, - error_r); - } - - void Deinitialize() { - ao_base_finish(&base); - } -}; - -static constexpr Domain oss_output_domain("oss_output"); - -enum oss_stat { - OSS_STAT_NO_ERROR = 0, - OSS_STAT_NOT_CHAR_DEV = -1, - OSS_STAT_NO_PERMS = -2, - OSS_STAT_DOESN_T_EXIST = -3, - OSS_STAT_OTHER = -4, -}; - -static enum oss_stat -oss_stat_device(const char *device, int *errno_r) -{ - struct stat st; - - if (0 == stat(device, &st)) { - if (!S_ISCHR(st.st_mode)) { - return OSS_STAT_NOT_CHAR_DEV; - } - } else { - *errno_r = errno; - - switch (errno) { - case ENOENT: - case ENOTDIR: - return OSS_STAT_DOESN_T_EXIST; - case EACCES: - return OSS_STAT_NO_PERMS; - default: - return OSS_STAT_OTHER; - } - } - - return OSS_STAT_NO_ERROR; -} - -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; - -static bool -oss_output_test_default_device(void) -{ - int fd, i; - - for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { - fd = open_cloexec(default_devices[i], O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - - FormatErrno(oss_output_domain, - "Error opening OSS device \"%s\"", - default_devices[i]); - } - - return false; -} - -static struct audio_output * -oss_open_default(Error &error) -{ - int err[ARRAY_SIZE(default_devices)]; - enum oss_stat ret[ARRAY_SIZE(default_devices)]; - - const config_param empty; - for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) { - ret[i] = oss_stat_device(default_devices[i], &err[i]); - if (ret[i] == OSS_STAT_NO_ERROR) { - OssOutput *od = new OssOutput(); - if (!od->Initialize(empty, error)) { - delete od; - return NULL; - } - - od->device = default_devices[i]; - return &od->base; - } - } - - for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) { - const char *dev = default_devices[i]; - switch(ret[i]) { - case OSS_STAT_NO_ERROR: - /* never reached */ - break; - case OSS_STAT_DOESN_T_EXIST: - FormatWarning(oss_output_domain, - "%s not found", dev); - break; - case OSS_STAT_NOT_CHAR_DEV: - FormatWarning(oss_output_domain, - "%s is not a character device", dev); - break; - case OSS_STAT_NO_PERMS: - FormatWarning(oss_output_domain, - "%s: permission denied", dev); - break; - case OSS_STAT_OTHER: - FormatErrno(oss_output_domain, err[i], - "Error accessing %s", dev); - } - } - - error.Set(oss_output_domain, - "error trying to open default OSS device"); - return NULL; -} - -static struct audio_output * -oss_output_init(const config_param ¶m, Error &error) -{ - const char *device = param.GetBlockValue("device"); - if (device != NULL) { - OssOutput *od = new OssOutput(); - if (!od->Initialize(param, error)) { - delete od; - return NULL; - } - - od->device = device; - return &od->base; - } - - return oss_open_default(error); -} - -static void -oss_output_finish(struct audio_output *ao) -{ - OssOutput *od = (OssOutput *)ao; - - ao_base_finish(&od->base); - delete od; -} - -#ifdef AFMT_S24_PACKED - -static bool -oss_output_enable(struct audio_output *ao, gcc_unused Error &error) -{ - OssOutput *od = (OssOutput *)ao; - - od->pcm_export.Construct(); - return true; -} - -static void -oss_output_disable(struct audio_output *ao) -{ - OssOutput *od = (OssOutput *)ao; - - od->pcm_export.Destruct(); -} - -#endif - -static void -oss_close(OssOutput *od) -{ - if (od->fd >= 0) - close(od->fd); - od->fd = -1; -} - -/** - * A tri-state type for oss_try_ioctl(). - */ -enum oss_setup_result { - SUCCESS, - ERROR, - UNSUPPORTED, -}; - -/** - * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is - * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates an #Error. - */ -static enum oss_setup_result -oss_try_ioctl_r(int fd, unsigned long request, int *value_r, - const char *msg, Error &error) -{ - assert(fd >= 0); - assert(value_r != NULL); - assert(msg != NULL); - assert(!error.IsDefined()); - - int ret = ioctl(fd, request, value_r); - if (ret >= 0) - return SUCCESS; - - if (errno == EINVAL) - return UNSUPPORTED; - - error.SetErrno(msg); - return ERROR; -} - -/** - * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is - * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates an #Error. - */ -static enum oss_setup_result -oss_try_ioctl(int fd, unsigned long request, int value, - const char *msg, Error &error_r) -{ - return oss_try_ioctl_r(fd, request, &value, msg, error_r); -} - -/** - * Set up the channel number, and attempts to find alternatives if the - * specified number is not supported. - */ -static bool -oss_setup_channels(int fd, AudioFormat &audio_format, Error &error) -{ - const char *const msg = "Failed to set channel count"; - int channels = audio_format.channels; - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error); - switch (result) { - case SUCCESS: - if (!audio_valid_channel_count(channels)) - break; - - audio_format.channels = channels; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - for (unsigned i = 1; i < 2; ++i) { - if (i == audio_format.channels) - /* don't try that again */ - continue; - - channels = i; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, - msg, error); - switch (result) { - case SUCCESS: - if (!audio_valid_channel_count(channels)) - break; - - audio_format.channels = channels; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - error.Set(oss_output_domain, msg); - return false; -} - -/** - * Set up the sample rate, and attempts to find alternatives if the - * specified sample rate is not supported. - */ -static bool -oss_setup_sample_rate(int fd, AudioFormat &audio_format, - Error &error) -{ - const char *const msg = "Failed to set sample rate"; - int sample_rate = audio_format.sample_rate; - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error); - switch (result) { - case SUCCESS: - if (!audio_valid_sample_rate(sample_rate)) - break; - - audio_format.sample_rate = sample_rate; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - static const int sample_rates[] = { 48000, 44100, 0 }; - for (unsigned i = 0; sample_rates[i] != 0; ++i) { - sample_rate = sample_rates[i]; - if (sample_rate == (int)audio_format.sample_rate) - continue; - - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error); - switch (result) { - case SUCCESS: - if (!audio_valid_sample_rate(sample_rate)) - break; - - audio_format.sample_rate = sample_rate; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - error.Set(oss_output_domain, msg); - return false; -} - -/** - * Convert a MPD sample format to its OSS counterpart. Returns - * AFMT_QUERY if there is no direct counterpart. - */ -static int -sample_format_to_oss(SampleFormat format) -{ - switch (format) { - case SampleFormat::UNDEFINED: - case SampleFormat::FLOAT: - case SampleFormat::DSD: - return AFMT_QUERY; - - case SampleFormat::S8: - return AFMT_S8; - - case SampleFormat::S16: - return AFMT_S16_NE; - - case SampleFormat::S24_P32: -#ifdef AFMT_S24_NE - return AFMT_S24_NE; -#else - return AFMT_QUERY; -#endif - - case SampleFormat::S32: -#ifdef AFMT_S32_NE - return AFMT_S32_NE; -#else - return AFMT_QUERY; -#endif - } - - return AFMT_QUERY; -} - -/** - * Convert an OSS sample format to its MPD counterpart. Returns - * SampleFormat::UNDEFINED if there is no direct counterpart. - */ -static SampleFormat -sample_format_from_oss(int format) -{ - switch (format) { - case AFMT_S8: - return SampleFormat::S8; - - case AFMT_S16_NE: - return SampleFormat::S16; - -#ifdef AFMT_S24_PACKED - case AFMT_S24_PACKED: - return SampleFormat::S24_P32; -#endif - -#ifdef AFMT_S24_NE - case AFMT_S24_NE: - return SampleFormat::S24_P32; -#endif - -#ifdef AFMT_S32_NE - case AFMT_S32_NE: - return SampleFormat::S32; -#endif - - default: - return SampleFormat::UNDEFINED; - } -} - -/** - * Probe one sample format. - * - * @return the selected sample format or SampleFormat::UNDEFINED on - * error - */ -static enum oss_setup_result -oss_probe_sample_format(int fd, SampleFormat sample_format, - SampleFormat *sample_format_r, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - PcmExport &pcm_export, -#endif - Error &error) -{ - int oss_format = sample_format_to_oss(sample_format); - if (oss_format == AFMT_QUERY) - return UNSUPPORTED; - - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error); - -#ifdef AFMT_S24_PACKED - if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) { - /* if the driver doesn't support padded 24 bit, try - packed 24 bit */ - oss_format = AFMT_S24_PACKED; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error); - } -#endif - - if (result != SUCCESS) - return result; - - sample_format = sample_format_from_oss(oss_format); - if (sample_format == SampleFormat::UNDEFINED) - return UNSUPPORTED; - - *sample_format_r = sample_format; - *oss_format_r = oss_format; - -#ifdef AFMT_S24_PACKED - pcm_export.Open(sample_format, 0, false, false, - oss_format == AFMT_S24_PACKED, - oss_format == AFMT_S24_PACKED && - !IsLittleEndian()); -#endif - - return SUCCESS; -} - -/** - * Set up the sample format, and attempts to find alternatives if the - * specified format is not supported. - */ -static bool -oss_setup_sample_format(int fd, AudioFormat &audio_format, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - PcmExport &pcm_export, -#endif - Error &error) -{ - SampleFormat mpd_format; - enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format.format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - pcm_export, -#endif - error); - switch (result) { - case SUCCESS: - audio_format.format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - if (result != UNSUPPORTED) - return result == SUCCESS; - - /* the requested sample format is not available - probe for - other formats supported by MPD */ - - static const SampleFormat sample_formats[] = { - SampleFormat::S24_P32, - SampleFormat::S32, - SampleFormat::S16, - SampleFormat::S8, - SampleFormat::UNDEFINED /* sentinel */ - }; - - for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) { - mpd_format = sample_formats[i]; - if (mpd_format == audio_format.format) - /* don't try that again */ - continue; - - result = oss_probe_sample_format(fd, mpd_format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - pcm_export, -#endif - error); - switch (result) { - case SUCCESS: - audio_format.format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - error.Set(oss_output_domain, "Failed to set sample format"); - return false; -} - -/** - * Sets up the OSS device which was opened before. - */ -static bool -oss_setup(OssOutput *od, AudioFormat &audio_format, - Error &error) -{ - return oss_setup_channels(od->fd, audio_format, error) && - oss_setup_sample_rate(od->fd, audio_format, error) && - oss_setup_sample_format(od->fd, audio_format, &od->oss_format, -#ifdef AFMT_S24_PACKED - od->pcm_export, -#endif - error); -} - -/** - * Reopen the device with the saved audio_format, without any probing. - */ -static bool -oss_reopen(OssOutput *od, Error &error) -{ - assert(od->fd < 0); - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - error.FormatErrno("Error opening OSS device \"%s\"", - od->device); - return false; - } - - enum oss_setup_result result; - - const char *const msg1 = "Failed to set channel count"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, - od->audio_format.channels, msg1, error); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - error.Set(oss_output_domain, msg1); - return false; - } - - const char *const msg2 = "Failed to set sample rate"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, - od->audio_format.sample_rate, msg2, error); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - error.Set(oss_output_domain, msg2); - return false; - } - - const char *const msg3 = "Failed to set sample format"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, - od->oss_format, - msg3, error); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - error.Set(oss_output_domain, msg3); - return false; - } - - return true; -} - -static bool -oss_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - OssOutput *od = (OssOutput *)ao; - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - error.FormatErrno("Error opening OSS device \"%s\"", - od->device); - return false; - } - - if (!oss_setup(od, audio_format, error)) { - oss_close(od); - return false; - } - - od->audio_format = audio_format; - return true; -} - -static void -oss_output_close(struct audio_output *ao) -{ - OssOutput *od = (OssOutput *)ao; - - oss_close(od); -} - -static void -oss_output_cancel(struct audio_output *ao) -{ - OssOutput *od = (OssOutput *)ao; - - if (od->fd >= 0) { - ioctl(od->fd, SNDCTL_DSP_RESET, 0); - oss_close(od); - } -} - -static size_t -oss_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - OssOutput *od = (OssOutput *)ao; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_reopen(od, error)) - return 0; - -#ifdef AFMT_S24_PACKED - chunk = od->pcm_export->Export(chunk, size, size); -#endif - - while (true) { - ret = write(od->fd, chunk, size); - if (ret > 0) { -#ifdef AFMT_S24_PACKED - ret = od->pcm_export->CalcSourceSize(ret); -#endif - return ret; - } - - if (ret < 0 && errno != EINTR) { - error.FormatErrno("Write error on %s", od->device); - return 0; - } - } -} - -const struct audio_output_plugin oss_output_plugin = { - "oss", - oss_output_test_default_device, - oss_output_init, - oss_output_finish, -#ifdef AFMT_S24_PACKED - oss_output_enable, - oss_output_disable, -#else - nullptr, - nullptr, -#endif - oss_output_open, - oss_output_close, - nullptr, - nullptr, - oss_output_play, - nullptr, - oss_output_cancel, - nullptr, - - &oss_mixer_plugin, -}; diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx deleted file mode 100644 index 6c5c9530b..000000000 --- a/src/output/OssOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX -#define MPD_OSS_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin oss_output_plugin; - -#endif diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx new file mode 100644 index 000000000..e0fd6eec8 --- /dev/null +++ b/src/output/OutputAPI.hxx @@ -0,0 +1,33 @@ +/* + * 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_OUTPUT_API_HXX +#define MPD_OUTPUT_API_HXX + +// IWYU pragma: begin_exports + +#include "OutputPlugin.hxx" +#include "Internal.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +#endif diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx new file mode 100644 index 000000000..6afb70cf1 --- /dev/null +++ b/src/output/OutputCommand.cxx @@ -0,0 +1,106 @@ +/* + * 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. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#include "config.h" +#include "OutputCommand.hxx" +#include "MultipleOutputs.hxx" +#include "Internal.hxx" +#include "PlayerControl.hxx" +#include "mixer/MixerControl.hxx" +#include "Idle.hxx" + +extern unsigned audio_output_state_version; + +bool +audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) +{ + if (idx >= outputs.Size()) + return false; + + AudioOutput &ao = outputs.Get(idx); + if (ao.enabled) + return true; + + ao.enabled = true; + idle_add(IDLE_OUTPUT); + + ao.player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) +{ + if (idx >= outputs.Size()) + return false; + + AudioOutput &ao = outputs.Get(idx); + if (!ao.enabled) + return true; + + ao.enabled = false; + idle_add(IDLE_OUTPUT); + + Mixer *mixer = ao.mixer; + if (mixer != nullptr) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + + ao.player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) +{ + if (idx >= outputs.Size()) + return false; + + AudioOutput &ao = outputs.Get(idx); + const bool enabled = ao.enabled = !ao.enabled; + idle_add(IDLE_OUTPUT); + + if (!enabled) { + Mixer *mixer = ao.mixer; + if (mixer != nullptr) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + } + + ao.player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx new file mode 100644 index 000000000..53fc5c95e --- /dev/null +++ b/src/output/OutputCommand.hxx @@ -0,0 +1,53 @@ +/* + * 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. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX + +class MultipleOutputs; + +/** + * Enables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_enable_index(MultipleOutputs &outputs, unsigned idx); + +/** + * Disables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_disable_index(MultipleOutputs &outputs, unsigned idx); + +/** + * Toggles an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx); + +#endif diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx new file mode 100644 index 000000000..89428fa87 --- /dev/null +++ b/src/output/OutputControl.cxx @@ -0,0 +1,295 @@ +/* + * 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 "Internal.hxx" +#include "OutputPlugin.hxx" +#include "Domain.hxx" +#include "mixer/MixerControl.hxx" +#include "notify.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> + +/** after a failure, wait this number of seconds before + automatically reopening the device */ +static constexpr unsigned REOPEN_AFTER = 10; + +struct notify audio_output_client_notify; + +void +AudioOutput::WaitForCommand() +{ + while (!IsCommandFinished()) { + mutex.unlock(); + audio_output_client_notify.Wait(); + mutex.lock(); + } +} + +void +AudioOutput::CommandAsync(audio_output_command cmd) +{ + assert(IsCommandFinished()); + + command = cmd; + cond.signal(); +} + +void +AudioOutput::CommandWait(audio_output_command cmd) +{ + CommandAsync(cmd); + WaitForCommand(); +} + +void +AudioOutput::LockCommandWait(audio_output_command cmd) +{ + const ScopeLock protect(mutex); + CommandWait(cmd); +} + +void +AudioOutput::SetReplayGainMode(ReplayGainMode mode) +{ + if (replay_gain_filter != nullptr) + replay_gain_filter_set_mode(replay_gain_filter, mode); + if (other_replay_gain_filter != nullptr) + replay_gain_filter_set_mode(other_replay_gain_filter, mode); +} + +void +AudioOutput::LockEnableWait() +{ + if (!thread.IsDefined()) { + if (plugin.enable == nullptr) { + /* don't bother to start the thread now if the + device doesn't even have a enable() method; + just assign the variable and we're done */ + really_enabled = true; + return; + } + + StartThread(); + } + + LockCommandWait(AO_COMMAND_ENABLE); +} + +void +AudioOutput::LockDisableWait() +{ + if (!thread.IsDefined()) { + if (plugin.disable == nullptr) + really_enabled = false; + else + /* if there's no thread yet, the device cannot + be enabled */ + assert(!really_enabled); + + return; + } + + LockCommandWait(AO_COMMAND_DISABLE); +} + +inline bool +AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) +{ + assert(allow_play); + assert(audio_format.IsValid()); + + fail_timer.Reset(); + + if (open && audio_format == in_audio_format) { + assert(pipe == &mp || (always_on && pause)); + + if (pause) { + current_chunk = nullptr; + pipe = ∓ + + /* unpause with the CANCEL command; this is a + hack, but suits well for forcing the thread + to leave the ao_pause() thread, and we need + to flush the device buffer anyway */ + + /* we're not using audio_output_cancel() here, + because that function is asynchronous */ + CommandWait(AO_COMMAND_CANCEL); + } + + return true; + } + + in_audio_format = audio_format; + current_chunk = nullptr; + + pipe = ∓ + + if (!thread.IsDefined()) + StartThread(); + + CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + const bool open2 = open; + + if (open2 && mixer != nullptr) { + Error error; + if (!mixer_open(mixer, error)) + FormatWarning(output_domain, + "Failed to open mixer for '%s'", name); + } + + return open2; +} + +void +AudioOutput::CloseWait() +{ + assert(allow_play); + + if (mixer != nullptr) + mixer_auto_close(mixer); + + assert(!open || !fail_timer.IsDefined()); + + if (open) + CommandWait(AO_COMMAND_CLOSE); + else + fail_timer.Reset(); +} + +bool +AudioOutput::LockUpdate(const AudioFormat audio_format, + const MusicPipe &mp) +{ + const ScopeLock protect(mutex); + + if (enabled && really_enabled) { + if (fail_timer.Check(REOPEN_AFTER * 1000)) { + return Open(audio_format, mp); + } + } else if (IsOpen()) + CloseWait(); + + return false; +} + +void +AudioOutput::LockPlay() +{ + const ScopeLock protect(mutex); + + assert(allow_play); + + if (IsOpen() && !in_playback_loop && !woken_for_play) { + woken_for_play = true; + cond.signal(); + } +} + +void +AudioOutput::LockPauseAsync() +{ + if (mixer != nullptr && plugin.pause == nullptr) + /* the device has no pause mode: close the mixer, + unless its "global" flag is set (checked by + mixer_auto_close()) */ + mixer_auto_close(mixer); + + const ScopeLock protect(mutex); + + assert(allow_play); + if (IsOpen()) + CommandAsync(AO_COMMAND_PAUSE); +} + +void +AudioOutput::LockDrainAsync() +{ + const ScopeLock protect(mutex); + + assert(allow_play); + if (IsOpen()) + CommandAsync(AO_COMMAND_DRAIN); +} + +void +AudioOutput::LockCancelAsync() +{ + const ScopeLock protect(mutex); + + if (IsOpen()) { + allow_play = false; + CommandAsync(AO_COMMAND_CANCEL); + } +} + +void +AudioOutput::LockAllowPlay() +{ + const ScopeLock protect(mutex); + + allow_play = true; + if (IsOpen()) + cond.signal(); +} + +void +AudioOutput::LockRelease() +{ + if (always_on) + LockPauseAsync(); + else + LockCloseWait(); +} + +void +AudioOutput::LockCloseWait() +{ + assert(!open || !fail_timer.IsDefined()); + + const ScopeLock protect(mutex); + CloseWait(); +} + +void +AudioOutput::StopThread() +{ + assert(thread.IsDefined()); + assert(allow_play); + + LockCommandWait(AO_COMMAND_KILL); + thread.Join(); +} + +void +AudioOutput::Finish() +{ + LockCloseWait(); + + assert(!fail_timer.IsDefined()); + + if (thread.IsDefined()) + StopThread(); + + audio_output_free(this); +} diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx new file mode 100644 index 000000000..fff3fe406 --- /dev/null +++ b/src/output/OutputControl.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_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +struct AudioOutput; + +#endif diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx new file mode 100644 index 000000000..33bb854d4 --- /dev/null +++ b/src/output/OutputPlugin.cxx @@ -0,0 +1,109 @@ +/* + * 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 "OutputPlugin.hxx" +#include "Internal.hxx" + +AudioOutput * +ao_plugin_init(const AudioOutputPlugin *plugin, + const config_param ¶m, + Error &error) +{ + assert(plugin != nullptr); + assert(plugin->init != nullptr); + + return plugin->init(param, error); +} + +void +ao_plugin_finish(AudioOutput *ao) +{ + ao->plugin.finish(ao); +} + +bool +ao_plugin_enable(AudioOutput *ao, Error &error_r) +{ + return ao->plugin.enable != nullptr + ? ao->plugin.enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(AudioOutput *ao) +{ + if (ao->plugin.disable != nullptr) + ao->plugin.disable(ao); +} + +bool +ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + return ao->plugin.open(ao, audio_format, error); +} + +void +ao_plugin_close(AudioOutput *ao) +{ + ao->plugin.close(ao); +} + +unsigned +ao_plugin_delay(AudioOutput *ao) +{ + return ao->plugin.delay != nullptr + ? ao->plugin.delay(ao) + : 0; +} + +void +ao_plugin_send_tag(AudioOutput *ao, const Tag *tag) +{ + if (ao->plugin.send_tag != nullptr) + ao->plugin.send_tag(ao, tag); +} + +size_t +ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + return ao->plugin.play(ao, chunk, size, error); +} + +void +ao_plugin_drain(AudioOutput *ao) +{ + if (ao->plugin.drain != nullptr) + ao->plugin.drain(ao); +} + +void +ao_plugin_cancel(AudioOutput *ao) +{ + if (ao->plugin.cancel != nullptr) + ao->plugin.cancel(ao); +} + +bool +ao_plugin_pause(AudioOutput *ao) +{ + return ao->plugin.pause != nullptr && ao->plugin.pause(ao); +} diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx new file mode 100644 index 000000000..00fa36bc0 --- /dev/null +++ b/src/output/OutputPlugin.hxx @@ -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. + */ + +#ifndef MPD_OUTPUT_PLUGIN_HXX +#define MPD_OUTPUT_PLUGIN_HXX + +#include "Compiler.h" + +#include <stddef.h> + +struct config_param; +struct AudioFormat; +struct Tag; +struct AudioOutput; +struct MixerPlugin; +class Error; + +/** + * A plugin which controls an audio output device. + */ +struct AudioOutputPlugin { + /** + * the plugin's name + */ + const char *name; + + /** + * Test if this plugin can provide a default output, in case + * none has been configured. This method is optional. + */ + bool (*test_default_device)(void); + + /** + * Configure and initialize the device, but do not open it + * yet. + * + * @param param the configuration section, or nullptr if there is + * no configuration + * @return nullptr on error, or an opaque pointer to the plugin's + * data + */ + AudioOutput *(*init)(const config_param ¶m, + Error &error); + + /** + * Free resources allocated by this device. + */ + void (*finish)(AudioOutput *data); + + /** + * Enable the device. This may allocate resources, preparing + * for the device to be opened. Enabling a device cannot + * fail: if an error occurs during that, it should be reported + * by the open() method. + * + * @return true on success, false on error + */ + bool (*enable)(AudioOutput *data, Error &error); + + /** + * Disables the device. It is closed before this method is + * called. + */ + void (*disable)(AudioOutput *data); + + /** + * Really open the device. + * + * @param audio_format the audio format in which data is going + * to be delivered; may be modified by the plugin + */ + bool (*open)(AudioOutput *data, AudioFormat &audio_format, + Error &error); + + /** + * Close the device. + */ + void (*close)(AudioOutput *data); + + /** + * Returns a positive number if the output thread shall delay + * the next call to play() or pause(). This should be + * implemented instead of doing a sleep inside the plugin, + * because this allows MPD to listen to commands meanwhile. + * + * @return the number of milliseconds to wait + */ + unsigned (*delay)(AudioOutput *data); + + /** + * Display metadata for the next chunk. Optional method, + * because not all devices can display metadata. + */ + void (*send_tag)(AudioOutput *data, const Tag *tag); + + /** + * Play a chunk of audio data. + * + * @return the number of bytes played, or 0 on error + */ + size_t (*play)(AudioOutput *data, + const void *chunk, size_t size, + Error &error); + + /** + * Wait until the device has finished playing. + */ + void (*drain)(AudioOutput *data); + + /** + * Try to cancel data which may still be in the device's + * buffers. + */ + void (*cancel)(AudioOutput *data); + + /** + * Pause the device. If supported, it may perform a special + * action, which keeps the device open, but does not play + * anything. Output plugins like "shout" might want to play + * silence during pause, so their clients won't be + * disconnected. Plugins which do not support pausing will + * simply be closed, and have to be reopened when unpaused. + * + * @return false on error (output will be closed then), true + * for continue to pause + */ + bool (*pause)(AudioOutput *data); + + /** + * The mixer plugin associated with this output plugin. This + * may be nullptr if no mixer plugin is implemented. When + * created, this mixer plugin gets the same #config_param as + * this audio output device. + */ + const MixerPlugin *mixer_plugin; +}; + +static inline bool +ao_plugin_test_default_device(const AudioOutputPlugin *plugin) +{ + return plugin->test_default_device != nullptr + ? plugin->test_default_device() + : false; +} + +gcc_malloc +AudioOutput * +ao_plugin_init(const AudioOutputPlugin *plugin, + const config_param ¶m, + Error &error); + +void +ao_plugin_finish(AudioOutput *ao); + +bool +ao_plugin_enable(AudioOutput *ao, Error &error); + +void +ao_plugin_disable(AudioOutput *ao); + +bool +ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error); + +void +ao_plugin_close(AudioOutput *ao); + +gcc_pure +unsigned +ao_plugin_delay(AudioOutput *ao); + +void +ao_plugin_send_tag(AudioOutput *ao, const Tag *tag); + +size_t +ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error); + +void +ao_plugin_drain(AudioOutput *ao); + +void +ao_plugin_cancel(AudioOutput *ao); + +bool +ao_plugin_pause(AudioOutput *ao); + +#endif diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx new file mode 100644 index 000000000..414a86e32 --- /dev/null +++ b/src/output/OutputPrint.cxx @@ -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. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#include "config.h" +#include "OutputPrint.hxx" +#include "MultipleOutputs.hxx" +#include "Internal.hxx" +#include "client/Client.hxx" + +void +printAudioDevices(Client &client, const MultipleOutputs &outputs) +{ + for (unsigned i = 0, n = outputs.Size(); i != n; ++i) { + const AudioOutput &ao = outputs.Get(i); + + client_printf(client, + "outputid: %i\n" + "outputname: %s\n" + "outputenabled: %i\n", + i, ao.name, ao.enabled); + } +} diff --git a/src/output/OutputPrint.hxx b/src/output/OutputPrint.hxx new file mode 100644 index 000000000..29aa2b11c --- /dev/null +++ b/src/output/OutputPrint.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX + +class Client; +class MultipleOutputs; + +void +printAudioDevices(Client &client, const MultipleOutputs &outputs); + +#endif diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx new file mode 100644 index 000000000..fb01b1c65 --- /dev/null +++ b/src/output/OutputState.cxx @@ -0,0 +1,88 @@ +/* + * 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. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#include "config.h" +#include "OutputState.hxx" +#include "MultipleOutputs.hxx" +#include "Internal.hxx" +#include "Domain.hxx" +#include "Log.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "util/StringUtil.hxx" + +#include <assert.h> +#include <stdlib.h> + +#define AUDIO_DEVICE_STATE "audio_device_state:" + +unsigned audio_output_state_version; + +void +audio_output_state_save(BufferedOutputStream &os, + const MultipleOutputs &outputs) +{ + for (unsigned i = 0, n = outputs.Size(); i != n; ++i) { + const AudioOutput &ao = outputs.Get(i); + + os.Format(AUDIO_DEVICE_STATE "%d:%s\n", ao.enabled, ao.name); + } +} + +bool +audio_output_state_read(const char *line, MultipleOutputs &outputs) +{ + long value; + char *endptr; + const char *name; + + if (!StringStartsWith(line, AUDIO_DEVICE_STATE)) + return false; + + line += sizeof(AUDIO_DEVICE_STATE) - 1; + + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; + + if (value != 0) + /* state is "enabled": no-op */ + return true; + + name = endptr + 1; + AudioOutput *ao = outputs.FindByName(name); + if (ao == NULL) { + FormatDebug(output_domain, + "Ignoring device state for '%s'", name); + return true; + } + + ao->enabled = false; + return true; +} + +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; +} diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx new file mode 100644 index 000000000..47f8429d5 --- /dev/null +++ b/src/output/OutputState.hxx @@ -0,0 +1,46 @@ +/* + * 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. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX + +class MultipleOutputs; +class BufferedOutputStream; + +bool +audio_output_state_read(const char *line, MultipleOutputs &outputs); + +void +audio_output_state_save(BufferedOutputStream &os, + const MultipleOutputs &outputs); + +/** + * Generates a version number for the current state of the audio + * outputs. This is used by timer_save_state_file() to determine + * whether the state has changed and the state file should be saved. + */ +unsigned +audio_output_state_get_version(void); + +#endif diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx new file mode 100644 index 000000000..98e43cffd --- /dev/null +++ b/src/output/OutputThread.cxx @@ -0,0 +1,655 @@ +/* + * 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 "Internal.hxx" +#include "OutputAPI.hxx" +#include "Domain.hxx" +#include "pcm/PcmMix.hxx" +#include "notify.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/plugins/ConvertFilterPlugin.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "thread/Util.hxx" +#include "thread/Slack.hxx" +#include "thread/Name.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "Log.hxx" +#include "Compiler.h" + +#include <assert.h> +#include <string.h> + +void +AudioOutput::CommandFinished() +{ + assert(command != AO_COMMAND_NONE); + command = AO_COMMAND_NONE; + + mutex.unlock(); + audio_output_client_notify.Signal(); + mutex.lock(); +} + +inline bool +AudioOutput::Enable() +{ + if (really_enabled) + return true; + + mutex.unlock(); + Error error; + bool success = ao_plugin_enable(this, error); + mutex.lock(); + if (!success) { + FormatError(error, + "Failed to enable \"%s\" [%s]", + name, plugin.name); + return false; + } + + really_enabled = true; + return true; +} + +inline void +AudioOutput::Disable() +{ + if (open) + Close(false); + + if (really_enabled) { + really_enabled = false; + + mutex.unlock(); + ao_plugin_disable(this); + mutex.lock(); + } +} + +inline AudioFormat +AudioOutput::OpenFilter(AudioFormat &format, Error &error_r) +{ + assert(format.IsValid()); + + /* the replay_gain filter cannot fail here */ + if (replay_gain_filter != nullptr && + !replay_gain_filter->Open(format, error_r).IsDefined()) + return AudioFormat::Undefined(); + + if (other_replay_gain_filter != nullptr && + !other_replay_gain_filter->Open(format, error_r).IsDefined()) { + if (replay_gain_filter != nullptr) + replay_gain_filter->Close(); + return AudioFormat::Undefined(); + } + + const AudioFormat af = filter->Open(format, error_r); + if (!af.IsDefined()) { + if (replay_gain_filter != nullptr) + replay_gain_filter->Close(); + if (other_replay_gain_filter != nullptr) + other_replay_gain_filter->Close(); + } + + return af; +} + +void +AudioOutput::CloseFilter() +{ + if (replay_gain_filter != nullptr) + replay_gain_filter->Close(); + if (other_replay_gain_filter != nullptr) + other_replay_gain_filter->Close(); + + filter->Close(); +} + +inline void +AudioOutput::Open() +{ + bool success; + Error error; + struct audio_format_string af_string; + + assert(!open); + assert(pipe != nullptr); + assert(current_chunk == nullptr); + assert(in_audio_format.IsValid()); + + fail_timer.Reset(); + + /* enable the device (just in case the last enable has failed) */ + + if (!Enable()) + /* still no luck */ + return; + + /* open the filter */ + + const AudioFormat filter_audio_format = + OpenFilter(in_audio_format, error); + if (!filter_audio_format.IsDefined()) { + FormatError(error, "Failed to open filter for \"%s\" [%s]", + name, plugin.name); + + fail_timer.Update(); + return; + } + + assert(filter_audio_format.IsValid()); + + out_audio_format = filter_audio_format; + out_audio_format.ApplyMask(config_audio_format); + + mutex.unlock(); + success = ao_plugin_open(this, out_audio_format, error); + mutex.lock(); + + assert(!open); + + if (!success) { + FormatError(error, "Failed to open \"%s\" [%s]", + name, plugin.name); + + CloseFilter(); + fail_timer.Update(); + return; + } + + if (!convert_filter_set(convert_filter, out_audio_format, + error)) { + FormatError(error, "Failed to convert for \"%s\" [%s]", + name, plugin.name); + + CloseFilter(); + fail_timer.Update(); + return; + } + + open = true; + + FormatDebug(output_domain, + "opened plugin=%s name=\"%s\" audio_format=%s", + plugin.name, name, + audio_format_to_string(out_audio_format, &af_string)); + + if (in_audio_format != out_audio_format) + FormatDebug(output_domain, "converting from %s", + audio_format_to_string(in_audio_format, + &af_string)); +} + +void +AudioOutput::Close(bool drain) +{ + assert(open); + + pipe = nullptr; + + current_chunk = nullptr; + open = false; + + mutex.unlock(); + + if (drain) + ao_plugin_drain(this); + else + ao_plugin_cancel(this); + + ao_plugin_close(this); + CloseFilter(); + + mutex.lock(); + + FormatDebug(output_domain, "closed plugin=%s name=\"%s\"", + plugin.name, name); +} + +void +AudioOutput::ReopenFilter() +{ + Error error; + + CloseFilter(); + const AudioFormat filter_audio_format = + OpenFilter(in_audio_format, error); + if (!filter_audio_format.IsDefined() || + !convert_filter_set(convert_filter, out_audio_format, + error)) { + FormatError(error, + "Failed to open filter for \"%s\" [%s]", + name, plugin.name); + + /* this is a little code duplication from Close(), + but we cannot call this function because we must + not call filter_close(filter) again */ + + pipe = nullptr; + + current_chunk = nullptr; + open = false; + fail_timer.Update(); + + mutex.unlock(); + ao_plugin_close(this); + mutex.lock(); + + return; + } +} + +void +AudioOutput::Reopen() +{ + if (!config_audio_format.IsFullyDefined()) { + if (open) { + const MusicPipe *mp = pipe; + Close(true); + pipe = mp; + } + + /* no audio format is configured: copy in->out, let + the output's open() method determine the effective + out_audio_format */ + out_audio_format = in_audio_format; + out_audio_format.ApplyMask(config_audio_format); + } + + if (open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ReopenFilter(); + else + Open(); +} + +/** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a command + * was issued + */ +inline bool +AudioOutput::WaitForDelay() +{ + while (true) { + unsigned delay = ao_plugin_delay(this); + if (delay == 0) + return true; + + (void)cond.timed_wait(mutex, delay); + + if (command != AO_COMMAND_NONE) + return false; + } +} + +static ConstBuffer<void> +ao_chunk_data(AudioOutput *ao, const MusicChunk *chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p) +{ + assert(chunk != nullptr); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); + + ConstBuffer<void> data(chunk->data, chunk->length); + + (void)ao; + + assert(data.size % ao->in_audio_format.GetFrameSize() == 0); + + if (!data.IsEmpty() && replay_gain_filter != nullptr) { + if (chunk->replay_gain_serial != *replay_gain_serial_p) { + replay_gain_filter_set_info(replay_gain_filter, + chunk->replay_gain_serial != 0 + ? &chunk->replay_gain_info + : nullptr); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + Error error; + data = replay_gain_filter->FilterPCM(data, error); + if (data.IsNull()) + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin.name); + } + + return data; +} + +static ConstBuffer<void> +ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk) +{ + ConstBuffer<void> data = + ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial); + if (data.IsEmpty()) + return data; + + /* cross-fade */ + + if (chunk->other != nullptr) { + ConstBuffer<void> other_data = + ao_chunk_data(ao, chunk->other, + ao->other_replay_gain_filter, + &ao->other_replay_gain_serial); + if (other_data.IsNull()) + return nullptr; + + if (other_data.IsEmpty()) + return data; + + /* if the "other" chunk is longer, then that trailer + is used as-is, without mixing; it is part of the + "next" song being faded in, and if there's a rest, + it means cross-fading ends here */ + + if (data.size > other_data.size) + data.size = other_data.size; + + void *dest = ao->cross_fade_buffer.Get(other_data.size); + memcpy(dest, other_data.data, other_data.size); + if (!pcm_mix(ao->cross_fade_dither, dest, data.data, data.size, + ao->in_audio_format.format, + 1.0 - chunk->mix_ratio)) { + FormatError(output_domain, + "Cannot cross-fade format %s", + sample_format_to_string(ao->in_audio_format.format)); + return nullptr; + } + + data.data = dest; + data.size = other_data.size; + } + + /* apply filter chain */ + + Error error; + data = ao->filter->FilterPCM(data, error); + if (data.IsNull()) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin.name); + return nullptr; + } + + return data; +} + +inline bool +AudioOutput::PlayChunk(const MusicChunk *chunk) +{ + assert(filter != nullptr); + + if (tags && gcc_unlikely(chunk->tag != nullptr)) { + mutex.unlock(); + ao_plugin_send_tag(this, chunk->tag); + mutex.lock(); + } + + auto data = ConstBuffer<char>::FromVoid(ao_filter_chunk(this, chunk)); + if (data.IsNull()) { + Close(false); + + /* don't automatically reopen this device for 10 + seconds */ + fail_timer.Update(); + return false; + } + + Error error; + + while (!data.IsEmpty() && command == AO_COMMAND_NONE) { + if (!WaitForDelay()) + break; + + mutex.unlock(); + size_t nbytes = ao_plugin_play(this, data.data, data.size, + error); + mutex.lock(); + if (nbytes == 0) { + /* play()==0 means failure */ + FormatError(error, "\"%s\" [%s] failed to play", + name, plugin.name); + + Close(false); + + /* don't automatically reopen this device for + 10 seconds */ + assert(!fail_timer.IsDefined()); + fail_timer.Update(); + + return false; + } + + assert(nbytes <= data.size); + assert(nbytes % out_audio_format.GetFrameSize() == 0); + + data.data += nbytes; + data.size -= nbytes; + } + + return true; +} + +inline const MusicChunk * +AudioOutput::GetNextChunk() const +{ + return current_chunk != nullptr + /* continue the previous play() call */ + ? current_chunk->next + /* get the first chunk from the pipe */ + : pipe->Peek(); +} + +inline bool +AudioOutput::Play() +{ + assert(pipe != nullptr); + + const MusicChunk *chunk = GetNextChunk(); + if (chunk == nullptr) + /* no chunk available */ + return false; + + current_chunk_finished = false; + + assert(!in_playback_loop); + in_playback_loop = true; + + while (chunk != nullptr && command == AO_COMMAND_NONE) { + assert(!current_chunk_finished); + + current_chunk = chunk; + + if (!PlayChunk(chunk)) { + assert(current_chunk == nullptr); + break; + } + + assert(current_chunk == chunk); + chunk = chunk->next; + } + + assert(in_playback_loop); + in_playback_loop = false; + + current_chunk_finished = true; + + mutex.unlock(); + player_control->LockSignal(); + mutex.lock(); + + return true; +} + +inline void +AudioOutput::Pause() +{ + mutex.unlock(); + ao_plugin_cancel(this); + mutex.lock(); + + pause = true; + CommandFinished(); + + do { + if (!WaitForDelay()) + break; + + mutex.unlock(); + bool success = ao_plugin_pause(this); + mutex.lock(); + + if (!success) { + Close(false); + break; + } + } while (command == AO_COMMAND_NONE); + + pause = false; +} + +inline void +AudioOutput::Task() +{ + FormatThreadName("output:%s", name); + + SetThreadRealtime(); + SetThreadTimerSlackUS(100); + + mutex.lock(); + + while (1) { + switch (command) { + case AO_COMMAND_NONE: + break; + + case AO_COMMAND_ENABLE: + Enable(); + CommandFinished(); + break; + + case AO_COMMAND_DISABLE: + Disable(); + CommandFinished(); + break; + + case AO_COMMAND_OPEN: + Open(); + CommandFinished(); + break; + + case AO_COMMAND_REOPEN: + Reopen(); + CommandFinished(); + break; + + case AO_COMMAND_CLOSE: + assert(open); + assert(pipe != nullptr); + + Close(false); + CommandFinished(); + break; + + case AO_COMMAND_PAUSE: + if (!open) { + /* the output has failed after + audio_output_all_pause() has + submitted the PAUSE command; bail + out */ + CommandFinished(); + break; + } + + Pause(); + /* don't "break" here: this might cause + Play() to be called when command==CLOSE + ends the paused state - "continue" checks + the new command first */ + continue; + + case AO_COMMAND_DRAIN: + if (open) { + assert(current_chunk == nullptr); + assert(pipe->Peek() == nullptr); + + mutex.unlock(); + ao_plugin_drain(this); + mutex.lock(); + } + + CommandFinished(); + continue; + + case AO_COMMAND_CANCEL: + current_chunk = nullptr; + + if (open) { + mutex.unlock(); + ao_plugin_cancel(this); + mutex.lock(); + } + + CommandFinished(); + continue; + + case AO_COMMAND_KILL: + current_chunk = nullptr; + CommandFinished(); + mutex.unlock(); + return; + } + + if (open && allow_play && Play()) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; + + if (command == AO_COMMAND_NONE) { + woken_for_play = false; + cond.wait(mutex); + } + } +} + +void +AudioOutput::Task(void *arg) +{ + AudioOutput *ao = (AudioOutput *)arg; + ao->Task(); +} + +void +AudioOutput::StartThread() +{ + assert(command == AO_COMMAND_NONE); + + Error error; + if (!thread.Start(Task, this, error)) + FatalError(error); +} diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx deleted file mode 100644 index 34d615284..000000000 --- a/src/output/PipeOutputPlugin.cxx +++ /dev/null @@ -1,147 +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 "PipeOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <string> - -#include <stdio.h> - -struct PipeOutput { - struct audio_output base; - - std::string cmd; - FILE *fh; - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &pipe_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - bool Configure(const config_param ¶m, Error &error); -}; - -static constexpr Domain pipe_output_domain("pipe_output"); - -inline bool -PipeOutput::Configure(const config_param ¶m, Error &error) -{ - cmd = param.GetBlockValue("command", ""); - if (cmd.empty()) { - error.Set(config_domain, - "No \"command\" parameter specified"); - return false; - } - - return true; -} - -static struct audio_output * -pipe_output_init(const config_param ¶m, Error &error) -{ - PipeOutput *pd = new PipeOutput(); - - if (!pd->Initialize(param, error)) { - delete pd; - return nullptr; - } - - if (!pd->Configure(param, error)) { - pd->Deinitialize(); - delete pd; - return nullptr; - } - - return &pd->base; -} - -static void -pipe_output_finish(struct audio_output *ao) -{ - PipeOutput *pd = (PipeOutput *)ao; - - pd->Deinitialize(); - delete pd; -} - -static bool -pipe_output_open(struct audio_output *ao, - gcc_unused AudioFormat &audio_format, - Error &error) -{ - PipeOutput *pd = (PipeOutput *)ao; - - pd->fh = popen(pd->cmd.c_str(), "w"); - if (pd->fh == nullptr) { - error.FormatErrno("Error opening pipe \"%s\"", - pd->cmd.c_str()); - return false; - } - - return true; -} - -static void -pipe_output_close(struct audio_output *ao) -{ - PipeOutput *pd = (PipeOutput *)ao; - - pclose(pd->fh); -} - -static size_t -pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - PipeOutput *pd = (PipeOutput *)ao; - size_t ret; - - ret = fwrite(chunk, 1, size, pd->fh); - if (ret == 0) - error.SetErrno("Write error on pipe"); - - return ret; -} - -const struct audio_output_plugin pipe_output_plugin = { - "pipe", - nullptr, - pipe_output_init, - pipe_output_finish, - nullptr, - nullptr, - pipe_output_open, - pipe_output_close, - nullptr, - nullptr, - pipe_output_play, - nullptr, - nullptr, - nullptr, - nullptr, -}; diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/PipeOutputPlugin.hxx deleted file mode 100644 index f0c29706b..000000000 --- a/src/output/PipeOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX -#define MPD_PIPE_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin pipe_output_plugin; - -#endif diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx deleted file mode 100644 index 1eece448a..000000000 --- a/src/output/PulseOutputPlugin.cxx +++ /dev/null @@ -1,887 +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 "PulseOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" -#include "mixer/PulseMixerPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <pulse/thread-mainloop.h> -#include <pulse/context.h> -#include <pulse/stream.h> -#include <pulse/introspect.h> -#include <pulse/subscribe.h> -#include <pulse/error.h> -#include <pulse/version.h> - -#include <assert.h> -#include <stddef.h> - -#define MPD_PULSE_NAME "Music Player Daemon" - -struct PulseOutput { - struct audio_output base; - - const char *name; - const char *server; - const char *sink; - - PulseMixer *mixer; - - struct pa_threaded_mainloop *mainloop; - struct pa_context *context; - struct pa_stream *stream; - - size_t writable; -}; - -static constexpr Domain pulse_output_domain("pulse_output"); - -static void -SetError(Error &error, pa_context *context, const char *msg) -{ - const int e = pa_context_errno(context); - error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e)); -} - -void -pulse_output_lock(PulseOutput *po) -{ - pa_threaded_mainloop_lock(po->mainloop); -} - -void -pulse_output_unlock(PulseOutput *po) -{ - pa_threaded_mainloop_unlock(po->mainloop); -} - -void -pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm) -{ - assert(po != nullptr); - assert(po->mixer == nullptr); - assert(pm != nullptr); - - po->mixer = pm; - - if (po->mainloop == nullptr) - return; - - pa_threaded_mainloop_lock(po->mainloop); - - if (po->context != nullptr && - pa_context_get_state(po->context) == PA_CONTEXT_READY) { - pulse_mixer_on_connect(pm, po->context); - - if (po->stream != nullptr && - pa_stream_get_state(po->stream) == PA_STREAM_READY) - pulse_mixer_on_change(pm, po->context, po->stream); - } - - pa_threaded_mainloop_unlock(po->mainloop); -} - -void -pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm) -{ - assert(po != nullptr); - assert(pm != nullptr); - assert(po->mixer == pm); - - po->mixer = nullptr; -} - -bool -pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume, - Error &error) -{ - pa_operation *o; - - if (po->context == nullptr || po->stream == nullptr || - pa_stream_get_state(po->stream) != PA_STREAM_READY) { - error.Set(pulse_output_domain, "disconnected"); - return false; - } - - o = pa_context_set_sink_input_volume(po->context, - pa_stream_get_index(po->stream), - volume, nullptr, nullptr); - if (o == nullptr) { - SetError(error, po->context, - "failed to set PulseAudio volume"); - return false; - } - - pa_operation_unref(o); - return true; -} - -/** - * \brief waits for a pulseaudio operation to finish, frees it and - * unlocks the mainloop - * \param operation the operation to wait for - * \return true if operation has finished normally (DONE state), - * false otherwise - */ -static bool -pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, - struct pa_operation *operation) -{ - pa_operation_state_t state; - - assert(mainloop != nullptr); - assert(operation != nullptr); - - state = pa_operation_get_state(operation); - while (state == PA_OPERATION_RUNNING) { - pa_threaded_mainloop_wait(mainloop); - state = pa_operation_get_state(operation); - } - - pa_operation_unref(operation); - - return state == PA_OPERATION_DONE; -} - -/** - * Callback function for stream operation. It just sends a signal to - * the caller thread, to wake pulse_wait_for_operation() up. - */ -static void -pulse_output_stream_success_cb(gcc_unused pa_stream *s, - gcc_unused int success, void *userdata) -{ - PulseOutput *po = (PulseOutput *)userdata; - - pa_threaded_mainloop_signal(po->mainloop, 0); -} - -static void -pulse_output_context_state_cb(struct pa_context *context, void *userdata) -{ - PulseOutput *po = (PulseOutput *)userdata; - - switch (pa_context_get_state(context)) { - case PA_CONTEXT_READY: - if (po->mixer != nullptr) - pulse_mixer_on_connect(po->mixer, context); - - pa_threaded_mainloop_signal(po->mainloop, 0); - break; - - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - if (po->mixer != nullptr) - pulse_mixer_on_disconnect(po->mixer); - - /* the caller thread might be waiting for these - states */ - pa_threaded_mainloop_signal(po->mainloop, 0); - break; - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - } -} - -static void -pulse_output_subscribe_cb(pa_context *context, - pa_subscription_event_type_t t, - uint32_t idx, void *userdata) -{ - PulseOutput *po = (PulseOutput *)userdata; - pa_subscription_event_type_t facility = - pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); - pa_subscription_event_type_t type = - pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); - - if (po->mixer != nullptr && - facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && - po->stream != nullptr && - pa_stream_get_state(po->stream) == PA_STREAM_READY && - idx == pa_stream_get_index(po->stream) && - (type == PA_SUBSCRIPTION_EVENT_NEW || - type == PA_SUBSCRIPTION_EVENT_CHANGE)) - pulse_mixer_on_change(po->mixer, context, po->stream); -} - -/** - * Attempt to connect asynchronously to the PulseAudio server. - * - * @return true on success, false on error - */ -static bool -pulse_output_connect(PulseOutput *po, Error &error) -{ - assert(po != nullptr); - assert(po->context != nullptr); - - if (pa_context_connect(po->context, po->server, - (pa_context_flags_t)0, nullptr) < 0) { - SetError(error, po->context, - "pa_context_connect() has failed"); - return false; - } - - return true; -} - -/** - * Frees and clears the stream. - */ -static void -pulse_output_delete_stream(PulseOutput *po) -{ - assert(po != nullptr); - assert(po->stream != nullptr); - - pa_stream_set_suspended_callback(po->stream, nullptr, nullptr); - - pa_stream_set_state_callback(po->stream, nullptr, nullptr); - pa_stream_set_write_callback(po->stream, nullptr, nullptr); - - pa_stream_disconnect(po->stream); - pa_stream_unref(po->stream); - po->stream = nullptr; -} - -/** - * Frees and clears the context. - * - * Caller must lock the main loop. - */ -static void -pulse_output_delete_context(PulseOutput *po) -{ - assert(po != nullptr); - assert(po->context != nullptr); - - pa_context_set_state_callback(po->context, nullptr, nullptr); - pa_context_set_subscribe_callback(po->context, nullptr, nullptr); - - pa_context_disconnect(po->context); - pa_context_unref(po->context); - po->context = nullptr; -} - -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_context(PulseOutput *po, Error &error) -{ - assert(po != nullptr); - assert(po->mainloop != nullptr); - - po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), - MPD_PULSE_NAME); - if (po->context == nullptr) { - error.Set(pulse_output_domain, "pa_context_new() has failed"); - return false; - } - - pa_context_set_state_callback(po->context, - pulse_output_context_state_cb, po); - pa_context_set_subscribe_callback(po->context, - pulse_output_subscribe_cb, po); - - if (!pulse_output_connect(po, error)) { - pulse_output_delete_context(po); - return false; - } - - return true; -} - -static struct audio_output * -pulse_output_init(const config_param ¶m, Error &error) -{ - PulseOutput *po; - - g_setenv("PULSE_PROP_media.role", "music", true); - - po = new PulseOutput(); - if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) { - delete po; - return nullptr; - } - - po->name = param.GetBlockValue("name", "mpd_pulse"); - po->server = param.GetBlockValue("server"); - po->sink = param.GetBlockValue("sink"); - - po->mixer = nullptr; - po->mainloop = nullptr; - po->context = nullptr; - po->stream = nullptr; - - return &po->base; -} - -static void -pulse_output_finish(struct audio_output *ao) -{ - PulseOutput *po = (PulseOutput *)ao; - - ao_base_finish(&po->base); - delete po; -} - -static bool -pulse_output_enable(struct audio_output *ao, Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop == nullptr); - assert(po->context == nullptr); - - /* create the libpulse mainloop and start the thread */ - - po->mainloop = pa_threaded_mainloop_new(); - if (po->mainloop == nullptr) { - error.Set(pulse_output_domain, - "pa_threaded_mainloop_new() has failed"); - return false; - } - - pa_threaded_mainloop_lock(po->mainloop); - - if (pa_threaded_mainloop_start(po->mainloop) < 0) { - pa_threaded_mainloop_unlock(po->mainloop); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; - - error.Set(pulse_output_domain, - "pa_threaded_mainloop_start() has failed"); - return false; - } - - /* create the libpulse context and connect it */ - - if (!pulse_output_setup_context(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - pa_threaded_mainloop_stop(po->mainloop); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; - return false; - } - - pa_threaded_mainloop_unlock(po->mainloop); - - return true; -} - -static void -pulse_output_disable(struct audio_output *ao) -{ - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - - pa_threaded_mainloop_stop(po->mainloop); - if (po->context != nullptr) - pulse_output_delete_context(po); - pa_threaded_mainloop_free(po->mainloop); - po->mainloop = nullptr; -} - -/** - * Check if the context is (already) connected, and waits if not. If - * the context has been disconnected, retry to connect. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_connection(PulseOutput *po, Error &error) -{ - assert(po->mainloop != nullptr); - - pa_context_state_t state; - - if (po->context == nullptr && !pulse_output_setup_context(po, error)) - return false; - - while (true) { - state = pa_context_get_state(po->context); - switch (state) { - case PA_CONTEXT_READY: - /* nothing to do */ - return true; - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - /* failure */ - SetError(error, po->context, "failed to connect"); - pulse_output_delete_context(po); - return false; - - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - /* wait some more */ - pa_threaded_mainloop_wait(po->mainloop); - break; - } - } -} - -static void -pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata) -{ - PulseOutput *po = (PulseOutput *)userdata; - - assert(stream == po->stream || po->stream == nullptr); - assert(po->mainloop != nullptr); - - /* wake up the main loop to break out of the loop in - pulse_output_play() */ - pa_threaded_mainloop_signal(po->mainloop, 0); -} - -static void -pulse_output_stream_state_cb(pa_stream *stream, void *userdata) -{ - PulseOutput *po = (PulseOutput *)userdata; - - assert(stream == po->stream || po->stream == nullptr); - assert(po->mainloop != nullptr); - assert(po->context != nullptr); - - switch (pa_stream_get_state(stream)) { - case PA_STREAM_READY: - if (po->mixer != nullptr) - pulse_mixer_on_change(po->mixer, po->context, stream); - - pa_threaded_mainloop_signal(po->mainloop, 0); - break; - - case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED: - if (po->mixer != nullptr) - pulse_mixer_on_disconnect(po->mixer); - - pa_threaded_mainloop_signal(po->mainloop, 0); - break; - - case PA_STREAM_UNCONNECTED: - case PA_STREAM_CREATING: - break; - } -} - -static void -pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes, - void *userdata) -{ - PulseOutput *po = (PulseOutput *)userdata; - - assert(po->mainloop != nullptr); - - po->writable = nbytes; - pa_threaded_mainloop_signal(po->mainloop, 0); -} - -/** - * Create, set up and connect a context. - * - * Caller must lock the main loop. - * - * @return true on success, false on error - */ -static bool -pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, - Error &error) -{ - assert(po != nullptr); - assert(po->context != nullptr); - - po->stream = pa_stream_new(po->context, po->name, ss, nullptr); - if (po->stream == nullptr) { - SetError(error, po->context, "pa_stream_new() has failed"); - return false; - } - - pa_stream_set_suspended_callback(po->stream, - pulse_output_stream_suspended_cb, po); - - pa_stream_set_state_callback(po->stream, - pulse_output_stream_state_cb, po); - pa_stream_set_write_callback(po->stream, - pulse_output_stream_write_cb, po); - - return true; -} - -static bool -pulse_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - pa_sample_spec ss; - - assert(po->mainloop != nullptr); - - pa_threaded_mainloop_lock(po->mainloop); - - if (po->context != nullptr) { - switch (pa_context_get_state(po->context)) { - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - /* the connection was closed meanwhile; delete - it, and pulse_output_wait_connection() will - reopen it */ - pulse_output_delete_context(po); - break; - - case PA_CONTEXT_READY: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - } - } - - if (!pulse_output_wait_connection(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - return false; - } - - /* MPD doesn't support the other pulseaudio sample formats, so - we just force MPD to send us everything as 16 bit */ - audio_format.format = SampleFormat::S16; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = audio_format.sample_rate; - ss.channels = audio_format.channels; - - /* create a stream .. */ - - if (!pulse_output_setup_stream(po, &ss, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - return false; - } - - /* .. and connect it (asynchronously) */ - - if (pa_stream_connect_playback(po->stream, po->sink, - nullptr, pa_stream_flags_t(0), - nullptr, nullptr) < 0) { - pulse_output_delete_stream(po); - - SetError(error, po->context, - "pa_stream_connect_playback() has failed"); - pa_threaded_mainloop_unlock(po->mainloop); - return false; - } - - pa_threaded_mainloop_unlock(po->mainloop); - - return true; -} - -static void -pulse_output_close(struct audio_output *ao) -{ - PulseOutput *po = (PulseOutput *)ao; - pa_operation *o; - - assert(po->mainloop != nullptr); - - pa_threaded_mainloop_lock(po->mainloop); - - if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { - o = pa_stream_drain(po->stream, - pulse_output_stream_success_cb, po); - if (o == nullptr) { - FormatWarning(pulse_output_domain, - "pa_stream_drain() has failed: %s", - pa_strerror(pa_context_errno(po->context))); - } else - pulse_wait_for_operation(po->mainloop, o); - } - - pulse_output_delete_stream(po); - - if (po->context != nullptr && - pa_context_get_state(po->context) != PA_CONTEXT_READY) - pulse_output_delete_context(po); - - pa_threaded_mainloop_unlock(po->mainloop); -} - -/** - * Check if the stream is (already) connected, and waits if not. The - * mainloop must be locked before calling this function. - * - * @return true on success, false on error - */ -static bool -pulse_output_wait_stream(PulseOutput *po, Error &error) -{ - while (true) { - switch (pa_stream_get_state(po->stream)) { - case PA_STREAM_READY: - return true; - - case PA_STREAM_FAILED: - case PA_STREAM_TERMINATED: - case PA_STREAM_UNCONNECTED: - SetError(error, po->context, - "failed to connect the stream"); - return false; - - case PA_STREAM_CREATING: - pa_threaded_mainloop_wait(po->mainloop); - break; - } - } -} - -/** - * Sets cork mode on the stream. - */ -static bool -pulse_output_stream_pause(PulseOutput *po, bool pause, - Error &error) -{ - pa_operation *o; - - assert(po->mainloop != nullptr); - assert(po->context != nullptr); - assert(po->stream != nullptr); - - o = pa_stream_cork(po->stream, pause, - pulse_output_stream_success_cb, po); - if (o == nullptr) { - SetError(error, po->context, "pa_stream_cork() has failed"); - return false; - } - - if (!pulse_wait_for_operation(po->mainloop, o)) { - SetError(error, po->context, "pa_stream_cork() has failed"); - return false; - } - - return true; -} - -static unsigned -pulse_output_delay(struct audio_output *ao) -{ - PulseOutput *po = (PulseOutput *)ao; - unsigned result = 0; - - pa_threaded_mainloop_lock(po->mainloop); - - if (po->base.pause && pa_stream_is_corked(po->stream) && - pa_stream_get_state(po->stream) == PA_STREAM_READY) - /* idle while paused */ - result = 1000; - - pa_threaded_mainloop_unlock(po->mainloop); - - return result; -} - -static size_t -pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); - - pa_threaded_mainloop_lock(po->mainloop); - - /* check if the stream is (already) connected */ - - if (!pulse_output_wait_stream(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - return 0; - } - - assert(po->context != nullptr); - - /* unpause if previously paused */ - - if (pa_stream_is_corked(po->stream) && - !pulse_output_stream_pause(po, false, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - return 0; - } - - /* wait until the server allows us to write */ - - while (po->writable == 0) { - if (pa_stream_is_suspended(po->stream)) { - pa_threaded_mainloop_unlock(po->mainloop); - error.Set(pulse_output_domain, "suspended"); - return 0; - } - - pa_threaded_mainloop_wait(po->mainloop); - - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { - pa_threaded_mainloop_unlock(po->mainloop); - error.Set(pulse_output_domain, "disconnected"); - return 0; - } - } - - /* now write */ - - if (size > po->writable) - /* don't send more than possible */ - size = po->writable; - - po->writable -= size; - - int result = pa_stream_write(po->stream, chunk, size, nullptr, - 0, PA_SEEK_RELATIVE); - pa_threaded_mainloop_unlock(po->mainloop); - if (result < 0) { - SetError(error, po->context, "pa_stream_write() failed"); - return 0; - } - - return size; -} - -static void -pulse_output_cancel(struct audio_output *ao) -{ - PulseOutput *po = (PulseOutput *)ao; - pa_operation *o; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); - - pa_threaded_mainloop_lock(po->mainloop); - - if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { - /* no need to flush when the stream isn't connected - yet */ - pa_threaded_mainloop_unlock(po->mainloop); - return; - } - - assert(po->context != nullptr); - - o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); - if (o == nullptr) { - FormatWarning(pulse_output_domain, - "pa_stream_flush() has failed: %s", - pa_strerror(pa_context_errno(po->context))); - pa_threaded_mainloop_unlock(po->mainloop); - return; - } - - pulse_wait_for_operation(po->mainloop, o); - pa_threaded_mainloop_unlock(po->mainloop); -} - -static bool -pulse_output_pause(struct audio_output *ao) -{ - PulseOutput *po = (PulseOutput *)ao; - - assert(po->mainloop != nullptr); - assert(po->stream != nullptr); - - pa_threaded_mainloop_lock(po->mainloop); - - /* check if the stream is (already/still) connected */ - - Error error; - if (!pulse_output_wait_stream(po, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - LogError(error); - return false; - } - - assert(po->context != nullptr); - - /* cork the stream */ - - if (!pa_stream_is_corked(po->stream) && - !pulse_output_stream_pause(po, true, error)) { - pa_threaded_mainloop_unlock(po->mainloop); - LogError(error); - return false; - } - - pa_threaded_mainloop_unlock(po->mainloop); - - return true; -} - -static bool -pulse_output_test_default_device(void) -{ - bool success; - - const config_param empty; - PulseOutput *po = (PulseOutput *) - pulse_output_init(empty, IgnoreError()); - if (po == nullptr) - return false; - - success = pulse_output_wait_connection(po, IgnoreError()); - pulse_output_finish(&po->base); - - return success; -} - -const struct audio_output_plugin pulse_output_plugin = { - "pulse", - pulse_output_test_default_device, - pulse_output_init, - pulse_output_finish, - pulse_output_enable, - pulse_output_disable, - pulse_output_open, - pulse_output_close, - pulse_output_delay, - nullptr, - pulse_output_play, - nullptr, - pulse_output_cancel, - pulse_output_pause, - - &pulse_mixer_plugin, -}; diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx deleted file mode 100644 index 0ed8404bc..000000000 --- a/src/output/PulseOutputPlugin.hxx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX -#define MPD_PULSE_OUTPUT_PLUGIN_HXX - -struct PulseOutput; -struct PulseMixer; -struct pa_cvolume; -class Error; - -extern const struct audio_output_plugin pulse_output_plugin; - -void -pulse_output_lock(PulseOutput *po); - -void -pulse_output_unlock(PulseOutput *po); - -void -pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm); - -void -pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm); - -bool -pulse_output_set_volume(PulseOutput *po, - const struct pa_cvolume *volume, Error &error); - -#endif diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx deleted file mode 100644 index 9a7eba01f..000000000 --- a/src/output/RecorderOutputPlugin.cxx +++ /dev/null @@ -1,262 +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 "RecorderOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "EncoderPlugin.hxx" -#include "EncoderList.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/fd_util.h" -#include "open.h" - -#include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -struct RecorderOutput { - struct audio_output base; - - /** - * The configured encoder plugin. - */ - Encoder *encoder; - - /** - * The destination file name. - */ - const char *path; - - /** - * The destination file descriptor. - */ - int fd; - - /** - * The buffer for encoder_read(). - */ - char buffer[32768]; - - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &recorder_output_plugin, param, - error_r); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - bool Configure(const config_param ¶m, Error &error); - - bool WriteToFile(const void *data, size_t length, Error &error); - - /** - * Writes pending data from the encoder to the output file. - */ - bool EncoderToFile(Error &error); -}; - -static constexpr Domain recorder_output_domain("recorder_output"); - -inline bool -RecorderOutput::Configure(const config_param ¶m, Error &error) -{ - /* read configuration */ - - const char *encoder_name = - param.GetBlockValue("encoder", "vorbis"); - const auto encoder_plugin = encoder_plugin_get(encoder_name); - if (encoder_plugin == nullptr) { - error.Format(config_domain, - "No such encoder: %s", encoder_name); - return false; - } - - path = param.GetBlockValue("path"); - if (path == nullptr) { - error.Set(config_domain, "'path' not configured"); - return false; - } - - /* initialize encoder */ - - encoder = encoder_init(*encoder_plugin, param, error); - if (encoder == nullptr) - return false; - - return true; -} - -static audio_output * -recorder_output_init(const config_param ¶m, Error &error) -{ - RecorderOutput *recorder = new RecorderOutput(); - - if (!recorder->Initialize(param, error)) { - delete recorder; - return nullptr; - } - - if (!recorder->Configure(param, error)) { - recorder->Deinitialize(); - delete recorder; - return nullptr; - } - - return &recorder->base; -} - -static void -recorder_output_finish(struct audio_output *ao) -{ - RecorderOutput *recorder = (RecorderOutput *)ao; - - encoder_finish(recorder->encoder); - recorder->Deinitialize(); - delete recorder; -} - -inline bool -RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error) -{ - assert(length > 0); - - const uint8_t *data = (const uint8_t *)_data, *end = data + length; - - while (true) { - ssize_t nbytes = write(fd, data, end - data); - if (nbytes > 0) { - data += nbytes; - if (data == end) - return true; - } else if (nbytes == 0) { - /* shouldn't happen for files */ - error.Set(recorder_output_domain, - "write() returned 0"); - return false; - } else if (errno != EINTR) { - error.FormatErrno("Failed to write to '%s'", path); - return false; - } - } -} - -inline bool -RecorderOutput::EncoderToFile(Error &error) -{ - assert(fd >= 0); - - while (true) { - /* read from the encoder */ - - size_t size = encoder_read(encoder, buffer, sizeof(buffer)); - if (size == 0) - return true; - - /* write everything into the file */ - - if (!WriteToFile(buffer, size, error)) - return false; - } -} - -static bool -recorder_output_open(struct audio_output *ao, - AudioFormat &audio_format, - Error &error) -{ - RecorderOutput *recorder = (RecorderOutput *)ao; - - /* create the output file */ - - recorder->fd = open_cloexec(recorder->path, - O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, - 0666); - if (recorder->fd < 0) { - error.FormatErrno("Failed to create '%s'", recorder->path); - return false; - } - - /* open the encoder */ - - if (!encoder_open(recorder->encoder, audio_format, error)) { - close(recorder->fd); - unlink(recorder->path); - return false; - } - - if (!recorder->EncoderToFile(error)) { - encoder_close(recorder->encoder); - close(recorder->fd); - unlink(recorder->path); - return false; - } - - return true; -} - -static void -recorder_output_close(struct audio_output *ao) -{ - RecorderOutput *recorder = (RecorderOutput *)ao; - - /* flush the encoder and write the rest to the file */ - - if (encoder_end(recorder->encoder, IgnoreError())) - recorder->EncoderToFile(IgnoreError()); - - /* now really close everything */ - - encoder_close(recorder->encoder); - - close(recorder->fd); -} - -static size_t -recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - RecorderOutput *recorder = (RecorderOutput *)ao; - - return encoder_write(recorder->encoder, chunk, size, error) && - recorder->EncoderToFile(error) - ? size : 0; -} - -const struct audio_output_plugin recorder_output_plugin = { - "recorder", - nullptr, - recorder_output_init, - recorder_output_finish, - nullptr, - nullptr, - recorder_output_open, - recorder_output_close, - nullptr, - nullptr, - recorder_output_play, - nullptr, - nullptr, - nullptr, - nullptr, -}; diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/RecorderOutputPlugin.hxx deleted file mode 100644 index a27f51e23..000000000 --- a/src/output/RecorderOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX -#define MPD_RECORDER_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin recorder_output_plugin; - -#endif diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx new file mode 100644 index 000000000..566f6b6a8 --- /dev/null +++ b/src/output/Registry.cxx @@ -0,0 +1,104 @@ +/* + * 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 "OutputAPI.hxx" +#include "plugins/AlsaOutputPlugin.hxx" +#include "plugins/AoOutputPlugin.hxx" +#include "plugins/FifoOutputPlugin.hxx" +#include "plugins/httpd/HttpdOutputPlugin.hxx" +#include "plugins/JackOutputPlugin.hxx" +#include "plugins/NullOutputPlugin.hxx" +#include "plugins/OpenALOutputPlugin.hxx" +#include "plugins/OssOutputPlugin.hxx" +#include "plugins/OSXOutputPlugin.hxx" +#include "plugins/PipeOutputPlugin.hxx" +#include "plugins/PulseOutputPlugin.hxx" +#include "plugins/RecorderOutputPlugin.hxx" +#include "plugins/RoarOutputPlugin.hxx" +#include "plugins/ShoutOutputPlugin.hxx" +#include "plugins/sles/SlesOutputPlugin.hxx" +#include "plugins/SolarisOutputPlugin.hxx" +#include "plugins/WinmmOutputPlugin.hxx" + +#include <string.h> + +const AudioOutputPlugin *const audio_output_plugins[] = { +#ifdef HAVE_SHOUT + &shout_output_plugin, +#endif + &null_output_plugin, +#ifdef ANDROID + &sles_output_plugin, +#endif +#ifdef HAVE_FIFO + &fifo_output_plugin, +#endif +#ifdef ENABLE_PIPE_OUTPUT + &pipe_output_plugin, +#endif +#ifdef HAVE_ALSA + &alsa_output_plugin, +#endif +#ifdef HAVE_ROAR + &roar_output_plugin, +#endif +#ifdef HAVE_AO + &ao_output_plugin, +#endif +#ifdef HAVE_OSS + &oss_output_plugin, +#endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif +#ifdef HAVE_OSX + &osx_output_plugin, +#endif +#ifdef ENABLE_SOLARIS_OUTPUT + &solaris_output_plugin, +#endif +#ifdef HAVE_PULSE + &pulse_output_plugin, +#endif +#ifdef HAVE_JACK + &jack_output_plugin, +#endif +#ifdef ENABLE_HTTPD_OUTPUT + &httpd_output_plugin, +#endif +#ifdef ENABLE_RECORDER_OUTPUT + &recorder_output_plugin, +#endif +#ifdef ENABLE_WINMM_OUTPUT + &winmm_output_plugin, +#endif + nullptr +}; + +const AudioOutputPlugin * +AudioOutputPlugin_get(const char *name) +{ + audio_output_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return nullptr; +} diff --git a/src/output/Registry.hxx b/src/output/Registry.hxx new file mode 100644 index 000000000..bc9c1ae2b --- /dev/null +++ b/src/output/Registry.hxx @@ -0,0 +1,35 @@ +/* + * 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_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX + +struct AudioOutputPlugin; + +extern const AudioOutputPlugin *const audio_output_plugins[]; + +const AudioOutputPlugin * +AudioOutputPlugin_get(const char *name); + +#define audio_output_plugins_for_each(plugin) \ + for (const AudioOutputPlugin *plugin, \ + *const*output_plugin_iterator = &audio_output_plugins[0]; \ + (plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator) + +#endif diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx deleted file mode 100644 index 895a165d1..000000000 --- a/src/output/RoarOutputPlugin.cxx +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * 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 "RoarOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "MixerList.hxx" -#include "thread/Mutex.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <string> - -/* libroar/services.h declares roar_service_stream::new - work around - this C++ problem */ -#define new _new -#include <roaraudio.h> -#undef new - -class RoarOutput { - struct audio_output base; - - std::string host, name; - - roar_vs_t * vss; - int err; - int role; - struct roar_connection con; - struct roar_audio_info info; - mutable Mutex mutex; - volatile bool alive; - -public: - RoarOutput() - :err(ROAR_ERROR_NONE) {} - - operator audio_output *() { - return &base; - } - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &roar_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - void Configure(const config_param ¶m); - - bool Open(AudioFormat &audio_format, Error &error); - void Close(); - - void SendTag(const Tag &tag); - size_t Play(const void *chunk, size_t size, Error &error); - void Cancel(); - - int GetVolume() const; - bool SetVolume(unsigned volume); -}; - -static constexpr Domain roar_output_domain("roar_output"); - -inline int -RoarOutput::GetVolume() const -{ - const ScopeLock protect(mutex); - - if (vss == nullptr || !alive) - return -1; - - float l, r; - int error; - if (roar_vs_volume_get(vss, &l, &r, &error) < 0) - return -1; - - return (l + r) * 50; -} - -int -roar_output_get_volume(RoarOutput *roar) -{ - return roar->GetVolume(); -} - -bool -RoarOutput::SetVolume(unsigned volume) -{ - assert(volume <= 100); - - const ScopeLock protect(mutex); - if (vss == nullptr || !alive) - return false; - - int error; - float level = volume / 100.0; - - roar_vs_volume_mono(vss, level, &error); - return true; -} - -bool -roar_output_set_volume(RoarOutput *roar, unsigned volume) -{ - return roar->SetVolume(volume); -} - -inline void -RoarOutput::Configure(const config_param ¶m) -{ - host = param.GetBlockValue("server", ""); - name = param.GetBlockValue("name", "MPD"); - - const char *_role = param.GetBlockValue("role", "music"); - role = _role != nullptr - ? roar_str2role(_role) - : ROAR_ROLE_MUSIC; -} - -static struct audio_output * -roar_init(const config_param ¶m, Error &error) -{ - RoarOutput *self = new RoarOutput(); - - if (!self->Initialize(param, error)) { - delete self; - return nullptr; - } - - self->Configure(param); - return *self; -} - -static void -roar_finish(struct audio_output *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - self->Deinitialize(); - delete self; -} - -static void -roar_use_audio_format(struct roar_audio_info *info, - AudioFormat &audio_format) -{ - info->rate = audio_format.sample_rate; - info->channels = audio_format.channels; - info->codec = ROAR_CODEC_PCM_S; - - switch (audio_format.format) { - case SampleFormat::UNDEFINED: - case SampleFormat::FLOAT: - case SampleFormat::DSD: - info->bits = 16; - audio_format.format = SampleFormat::S16; - break; - - case SampleFormat::S8: - info->bits = 8; - break; - - case SampleFormat::S16: - info->bits = 16; - break; - - case SampleFormat::S24_P32: - info->bits = 32; - audio_format.format = SampleFormat::S32; - break; - - case SampleFormat::S32: - info->bits = 32; - break; - } -} - -inline bool -RoarOutput::Open(AudioFormat &audio_format, Error &error) -{ - const ScopeLock protect(mutex); - - if (roar_simple_connect(&con, - host.empty() ? nullptr : host.c_str(), - name.c_str()) < 0) { - error.Set(roar_output_domain, - "Failed to connect to Roar server"); - return false; - } - - vss = roar_vs_new_from_con(&con, &err); - - if (vss == nullptr || err != ROAR_ERROR_NONE) { - error.Set(roar_output_domain, "Failed to connect to server"); - return false; - } - - roar_use_audio_format(&info, audio_format); - - if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) { - error.Set(roar_output_domain, "Failed to start stream"); - return false; - } - - roar_vs_role(vss, role, &err); - alive = true; - return true; -} - -static bool -roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - - return self->Open(audio_format, error); -} - -inline void -RoarOutput::Close() -{ - const ScopeLock protect(mutex); - - alive = false; - - if (vss != nullptr) - roar_vs_close(vss, ROAR_VS_TRUE, &err); - vss = nullptr; - roar_disconnect(&con); -} - -static void -roar_close(struct audio_output *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - self->Close(); -} - -inline void -RoarOutput::Cancel() -{ - const ScopeLock protect(mutex); - - if (vss == nullptr) - return; - - roar_vs_t *_vss = vss; - vss = nullptr; - roar_vs_close(_vss, ROAR_VS_TRUE, &err); - alive = false; - - _vss = roar_vs_new_from_con(&con, &err); - if (_vss == nullptr) - return; - - if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) { - roar_vs_close(_vss, ROAR_VS_TRUE, &err); - LogError(roar_output_domain, "Failed to start stream"); - return; - } - - roar_vs_role(_vss, role, &err); - vss = _vss; - alive = true; -} - -static void -roar_cancel(struct audio_output *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - self->Cancel(); -} - -inline size_t -RoarOutput::Play(const void *chunk, size_t size, Error &error) -{ - if (vss == nullptr) { - error.Set(roar_output_domain, "Connection is invalid"); - return 0; - } - - ssize_t nbytes = roar_vs_write(vss, chunk, size, &err); - if (nbytes <= 0) { - error.Set(roar_output_domain, "Failed to play data"); - return 0; - } - - return nbytes; -} - -static size_t -roar_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - return self->Play(chunk, size, error); -} - -static const char* -roar_tag_convert(TagType type, bool *is_uuid) -{ - *is_uuid = false; - switch (type) - { - case TAG_ARTIST: - case TAG_ALBUM_ARTIST: - return "AUTHOR"; - case TAG_ALBUM: - return "ALBUM"; - case TAG_TITLE: - return "TITLE"; - case TAG_TRACK: - return "TRACK"; - case TAG_NAME: - return "NAME"; - case TAG_GENRE: - return "GENRE"; - case TAG_DATE: - return "DATE"; - case TAG_PERFORMER: - return "PERFORMER"; - case TAG_COMMENT: - return "COMMENT"; - case TAG_DISC: - return "DISCID"; - case TAG_COMPOSER: -#ifdef ROAR_META_TYPE_COMPOSER - return "COMPOSER"; -#else - return "AUTHOR"; -#endif - case TAG_MUSICBRAINZ_ARTISTID: - case TAG_MUSICBRAINZ_ALBUMID: - case TAG_MUSICBRAINZ_ALBUMARTISTID: - case TAG_MUSICBRAINZ_TRACKID: - *is_uuid = true; - return "HASH"; - - default: - return nullptr; - } -} - -inline void -RoarOutput::SendTag(const Tag &tag) -{ - if (vss == nullptr) - return; - - const ScopeLock protect(mutex); - - size_t cnt = 1; - struct roar_keyval vals[32]; - char uuid_buf[32][64]; - - char timebuf[16]; - snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", - tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60); - - vals[0].key = const_cast<char *>("LENGTH"); - vals[0].value = timebuf; - - for (unsigned i = 0; i < tag.num_items && cnt < 32; i++) - { - bool is_uuid = false; - const char *key = roar_tag_convert(tag.items[i]->type, - &is_uuid); - if (key != nullptr) { - vals[cnt].key = const_cast<char *>(key); - - if (is_uuid) { - snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", - tag.items[i]->value); - vals[cnt].value = uuid_buf[cnt]; - } else { - vals[cnt].value = tag.items[i]->value; - } - - cnt++; - } - } - - roar_vs_meta(vss, vals, cnt, &(err)); -} - -static void -roar_send_tag(struct audio_output *ao, const Tag *meta) -{ - RoarOutput *self = (RoarOutput *)ao; - self->SendTag(*meta); -} - -const struct audio_output_plugin roar_output_plugin = { - "roar", - nullptr, - roar_init, - roar_finish, - nullptr, - nullptr, - roar_open, - roar_close, - nullptr, - roar_send_tag, - roar_play, - nullptr, - roar_cancel, - nullptr, - &roar_mixer_plugin, -}; diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/RoarOutputPlugin.hxx deleted file mode 100644 index 04949e421..000000000 --- a/src/output/RoarOutputPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ROAR_OUTPUT_PLUGIN_H -#define MPD_ROAR_OUTPUT_PLUGIN_H - -class RoarOutput; - -extern const struct audio_output_plugin roar_output_plugin; - -int -roar_output_get_volume(RoarOutput *roar); - -bool -roar_output_set_volume(RoarOutput *roar, unsigned volume); - -#endif diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx deleted file mode 100644 index 19f2b61cd..000000000 --- a/src/output/ShoutOutputPlugin.cxx +++ /dev/null @@ -1,544 +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 "ShoutOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "EncoderPlugin.hxx" -#include "EncoderList.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <shout/shout.h> -#include <glib.h> - -#include <assert.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> - -static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2; - -struct ShoutOutput final { - struct audio_output base; - - shout_t *shout_conn; - shout_metadata_t *shout_meta; - - Encoder *encoder; - - float quality; - int bitrate; - - int timeout; - - uint8_t buffer[32768]; - - ShoutOutput() - :shout_conn(shout_new()), - shout_meta(shout_metadata_new()), - quality(-2.0), - bitrate(-1), - timeout(DEFAULT_CONN_TIMEOUT) {} - - ~ShoutOutput() { - if (shout_meta != nullptr) - shout_metadata_free(shout_meta); - if (shout_conn != nullptr) - shout_free(shout_conn); - } - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &shout_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - bool Configure(const config_param ¶m, Error &error); -}; - -static int shout_init_count; - -static constexpr Domain shout_output_domain("shout_output"); - -static const EncoderPlugin * -shout_encoder_plugin_get(const char *name) -{ - if (strcmp(name, "ogg") == 0) - name = "vorbis"; - else if (strcmp(name, "mp3") == 0) - name = "lame"; - - return encoder_plugin_get(name); -} - -gcc_pure -static const char * -require_block_string(const config_param ¶m, const char *name) -{ - const char *value = param.GetBlockValue(name); - if (value == nullptr) - FormatFatalError("no \"%s\" defined for shout device defined " - "at line %u\n", name, param.line); - - return value; -} - -inline bool -ShoutOutput::Configure(const config_param ¶m, Error &error) -{ - - const AudioFormat audio_format = base.config_audio_format; - if (!audio_format.IsFullyDefined()) { - error.Set(config_domain, - "Need full audio format specification"); - return nullptr; - } - - const char *host = require_block_string(param, "host"); - const char *mount = require_block_string(param, "mount"); - unsigned port = param.GetBlockValue("port", 0u); - if (port == 0) { - error.Set(config_domain, "shout port must be configured"); - return false; - } - - const char *passwd = require_block_string(param, "password"); - const char *name = require_block_string(param, "name"); - - bool is_public = param.GetBlockValue("public", false); - - const char *user = param.GetBlockValue("user", "source"); - - const char *value = param.GetBlockValue("quality"); - if (value != nullptr) { - char *test; - quality = strtod(value, &test); - - if (*test != '\0' || quality < -1.0 || quality > 10.0) { - error.Format(config_domain, - "shout quality \"%s\" is not a number in the " - "range -1 to 10", - value); - return false; - } - - if (param.GetBlockValue("bitrate") != nullptr) { - error.Set(config_domain, - "quality and bitrate are " - "both defined"); - return false; - } - } else { - value = param.GetBlockValue("bitrate"); - if (value == nullptr) { - error.Set(config_domain, - "neither bitrate nor quality defined"); - return false; - } - - char *test; - bitrate = strtol(value, &test, 10); - - if (*test != '\0' || bitrate <= 0) { - error.Set(config_domain, - "bitrate must be a positive integer"); - return false; - } - } - - const char *encoding = param.GetBlockValue("encoding", "ogg"); - const auto encoder_plugin = shout_encoder_plugin_get(encoding); - if (encoder_plugin == nullptr) { - error.Format(config_domain, - "couldn't find shout encoder plugin \"%s\"", - encoding); - return false; - } - - encoder = encoder_init(*encoder_plugin, param, error); - if (encoder == nullptr) - return false; - - unsigned shout_format; - if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) - shout_format = SHOUT_FORMAT_MP3; - else - shout_format = SHOUT_FORMAT_OGG; - - unsigned protocol; - value = param.GetBlockValue("protocol"); - if (value != nullptr) { - if (0 == strcmp(value, "shoutcast") && - 0 != strcmp(encoding, "mp3")) { - error.Format(config_domain, - "you cannot stream \"%s\" to shoutcast, use mp3", - encoding); - return false; - } else if (0 == strcmp(value, "shoutcast")) - protocol = SHOUT_PROTOCOL_ICY; - else if (0 == strcmp(value, "icecast1")) - protocol = SHOUT_PROTOCOL_XAUDIOCAST; - else if (0 == strcmp(value, "icecast2")) - protocol = SHOUT_PROTOCOL_HTTP; - else { - error.Format(config_domain, - "shout protocol \"%s\" is not \"shoutcast\" or " - "\"icecast1\"or \"icecast2\"", - value); - return false; - } - } else { - protocol = SHOUT_PROTOCOL_HTTP; - } - - if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS || - shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS || - shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS || - shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS || - shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS || - shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS || - shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS || - shout_set_format(shout_conn, shout_format) - != SHOUTERR_SUCCESS || - shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS || - shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) { - error.Set(shout_output_domain, shout_get_error(shout_conn)); - return false; - } - - /* optional paramters */ - timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT); - - value = param.GetBlockValue("genre"); - if (value != nullptr && shout_set_genre(shout_conn, value)) { - error.Set(shout_output_domain, shout_get_error(shout_conn)); - return false; - } - - value = param.GetBlockValue("description"); - if (value != nullptr && shout_set_description(shout_conn, value)) { - error.Set(shout_output_domain, shout_get_error(shout_conn)); - return false; - } - - value = param.GetBlockValue("url"); - if (value != nullptr && shout_set_url(shout_conn, value)) { - error.Set(shout_output_domain, shout_get_error(shout_conn)); - return false; - } - - { - char temp[11]; - memset(temp, 0, sizeof(temp)); - - snprintf(temp, sizeof(temp), "%u", audio_format.channels); - shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp); - - snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate); - - shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp); - - if (quality >= -1.0) { - snprintf(temp, sizeof(temp), "%2.2f", quality); - shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY, - temp); - } else { - snprintf(temp, sizeof(temp), "%d", bitrate); - shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE, - temp); - } - } - - return true; -} - -static struct audio_output * -my_shout_init_driver(const config_param ¶m, Error &error) -{ - ShoutOutput *sd = new ShoutOutput(); - if (!sd->Initialize(param, error)) { - delete sd; - return nullptr; - } - - if (!sd->Configure(param, error)) { - sd->Deinitialize(); - delete sd; - return nullptr; - } - - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - - return &sd->base; -} - -static bool -handle_shout_error(ShoutOutput *sd, int err, Error &error) -{ - switch (err) { - case SHOUTERR_SUCCESS: - break; - - case SHOUTERR_UNCONNECTED: - case SHOUTERR_SOCKET: - error.Format(shout_output_domain, err, - "Lost shout connection to %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - - default: - error.Format(shout_output_domain, err, - "connection to %s:%i error: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } - - return true; -} - -static bool -write_page(ShoutOutput *sd, Error &error) -{ - assert(sd->encoder != nullptr); - - while (true) { - size_t nbytes = encoder_read(sd->encoder, - sd->buffer, sizeof(sd->buffer)); - if (nbytes == 0) - return true; - - int err = shout_send(sd->shout_conn, sd->buffer, nbytes); - if (!handle_shout_error(sd, err, error)) - return false; - } - - return true; -} - -static void close_shout_conn(ShoutOutput * sd) -{ - if (sd->encoder != nullptr) { - if (encoder_end(sd->encoder, IgnoreError())) - write_page(sd, IgnoreError()); - - encoder_close(sd->encoder); - } - - if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && - shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { - FormatWarning(shout_output_domain, - "problem closing connection to shout server: %s", - shout_get_error(sd->shout_conn)); - } -} - -static void -my_shout_finish_driver(struct audio_output *ao) -{ - ShoutOutput *sd = (ShoutOutput *)ao; - - encoder_finish(sd->encoder); - - sd->Deinitialize(); - delete sd; - - shout_init_count--; - - if (shout_init_count == 0) - shout_shutdown(); -} - -static void -my_shout_drop_buffered_audio(struct audio_output *ao) -{ - gcc_unused - ShoutOutput *sd = (ShoutOutput *)ao; - - /* needs to be implemented for shout */ -} - -static void -my_shout_close_device(struct audio_output *ao) -{ - ShoutOutput *sd = (ShoutOutput *)ao; - - close_shout_conn(sd); -} - -static bool -shout_connect(ShoutOutput *sd, Error &error) -{ - switch (shout_open(sd->shout_conn)) { - case SHOUTERR_SUCCESS: - case SHOUTERR_CONNECTED: - return true; - - default: - error.Format(shout_output_domain, - "problem opening connection to shout server %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } -} - -static bool -my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - ShoutOutput *sd = (ShoutOutput *)ao; - - if (!shout_connect(sd, error)) - return false; - - if (!encoder_open(sd->encoder, audio_format, error)) { - shout_close(sd->shout_conn); - return false; - } - - if (!write_page(sd, error)) { - encoder_close(sd->encoder); - shout_close(sd->shout_conn); - return false; - } - - return true; -} - -static unsigned -my_shout_delay(struct audio_output *ao) -{ - ShoutOutput *sd = (ShoutOutput *)ao; - - int delay = shout_delay(sd->shout_conn); - if (delay < 0) - delay = 0; - - return delay; -} - -static size_t -my_shout_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - ShoutOutput *sd = (ShoutOutput *)ao; - - return encoder_write(sd->encoder, chunk, size, error) && - write_page(sd, error) - ? size - : 0; -} - -static bool -my_shout_pause(struct audio_output *ao) -{ - static char silence[1020]; - - return my_shout_play(ao, silence, sizeof(silence), IgnoreError()); -} - -static void -shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) -{ - char artist[size]; - char title[size]; - - artist[0] = 0; - title[0] = 0; - - for (unsigned i = 0; i < tag->num_items; i++) { - switch (tag->items[i]->type) { - case TAG_ARTIST: - strncpy(artist, tag->items[i]->value, size); - break; - case TAG_TITLE: - strncpy(title, tag->items[i]->value, size); - break; - - default: - break; - } - } - - snprintf(dest, size, "%s - %s", artist, title); -} - -static void my_shout_set_tag(struct audio_output *ao, - const Tag *tag) -{ - ShoutOutput *sd = (ShoutOutput *)ao; - - if (sd->encoder->plugin.tag != nullptr) { - /* encoder plugin supports stream tags */ - - Error error; - if (!encoder_pre_tag(sd->encoder, error) || - !write_page(sd, error) || - !encoder_tag(sd->encoder, tag, error)) { - LogError(error); - return; - } - } else { - /* no stream tag support: fall back to icy-metadata */ - char song[1024]; - shout_tag_to_metadata(tag, song, sizeof(song)); - - shout_metadata_add(sd->shout_meta, "song", song); - if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, - sd->shout_meta)) { - LogWarning(shout_output_domain, - "error setting shout metadata"); - } - } - - write_page(sd, IgnoreError()); -} - -const struct audio_output_plugin shout_output_plugin = { - "shout", - nullptr, - my_shout_init_driver, - my_shout_finish_driver, - nullptr, - nullptr, - my_shout_open_device, - my_shout_close_device, - my_shout_delay, - my_shout_set_tag, - my_shout_play, - nullptr, - my_shout_drop_buffered_audio, - my_shout_pause, - nullptr, -}; diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/ShoutOutputPlugin.hxx deleted file mode 100644 index 496b77975..000000000 --- a/src/output/ShoutOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX -#define MPD_SHOUT_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin shout_output_plugin; - -#endif diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx deleted file mode 100644 index 0836dc2e2..000000000 --- a/src/output/SolarisOutputPlugin.cxx +++ /dev/null @@ -1,201 +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 "SolarisOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "system/fd_util.h" -#include "util/Error.hxx" - -#include <sys/stropts.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> - -#ifdef __sun -#include <sys/audio.h> -#else - -/* some fake declarations that allow build this plugin on systems - other than Solaris, just to see if it compiles */ - -#define AUDIO_GETINFO 0 -#define AUDIO_SETINFO 0 -#define AUDIO_ENCODING_LINEAR 0 - -struct audio_info { - struct { - unsigned sample_rate, channels, precision, encoding; - } play; -}; - -#endif - -struct SolarisOutput { - struct audio_output base; - - /* configuration */ - const char *device; - - int fd; - - bool Initialize(const config_param ¶m, Error &error_r) { - return ao_base_init(&base, &solaris_output_plugin, param, - error_r); - } - - void Deinitialize() { - ao_base_finish(&base); - } -}; - -static bool -solaris_output_test_default_device(void) -{ - struct stat st; - - return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) && - access("/dev/audio", W_OK) == 0; -} - -static struct audio_output * -solaris_output_init(const config_param ¶m, Error &error_r) -{ - SolarisOutput *so = new SolarisOutput(); - if (!so->Initialize(param, error_r)) { - delete so; - return nullptr; - } - - so->device = param.GetBlockValue("device", "/dev/audio"); - - return &so->base; -} - -static void -solaris_output_finish(struct audio_output *ao) -{ - SolarisOutput *so = (SolarisOutput *)ao; - - so->Deinitialize(); - delete so; -} - -static bool -solaris_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - SolarisOutput *so = (SolarisOutput *)ao; - struct audio_info info; - int ret, flags; - - /* support only 16 bit mono/stereo for now; nothing else has - been tested */ - audio_format.format = SampleFormat::S16; - - /* open the device in non-blocking mode */ - - so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0); - if (so->fd < 0) { - error.FormatErrno("Failed to open %s", - so->device); - return false; - } - - /* restore blocking mode */ - - flags = fcntl(so->fd, F_GETFL); - if (flags > 0 && (flags & O_NONBLOCK) != 0) - fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK); - - /* configure the audio device */ - - ret = ioctl(so->fd, AUDIO_GETINFO, &info); - if (ret < 0) { - error.SetErrno("AUDIO_GETINFO failed"); - close(so->fd); - return false; - } - - info.play.sample_rate = audio_format.sample_rate; - info.play.channels = audio_format.channels; - info.play.precision = 16; - info.play.encoding = AUDIO_ENCODING_LINEAR; - - ret = ioctl(so->fd, AUDIO_SETINFO, &info); - if (ret < 0) { - error.SetErrno("AUDIO_SETINFO failed"); - close(so->fd); - return false; - } - - return true; -} - -static void -solaris_output_close(struct audio_output *ao) -{ - SolarisOutput *so = (SolarisOutput *)ao; - - close(so->fd); -} - -static size_t -solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - SolarisOutput *so = (SolarisOutput *)ao; - ssize_t nbytes; - - nbytes = write(so->fd, chunk, size); - if (nbytes <= 0) { - error.SetErrno("Write failed"); - return 0; - } - - return nbytes; -} - -static void -solaris_output_cancel(struct audio_output *ao) -{ - SolarisOutput *so = (SolarisOutput *)ao; - - ioctl(so->fd, I_FLUSH); -} - -const struct audio_output_plugin solaris_output_plugin = { - "solaris", - solaris_output_test_default_device, - solaris_output_init, - solaris_output_finish, - nullptr, - nullptr, - solaris_output_open, - solaris_output_close, - nullptr, - nullptr, - solaris_output_play, - nullptr, - solaris_output_cancel, - nullptr, - nullptr, -}; diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/SolarisOutputPlugin.hxx deleted file mode 100644 index d0fbd32c8..000000000 --- a/src/output/SolarisOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX -#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX - -extern const struct audio_output_plugin solaris_output_plugin; - -#endif diff --git a/src/output/Timer.cxx b/src/output/Timer.cxx new file mode 100644 index 000000000..d3dcc714d --- /dev/null +++ b/src/output/Timer.cxx @@ -0,0 +1,67 @@ +/* + * 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 "Timer.hxx" +#include "AudioFormat.hxx" +#include "system/Clock.hxx" + +#include <limits> + +#include <assert.h> + +Timer::Timer(const AudioFormat af) + : time(0), + started(false), + rate(af.sample_rate * af.GetFrameSize()) +{ +} + +void Timer::Start() +{ + time = MonotonicClockUS(); + started = true; +} + +void Timer::Reset() +{ + time = 0; + started = false; +} + +void Timer::Add(int size) +{ + assert(started); + + // (size samples) / (rate samples per second) = duration seconds + // duration seconds * 1000000 = duration us + time += ((uint64_t)size * 1000000) / rate; +} + +unsigned Timer::GetDelay() const +{ + int64_t delay = (int64_t)(time - MonotonicClockUS()) / 1000; + if (delay < 0) + return 0; + + if (delay > std::numeric_limits<int>::max()) + delay = std::numeric_limits<int>::max(); + + return delay; +} diff --git a/src/output/Timer.hxx b/src/output/Timer.hxx new file mode 100644 index 000000000..3c935cfac --- /dev/null +++ b/src/output/Timer.hxx @@ -0,0 +1,47 @@ +/* + * 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_TIMER_HXX +#define MPD_TIMER_HXX + +#include <stdint.h> + +struct AudioFormat; + +class Timer { + uint64_t time; + bool started; + const int rate; +public: + explicit Timer(AudioFormat af); + + bool IsStarted() const { return started; } + + void Start(); + void Reset(); + + void Add(int size); + + /** + * Returns the number of milliseconds to sleep to get back to sync. + */ + unsigned GetDelay() const; +}; + +#endif diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx deleted file mode 100644 index d2508ee2a..000000000 --- a/src/output/WinmmOutputPlugin.cxx +++ /dev/null @@ -1,353 +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 "WinmmOutputPlugin.hxx" -#include "OutputAPI.hxx" -#include "pcm/PcmBuffer.hxx" -#include "MixerList.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "util/Macros.hxx" - -#include <glib.h> - -#include <stdlib.h> -#include <string.h> - -struct WinmmBuffer { - PcmBuffer buffer; - - WAVEHDR hdr; -}; - -struct WinmmOutput { - struct audio_output base; - - UINT device_id; - HWAVEOUT handle; - - /** - * This event is triggered by Windows when a buffer is - * finished. - */ - HANDLE event; - - WinmmBuffer buffers[8]; - unsigned next_buffer; -}; - -static constexpr Domain winmm_output_domain("winmm_output"); - -HWAVEOUT -winmm_output_get_handle(WinmmOutput *output) -{ - return output->handle; -} - -static bool -winmm_output_test_default_device(void) -{ - return waveOutGetNumDevs() > 0; -} - -static bool -get_device_id(const char *device_name, UINT *device_id, Error &error) -{ - /* if device is not specified use wave mapper */ - if (device_name == nullptr) { - *device_id = WAVE_MAPPER; - return true; - } - - UINT numdevs = waveOutGetNumDevs(); - - /* check for device id */ - char *endptr; - UINT id = strtoul(device_name, &endptr, 0); - if (endptr > device_name && *endptr == 0) { - if (id >= numdevs) - goto fail; - *device_id = id; - return true; - } - - /* check for device name */ - for (UINT i = 0; i < numdevs; i++) { - WAVEOUTCAPS caps; - MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); - if (result != MMSYSERR_NOERROR) - continue; - /* szPname is only 32 chars long, so it is often truncated. - Use partial match to work around this. */ - if (strstr(device_name, caps.szPname) == device_name) { - *device_id = i; - return true; - } - } - -fail: - error.Format(winmm_output_domain, - "device \"%s\" is not found", device_name); - return false; -} - -static struct audio_output * -winmm_output_init(const config_param ¶m, Error &error) -{ - WinmmOutput *wo = new WinmmOutput(); - if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) { - delete wo; - return nullptr; - } - - const char *device = param.GetBlockValue("device"); - if (!get_device_id(device, &wo->device_id, error)) { - ao_base_finish(&wo->base); - delete wo; - return nullptr; - } - - return &wo->base; -} - -static void -winmm_output_finish(struct audio_output *ao) -{ - WinmmOutput *wo = (WinmmOutput *)ao; - - ao_base_finish(&wo->base); - delete wo; -} - -static bool -winmm_output_open(struct audio_output *ao, AudioFormat &audio_format, - Error &error) -{ - WinmmOutput *wo = (WinmmOutput *)ao; - - wo->event = CreateEvent(nullptr, false, false, nullptr); - if (wo->event == nullptr) { - error.Set(winmm_output_domain, "CreateEvent() failed"); - return false; - } - - switch (audio_format.format) { - case SampleFormat::S8: - case SampleFormat::S16: - break; - - case SampleFormat::S24_P32: - case SampleFormat::S32: - case SampleFormat::FLOAT: - case SampleFormat::DSD: - case SampleFormat::UNDEFINED: - /* we havn't tested formats other than S16 */ - audio_format.format = SampleFormat::S16; - break; - } - - if (audio_format.channels > 2) - /* same here: more than stereo was not tested */ - audio_format.channels = 2; - - WAVEFORMATEX format; - format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = audio_format.channels; - format.nSamplesPerSec = audio_format.sample_rate; - format.nBlockAlign = audio_format.GetFrameSize(); - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - format.wBitsPerSample = audio_format.GetSampleSize() * 8; - format.cbSize = 0; - - MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format, - (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); - if (result != MMSYSERR_NOERROR) { - CloseHandle(wo->event); - error.Set(winmm_output_domain, "waveOutOpen() failed"); - return false; - } - - for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) { - memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); - } - - wo->next_buffer = 0; - - return true; -} - -static void -winmm_output_close(struct audio_output *ao) -{ - WinmmOutput *wo = (WinmmOutput *)ao; - - for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) - wo->buffers[i].buffer.Clear(); - - waveOutClose(wo->handle); - - CloseHandle(wo->event); -} - -/** - * Copy data into a buffer, and prepare the wave header. - */ -static bool -winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, - const void *data, size_t size, - Error &error) -{ - void *dest = buffer->buffer.Get(size); - assert(dest != nullptr); - - memcpy(dest, data, size); - - memset(&buffer->hdr, 0, sizeof(buffer->hdr)); - buffer->hdr.lpData = (LPSTR)dest; - buffer->hdr.dwBufferLength = size; - - MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, - sizeof(buffer->hdr)); - if (result != MMSYSERR_NOERROR) { - error.Set(winmm_output_domain, result, - "waveOutPrepareHeader() failed"); - return false; - } - - return true; -} - -/** - * Wait until the buffer is finished. - */ -static bool -winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, - Error &error) -{ - if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) - /* already finished */ - return true; - - while (true) { - MMRESULT result = waveOutUnprepareHeader(wo->handle, - &buffer->hdr, - sizeof(buffer->hdr)); - if (result == MMSYSERR_NOERROR) - return true; - else if (result != WAVERR_STILLPLAYING) { - error.Set(winmm_output_domain, result, - "waveOutUnprepareHeader() failed"); - return false; - } - - /* wait some more */ - WaitForSingleObject(wo->event, INFINITE); - } -} - -static size_t -winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error) -{ - WinmmOutput *wo = (WinmmOutput *)ao; - - /* get the next buffer from the ring and prepare it */ - WinmmBuffer *buffer = &wo->buffers[wo->next_buffer]; - if (!winmm_drain_buffer(wo, buffer, error) || - !winmm_set_buffer(wo, buffer, chunk, size, error)) - return 0; - - /* enqueue the buffer */ - MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr, - sizeof(buffer->hdr)); - if (result != MMSYSERR_NOERROR) { - waveOutUnprepareHeader(wo->handle, &buffer->hdr, - sizeof(buffer->hdr)); - error.Set(winmm_output_domain, result, - "waveOutWrite() failed"); - return 0; - } - - /* mark our buffer as "used" */ - wo->next_buffer = (wo->next_buffer + 1) % - ARRAY_SIZE(wo->buffers); - - return size; -} - -static bool -winmm_drain_all_buffers(WinmmOutput *wo, Error &error) -{ - for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i) - if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) - return false; - - for (unsigned i = 0; i < wo->next_buffer; ++i) - if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) - return false; - - return true; -} - -static void -winmm_stop(WinmmOutput *wo) -{ - waveOutReset(wo->handle); - - for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) { - WinmmBuffer *buffer = &wo->buffers[i]; - waveOutUnprepareHeader(wo->handle, &buffer->hdr, - sizeof(buffer->hdr)); - } -} - -static void -winmm_output_drain(struct audio_output *ao) -{ - WinmmOutput *wo = (WinmmOutput *)ao; - - if (!winmm_drain_all_buffers(wo, IgnoreError())) - winmm_stop(wo); -} - -static void -winmm_output_cancel(struct audio_output *ao) -{ - WinmmOutput *wo = (WinmmOutput *)ao; - - winmm_stop(wo); -} - -const struct audio_output_plugin winmm_output_plugin = { - "winmm", - winmm_output_test_default_device, - winmm_output_init, - winmm_output_finish, - nullptr, - nullptr, - winmm_output_open, - winmm_output_close, - nullptr, - nullptr, - winmm_output_play, - winmm_output_drain, - winmm_output_cancel, - nullptr, - &winmm_mixer_plugin, -}; diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/WinmmOutputPlugin.hxx deleted file mode 100644 index a6b7733ec..000000000 --- a/src/output/WinmmOutputPlugin.hxx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX -#define MPD_WINMM_OUTPUT_PLUGIN_HXX - -#include "check.h" - -#ifdef ENABLE_WINMM_OUTPUT - -#include "Compiler.h" - -#include <windows.h> -#include <mmsystem.h> - -struct WinmmOutput; - -extern const struct audio_output_plugin winmm_output_plugin; - -gcc_pure -HWAVEOUT -winmm_output_get_handle(WinmmOutput *); - -#endif - -#endif diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx new file mode 100644 index 000000000..a66561f0b --- /dev/null +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -0,0 +1,856 @@ +/* + * 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 "AlsaOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/ConstBuffer.hxx" +#include "Log.hxx" + +#include <alsa/asoundlib.h> + +#include <string> + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +static const char default_device[] = "default"; + +static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000; + +#define MPD_ALSA_RETRY_NR 5 + +typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, + snd_pcm_uframes_t size); + +struct AlsaOutput { + AudioOutput base; + + Manual<PcmExport> pcm_export; + + /** + * The configured name of the ALSA device; empty for the + * default device + */ + std::string device; + + /** use memory mapped I/O? */ + bool use_mmap; + + /** + * Enable DSD over USB according to the dCS suggested + * standard? + * + * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + */ + bool dsd_usb; + + /** libasound's buffer_time setting (in microseconds) */ + unsigned int buffer_time; + + /** libasound's period_time setting (in microseconds) */ + unsigned int period_time; + + /** the mode flags passed to snd_pcm_open */ + int mode; + + /** the libasound PCM device handle */ + snd_pcm_t *pcm; + + /** + * a pointer to the libasound writei() function, which is + * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the + * use_mmap configuration + */ + alsa_writei_t *writei; + + /** + * The size of one audio frame passed to method play(). + */ + size_t in_frame_size; + + /** + * The size of one audio frame passed to libasound. + */ + size_t out_frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; + + /** + * Do we need to call snd_pcm_prepare() before the next write? + * It means that we put the device to SND_PCM_STATE_SETUP by + * calling snd_pcm_drop(). + * + * Without this flag, we could easily recover after a failed + * optimistic write (returning -EBADFD), but the Raspberry Pi + * audio driver is infamous for generating ugly artefacts from + * this. + */ + bool must_prepare; + + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + uint8_t *silence; + + AlsaOutput() + :base(alsa_output_plugin), + mode(0), writei(snd_pcm_writei) { + } + + bool Init(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } +}; + +static constexpr Domain alsa_output_domain("alsa_output"); + +static const char * +alsa_device(const AlsaOutput *ad) +{ + return ad->device.empty() ? default_device : ad->device.c_str(); +} + +static void +alsa_configure(AlsaOutput *ad, const config_param ¶m) +{ + ad->device = param.GetBlockValue("device", ""); + + ad->use_mmap = param.GetBlockValue("use_mmap", false); + + ad->dsd_usb = param.GetBlockValue("dsd_usb", false); + + ad->buffer_time = param.GetBlockValue("buffer_time", + MPD_ALSA_BUFFER_TIME_US); + ad->period_time = param.GetBlockValue("period_time", 0u); + +#ifdef SND_PCM_NO_AUTO_RESAMPLE + if (!param.GetBlockValue("auto_resample", true)) + ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; +#endif + +#ifdef SND_PCM_NO_AUTO_CHANNELS + if (!param.GetBlockValue("auto_channels", true)) + ad->mode |= SND_PCM_NO_AUTO_CHANNELS; +#endif + +#ifdef SND_PCM_NO_AUTO_FORMAT + if (!param.GetBlockValue("auto_format", true)) + ad->mode |= SND_PCM_NO_AUTO_FORMAT; +#endif +} + +static AudioOutput * +alsa_init(const config_param ¶m, Error &error) +{ + AlsaOutput *ad = new AlsaOutput(); + + if (!ad->Init(param, error)) { + delete ad; + return nullptr; + } + + alsa_configure(ad, param); + + return &ad->base; +} + +static void +alsa_finish(AudioOutput *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + delete ad; + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +static bool +alsa_output_enable(AudioOutput *ao, gcc_unused Error &error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->pcm_export.Construct(); + return true; +} + +static void +alsa_output_disable(AudioOutput *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->pcm_export.Destruct(); +} + +static bool +alsa_test_default_device(void) +{ + snd_pcm_t *handle; + + int ret = snd_pcm_open(&handle, default_device, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (ret) { + FormatError(alsa_output_domain, + "Error opening default ALSA device: %s", + snd_strerror(-ret)); + return false; + } else + snd_pcm_close(handle); + + return true; +} + +static snd_pcm_format_t +get_bitformat(SampleFormat sample_format) +{ + switch (sample_format) { + case SampleFormat::UNDEFINED: + case SampleFormat::DSD: + return SND_PCM_FORMAT_UNKNOWN; + + case SampleFormat::S8: + return SND_PCM_FORMAT_S8; + + case SampleFormat::S16: + return SND_PCM_FORMAT_S16; + + case SampleFormat::S24_P32: + return SND_PCM_FORMAT_S24; + + case SampleFormat::S32: + return SND_PCM_FORMAT_S32; + + case SampleFormat::FLOAT: + return SND_PCM_FORMAT_FLOAT; + } + + assert(false); + gcc_unreachable(); +} + +static snd_pcm_format_t +byteswap_bitformat(snd_pcm_format_t fmt) +{ + switch(fmt) { + case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; + case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; + case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; + case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; + case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; + + case SND_PCM_FORMAT_S24_3BE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_3LE: + return SND_PCM_FORMAT_S24_3BE; + + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} + +static snd_pcm_format_t +alsa_to_packed_format(snd_pcm_format_t fmt) +{ + switch (fmt) { + case SND_PCM_FORMAT_S24_LE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_BE: + return SND_PCM_FORMAT_S24_3BE; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static int +alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t fmt, bool *packed_r) +{ + int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = false; + + if (err != -EINVAL) + return err; + + fmt = alsa_to_packed_format(fmt); + if (fmt == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = true; + + return err; +} + +/** + * Attempts to configure the specified sample format, and tries the + * reversed host byte order if was not supported. + */ +static int +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + SampleFormat sample_format, + bool *packed_r, bool *reverse_endian_r) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, + packed_r); + if (err == 0) + *reverse_endian_r = false; + + if (err != -EINVAL) + return err; + + alsa_format = byteswap_bitformat(alsa_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); + if (err == 0) + *reverse_endian_r = true; + + return err; +} + +/** + * Configure a sample format, and probe other formats if that fails. + */ +static int +alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + AudioFormat &audio_format, + bool *packed_r, bool *reverse_endian_r) +{ + /* try the input format first */ + + int err = alsa_output_try_format(pcm, hwparams, + audio_format.format, + packed_r, reverse_endian_r); + + /* if unsupported by the hardware, try other formats */ + + static const SampleFormat probe_formats[] = { + SampleFormat::S24_P32, + SampleFormat::S32, + SampleFormat::S16, + SampleFormat::S8, + SampleFormat::UNDEFINED, + }; + + for (unsigned i = 0; + err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED; + ++i) { + const SampleFormat mpd_format = probe_formats[i]; + if (mpd_format == audio_format.format) + continue; + + err = alsa_output_try_format(pcm, hwparams, mpd_format, + packed_r, reverse_endian_r); + if (err == 0) + audio_format.format = mpd_format; + } + + return err; +} + +/** + * Set up the snd_pcm_t object which was opened by the caller. Set up + * the configured settings and the audio format. + */ +static bool +alsa_setup(AlsaOutput *ad, AudioFormat &audio_format, + bool *packed_r, bool *reverse_endian_r, Error &error) +{ + unsigned int sample_rate = audio_format.sample_rate; + unsigned int channels = audio_format.channels; + int err; + const char *cmd = nullptr; + int retry = MPD_ALSA_RETRY_NR; + unsigned int period_time, period_time_ro; + unsigned int buffer_time; + + period_time_ro = period_time = ad->period_time; +configure_hw: + /* configure HW params */ + snd_pcm_hw_params_t *hwparams; + snd_pcm_hw_params_alloca(&hwparams); + cmd = "snd_pcm_hw_params_any"; + err = snd_pcm_hw_params_any(ad->pcm, hwparams); + if (err < 0) + goto error; + + if (ad->use_mmap) { + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (err < 0) { + FormatWarning(alsa_output_domain, + "Cannot set mmap'ed mode on ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(-err)); + LogWarning(alsa_output_domain, + "Falling back to direct write mode"); + ad->use_mmap = false; + } else + ad->writei = snd_pcm_mmap_writei; + } + + if (!ad->use_mmap) { + cmd = "snd_pcm_hw_params_set_access"; + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + goto error; + ad->writei = snd_pcm_writei; + } + + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, + packed_r, reverse_endian_r); + if (err < 0) { + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(audio_format.format), + snd_strerror(-err)); + return false; + } + + snd_pcm_format_t format; + if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) + FormatDebug(alsa_output_domain, + "format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); + + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, + &channels); + if (err < 0) { + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support %i channels: %s", + alsa_device(ad), (int)audio_format.channels, + snd_strerror(-err)); + return false; + } + audio_format.channels = (int8_t)channels; + + err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, + &sample_rate, nullptr); + if (err < 0 || sample_rate == 0) { + error.Format(alsa_output_domain, err, + "ALSA device \"%s\" does not support %u Hz audio", + alsa_device(ad), audio_format.sample_rate); + return false; + } + audio_format.sample_rate = sample_rate; + + snd_pcm_uframes_t buffer_size_min, buffer_size_max; + snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); + snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); + unsigned buffer_time_min, buffer_time_max; + snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); + snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); + FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u", + (unsigned)buffer_size_min, (unsigned)buffer_size_max, + buffer_time_min, buffer_time_max); + + snd_pcm_uframes_t period_size_min, period_size_max; + snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); + snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); + unsigned period_time_min, period_time_max; + snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); + snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); + FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u", + (unsigned)period_size_min, (unsigned)period_size_max, + period_time_min, period_time_max); + + if (ad->buffer_time > 0) { + buffer_time = ad->buffer_time; + cmd = "snd_pcm_hw_params_set_buffer_time_near"; + err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, + &buffer_time, nullptr); + if (err < 0) + goto error; + } else { + err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, + nullptr); + if (err < 0) + buffer_time = 0; + } + + if (period_time_ro == 0 && buffer_time >= 10000) { + period_time_ro = period_time = buffer_time / 4; + + FormatDebug(alsa_output_domain, + "default period_time = buffer_time/4 = %u/4 = %u", + buffer_time, period_time); + } + + if (period_time_ro > 0) { + period_time = period_time_ro; + cmd = "snd_pcm_hw_params_set_period_time_near"; + err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, + &period_time, nullptr); + if (err < 0) + goto error; + } + + cmd = "snd_pcm_hw_params"; + err = snd_pcm_hw_params(ad->pcm, hwparams); + if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { + period_time_ro = period_time_ro >> 1; + goto configure_hw; + } else if (err < 0) + goto error; + if (retry != MPD_ALSA_RETRY_NR) + FormatDebug(alsa_output_domain, + "ALSA period_time set to %d", period_time); + + snd_pcm_uframes_t alsa_buffer_size; + cmd = "snd_pcm_hw_params_get_buffer_size"; + err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); + if (err < 0) + goto error; + + snd_pcm_uframes_t alsa_period_size; + cmd = "snd_pcm_hw_params_get_period_size"; + err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, + nullptr); + if (err < 0) + goto error; + + /* configure SW params */ + snd_pcm_sw_params_t *swparams; + snd_pcm_sw_params_alloca(&swparams); + + cmd = "snd_pcm_sw_params_current"; + err = snd_pcm_sw_params_current(ad->pcm, swparams); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_start_threshold"; + err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, + alsa_buffer_size - + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_avail_min"; + err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params"; + err = snd_pcm_sw_params(ad->pcm, swparams); + if (err < 0) + goto error; + + FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u", + (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + + if (alsa_period_size == 0) + /* this works around a SIGFPE bug that occurred when + an ALSA driver indicated period_size==0; this + caused a division by zero in alsa_play(). By using + the fallback "1", we make sure that this won't + happen again. */ + alsa_period_size = 1; + + ad->period_frames = alsa_period_size; + ad->period_position = 0; + + ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm, + alsa_period_size)]; + snd_pcm_format_set_silence(format, ad->silence, + alsa_period_size * channels); + + return true; + +error: + error.Format(alsa_output_domain, err, + "Error opening ALSA device \"%s\" (%s): %s", + alsa_device(ad), cmd, snd_strerror(-err)); + return false; +} + +static bool +alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + Error &error) +{ + assert(ad->dsd_usb); + assert(audio_format.format == SampleFormat::DSD); + + /* pass 24 bit to alsa_setup() */ + + AudioFormat usb_format = audio_format; + usb_format.format = SampleFormat::S24_P32; + usb_format.sample_rate /= 2; + + const AudioFormat check = usb_format; + + if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error)) + return false; + + /* if the device allows only 32 bit, shift all DSD-over-USB + samples left by 8 bit and leave the lower 8 bit cleared; + the DSD-over-USB documentation does not specify whether + this is legal, but there is anecdotical evidence that this + is possible (and the only option for some devices) */ + *shift8_r = usb_format.format == SampleFormat::S32; + if (usb_format.format == SampleFormat::S32) + usb_format.format = SampleFormat::S24_P32; + + if (usb_format != check) { + /* no bit-perfect playback, which is required + for DSD over USB */ + error.Format(alsa_output_domain, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + delete[] ad->silence; + return false; + } + + return true; +} + +static bool +alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format, + Error &error) +{ + bool shift8 = false, packed, reverse_endian; + + const bool dsd_usb = ad->dsd_usb && + audio_format.format == SampleFormat::DSD; + const bool success = dsd_usb + ? alsa_setup_dsd(ad, audio_format, + &shift8, &packed, &reverse_endian, + error) + : alsa_setup(ad, audio_format, &packed, &reverse_endian, + error); + if (!success) + return false; + + ad->pcm_export->Open(audio_format.format, + audio_format.channels, + dsd_usb, shift8, packed, reverse_endian); + return true; +} + +static bool +alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + int err = snd_pcm_open(&ad->pcm, alsa_device(ad), + SND_PCM_STREAM_PLAYBACK, ad->mode); + if (err < 0) { + error.Format(alsa_output_domain, err, + "Failed to open ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(err)); + return false; + } + + FormatDebug(alsa_output_domain, "opened %s type=%s", + snd_pcm_name(ad->pcm), + snd_pcm_type_name(snd_pcm_type(ad->pcm))); + + if (!alsa_setup_or_dsd(ad, audio_format, error)) { + snd_pcm_close(ad->pcm); + return false; + } + + ad->in_frame_size = audio_format.GetFrameSize(); + ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format); + + ad->must_prepare = false; + + return true; +} + +/** + * Write silence to the ALSA device. + */ +static void +alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes) +{ + ad->writei(ad->pcm, ad->silence, nframes); +} + +static int +alsa_recover(AlsaOutput *ad, int err) +{ + if (err == -EPIPE) { + FormatDebug(alsa_output_domain, + "Underrun on ALSA device \"%s\"", alsa_device(ad)); + } else if (err == -ESTRPIPE) { + FormatDebug(alsa_output_domain, + "ALSA device \"%s\" was suspended", + alsa_device(ad)); + } + + switch (snd_pcm_state(ad->pcm)) { + case SND_PCM_STATE_PAUSED: + err = snd_pcm_pause(ad->pcm, /* disable */ 0); + break; + case SND_PCM_STATE_SUSPENDED: + err = snd_pcm_resume(ad->pcm); + if (err == -EAGAIN) + return 0; + /* fall-through to snd_pcm_prepare: */ + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_XRUN: + ad->period_position = 0; + err = snd_pcm_prepare(ad->pcm); + break; + case SND_PCM_STATE_DISCONNECTED: + break; + /* this is no error, so just keep running */ + case SND_PCM_STATE_RUNNING: + err = 0; + break; + default: + /* unknown state, do nothing */ + break; + } + + return err; +} + +static void +alsa_drain(AudioOutput *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + alsa_write_silence(ad, nframes); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void +alsa_cancel(AudioOutput *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->period_position = 0; + ad->must_prepare = true; + + snd_pcm_drop(ad->pcm); +} + +static void +alsa_close(AudioOutput *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + snd_pcm_close(ad->pcm); + delete[] ad->silence; +} + +static size_t +alsa_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + assert(size % ad->in_frame_size == 0); + + if (ad->must_prepare) { + ad->must_prepare = false; + + int err = snd_pcm_prepare(ad->pcm); + if (err < 0) { + error.Set(alsa_output_domain, err, snd_strerror(-err)); + return 0; + } + } + + const auto e = ad->pcm_export->Export({chunk, size}); + chunk = e.data; + size = e.size; + + assert(size % ad->out_frame_size == 0); + + size /= ad->out_frame_size; + + while (true) { + snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; + + size_t bytes_written = ret * ad->out_frame_size; + return ad->pcm_export->CalcSourceSize(bytes_written); + } + + if (ret < 0 && ret != -EAGAIN && ret != -EINTR && + alsa_recover(ad, ret) < 0) { + error.Set(alsa_output_domain, ret, snd_strerror(-ret)); + return 0; + } + } +} + +const struct AudioOutputPlugin alsa_output_plugin = { + "alsa", + alsa_test_default_device, + alsa_init, + alsa_finish, + alsa_output_enable, + alsa_output_disable, + alsa_open, + alsa_close, + nullptr, + nullptr, + alsa_play, + alsa_drain, + alsa_cancel, + nullptr, + + &alsa_mixer_plugin, +}; diff --git a/src/output/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx new file mode 100644 index 000000000..f72116f91 --- /dev/null +++ b/src/output/plugins/AlsaOutputPlugin.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_ALSA_OUTPUT_PLUGIN_HXX +#define MPD_ALSA_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin alsa_output_plugin; + +#endif diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx new file mode 100644 index 000000000..af8c88fa1 --- /dev/null +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -0,0 +1,282 @@ +/* + * 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 "AoOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <ao/ao.h> +#include <glib.h> + +#include <string.h> + +/* An ao_sample_format, with all fields set to zero: */ +static ao_sample_format OUR_AO_FORMAT_INITIALIZER; + +static unsigned ao_output_ref; + +struct AoOutput { + AudioOutput base; + + size_t write_size; + int driver; + ao_option *options; + ao_device *device; + + AoOutput() + :base(ao_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain ao_output_domain("ao_output"); + +static void +ao_output_error(Error &error_r) +{ + const char *error; + + switch (errno) { + case AO_ENODRIVER: + error = "No such libao driver"; + break; + + case AO_ENOTLIVE: + error = "This driver is not a libao live device"; + break; + + case AO_EBADOPTION: + error = "Invalid libao option"; + break; + + case AO_EOPENDEVICE: + error = "Cannot open the libao device"; + break; + + case AO_EFAIL: + error = "Generic libao failure"; + break; + + default: + error_r.SetErrno(); + return; + } + + error_r.Set(ao_output_domain, errno, error); +} + +inline bool +AoOutput::Configure(const config_param ¶m, Error &error) +{ + const char *value; + + options = nullptr; + + write_size = param.GetBlockValue("write_size", 1024u); + + if (ao_output_ref == 0) { + ao_initialize(); + } + ao_output_ref++; + + value = param.GetBlockValue("driver", "default"); + if (0 == strcmp(value, "default")) + driver = ao_default_driver_id(); + else + driver = ao_driver_id(value); + + if (driver < 0) { + error.Format(ao_output_domain, + "\"%s\" is not a valid ao driver", + value); + return false; + } + + ao_info *ai = ao_driver_info(driver); + if (ai == nullptr) { + error.Set(ao_output_domain, "problems getting driver info"); + return false; + } + + FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n", + ai->short_name, param.GetBlockValue("name", nullptr)); + + value = param.GetBlockValue("options", nullptr); + if (value != nullptr) { + gchar **_options = g_strsplit(value, ";", 0); + + for (unsigned i = 0; _options[i] != nullptr; ++i) { + gchar **key_value = g_strsplit(_options[i], "=", 2); + + if (key_value[0] == nullptr || key_value[1] == nullptr) { + error.Format(ao_output_domain, + "problems parsing options \"%s\"", + _options[i]); + return false; + } + + ao_append_option(&options, key_value[0], + key_value[1]); + + g_strfreev(key_value); + } + + g_strfreev(_options); + } + + return true; +} + +static AudioOutput * +ao_output_init(const config_param ¶m, Error &error) +{ + AoOutput *ad = new AoOutput(); + + if (!ad->Initialize(param, error)) { + delete ad; + return nullptr; + } + + if (!ad->Configure(param, error)) { + delete ad; + return nullptr; + } + + return &ad->base; +} + +static void +ao_output_finish(AudioOutput *ao) +{ + AoOutput *ad = (AoOutput *)ao; + + ao_free_options(ad->options); + delete ad; + + ao_output_ref--; + + if (ao_output_ref == 0) + ao_shutdown(); +} + +static void +ao_output_close(AudioOutput *ao) +{ + AoOutput *ad = (AoOutput *)ao; + + ao_close(ad->device); +} + +static bool +ao_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; + AoOutput *ad = (AoOutput *)ao; + + switch (audio_format.format) { + case SampleFormat::S8: + format.bits = 8; + break; + + case SampleFormat::S16: + format.bits = 16; + break; + + default: + /* support for 24 bit samples in libao is currently + dubious, and until we have sorted that out, + convert everything to 16 bit */ + audio_format.format = SampleFormat::S16; + format.bits = 16; + break; + } + + format.rate = audio_format.sample_rate; + format.byte_format = AO_FMT_NATIVE; + format.channels = audio_format.channels; + + ad->device = ao_open_live(ad->driver, &format, ad->options); + + if (ad->device == nullptr) { + ao_output_error(error); + return false; + } + + return true; +} + +/** + * For whatever reason, libao wants a non-const pointer. Let's hope + * it does not write to the buffer, and use the union deconst hack to + * work around this API misdesign. + */ +static int ao_play_deconst(ao_device *device, const void *output_samples, + uint_32 num_bytes) +{ + union { + const void *in; + char *out; + } u; + + u.in = output_samples; + return ao_play(device, u.out, num_bytes); +} + +static size_t +ao_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + AoOutput *ad = (AoOutput *)ao; + + if (size > ad->write_size) + size = ad->write_size; + + if (ao_play_deconst(ad->device, chunk, size) == 0) { + ao_output_error(error); + return 0; + } + + return size; +} + +const struct AudioOutputPlugin ao_output_plugin = { + "ao", + nullptr, + ao_output_init, + ao_output_finish, + nullptr, + nullptr, + ao_output_open, + ao_output_close, + nullptr, + nullptr, + ao_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx new file mode 100644 index 000000000..07c2ba16b --- /dev/null +++ b/src/output/plugins/AoOutputPlugin.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_AO_OUTPUT_PLUGIN_HXX +#define MPD_AO_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin ao_output_plugin; + +#endif diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx new file mode 100644 index 000000000..9df5a74dd --- /dev/null +++ b/src/output/plugins/FifoOutputPlugin.cxx @@ -0,0 +1,307 @@ +/* + * 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 "FifoOutputPlugin.hxx" +#include "config/ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "../Timer.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "open.h" + +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> + +#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ + +struct FifoOutput { + AudioOutput base; + + AllocatedPath path; + std::string path_utf8; + + int input; + int output; + bool created; + Timer *timer; + + FifoOutput() + :base(fifo_output_plugin), + path(AllocatedPath::Null()), input(-1), output(-1), + created(false) {} + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Create(Error &error); + bool Check(Error &error); + void Delete(); + + bool Open(Error &error); + void Close(); +}; + +static constexpr Domain fifo_output_domain("fifo_output"); + +inline void +FifoOutput::Delete() +{ + FormatDebug(fifo_output_domain, + "Removing FIFO \"%s\"", path_utf8.c_str()); + + if (!RemoveFile(path)) { + FormatErrno(fifo_output_domain, + "Could not remove FIFO \"%s\"", + path_utf8.c_str()); + return; + } + + created = false; +} + +void +FifoOutput::Close() +{ + if (input >= 0) { + close(input); + input = -1; + } + + if (output >= 0) { + close(output); + output = -1; + } + + struct stat st; + if (created && StatFile(path, st)) + Delete(); +} + +inline bool +FifoOutput::Create(Error &error) +{ + if (!MakeFifo(path, 0666)) { + error.FormatErrno("Couldn't create FIFO \"%s\"", + path_utf8.c_str()); + return false; + } + + created = true; + return true; +} + +inline bool +FifoOutput::Check(Error &error) +{ + struct stat st; + if (!StatFile(path, st)) { + if (errno == ENOENT) { + /* Path doesn't exist */ + return Create(error); + } + + error.FormatErrno("Failed to stat FIFO \"%s\"", + path_utf8.c_str()); + return false; + } + + if (!S_ISFIFO(st.st_mode)) { + error.Format(fifo_output_domain, + "\"%s\" already exists, but is not a FIFO", + path_utf8.c_str()); + return false; + } + + return true; +} + +inline bool +FifoOutput::Open(Error &error) +{ + if (!Check(error)) + return false; + + input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0); + if (input < 0) { + error.FormatErrno("Could not open FIFO \"%s\" for reading", + path_utf8.c_str()); + Close(); + return false; + } + + output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0); + if (output < 0) { + error.FormatErrno("Could not open FIFO \"%s\" for writing", + path_utf8.c_str()); + Close(); + return false; + } + + return true; +} + +static bool +fifo_open(FifoOutput *fd, Error &error) +{ + return fd->Open(error); +} + +static AudioOutput * +fifo_output_init(const config_param ¶m, Error &error) +{ + FifoOutput *fd = new FifoOutput(); + + fd->path = param.GetBlockPath("path", error); + if (fd->path.IsNull()) { + delete fd; + + if (!error.IsDefined()) + error.Set(config_domain, + "No \"path\" parameter specified"); + return nullptr; + } + + fd->path_utf8 = fd->path.ToUTF8(); + + if (!fd->Initialize(param, error)) { + delete fd; + return nullptr; + } + + if (!fifo_open(fd, error)) { + delete fd; + return nullptr; + } + + return &fd->base; +} + +static void +fifo_output_finish(AudioOutput *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + fd->Close(); + delete fd; +} + +static bool +fifo_output_open(AudioOutput *ao, AudioFormat &audio_format, + gcc_unused Error &error) +{ + FifoOutput *fd = (FifoOutput *)ao; + + fd->timer = new Timer(audio_format); + + return true; +} + +static void +fifo_output_close(AudioOutput *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + delete fd->timer; +} + +static void +fifo_output_cancel(AudioOutput *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + char buf[FIFO_BUFFER_SIZE]; + int bytes = 1; + + fd->timer->Reset(); + + while (bytes > 0 && errno != EINTR) + bytes = read(fd->input, buf, FIFO_BUFFER_SIZE); + + if (bytes < 0 && errno != EAGAIN) { + FormatErrno(fifo_output_domain, + "Flush of FIFO \"%s\" failed", + fd->path_utf8.c_str()); + } +} + +static unsigned +fifo_output_delay(AudioOutput *ao) +{ + FifoOutput *fd = (FifoOutput *)ao; + + return fd->timer->IsStarted() + ? fd->timer->GetDelay() + : 0; +} + +static size_t +fifo_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + FifoOutput *fd = (FifoOutput *)ao; + ssize_t bytes; + + if (!fd->timer->IsStarted()) + fd->timer->Start(); + fd->timer->Add(size); + + while (true) { + bytes = write(fd->output, chunk, size); + if (bytes > 0) + return (size_t)bytes; + + if (bytes < 0) { + switch (errno) { + case EAGAIN: + /* The pipe is full, so empty it */ + fifo_output_cancel(&fd->base); + continue; + case EINTR: + continue; + } + + error.FormatErrno("Failed to write to FIFO %s", + fd->path_utf8.c_str()); + return 0; + } + } +} + +const struct AudioOutputPlugin fifo_output_plugin = { + "fifo", + nullptr, + fifo_output_init, + fifo_output_finish, + nullptr, + nullptr, + fifo_output_open, + fifo_output_close, + fifo_output_delay, + nullptr, + fifo_output_play, + nullptr, + fifo_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx new file mode 100644 index 000000000..f41ceded6 --- /dev/null +++ b/src/output/plugins/FifoOutputPlugin.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_FIFO_OUTPUT_PLUGIN_HXX +#define MPD_FIFO_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin fifo_output_plugin; + +#endif diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx new file mode 100644 index 000000000..e1dad7893 --- /dev/null +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -0,0 +1,762 @@ +/* + * 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 "JackOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +#include <glib.h> +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +#include <stdlib.h> +#include <string.h> + +enum { + MAX_PORTS = 16, +}; + +static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); + +struct JackOutput { + AudioOutput base; + + /** + * libjack options passed to jack_client_open(). + */ + jack_options_t options; + + const char *name; + + const char *server_name; + + /* configuration */ + + char *source_ports[MAX_PORTS]; + unsigned num_source_ports; + + char *destination_ports[MAX_PORTS]; + unsigned num_destination_ports; + + size_t ringbuffer_size; + + /* the current audio format */ + AudioFormat audio_format; + + /* jack library stuff */ + jack_port_t *ports[MAX_PORTS]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[MAX_PORTS]; + + bool shutdown; + + /** + * While this flag is set, the "process" callback generates + * silence. + */ + bool pause; + + JackOutput() + :base(jack_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); + } +}; + +static constexpr Domain jack_output_domain("jack_output"); + +/** + * Determine the number of frames guaranteed to be available on all + * channels. + */ +static jack_nframes_t +mpd_jack_available(const JackOutput *jd) +{ + size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + if (current < min) + min = current; + } + + assert(min % jack_sample_size == 0); + + return min / jack_sample_size; +} + +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + JackOutput *jd = (JackOutput *) arg; + + if (nframes <= 0) + return 0; + + if (jd->pause) { + /* empty the ring buffers */ + + const jack_nframes_t available = mpd_jack_available(jd); + for (unsigned i = 0; i < jd->audio_format.channels; ++i) + jack_ringbuffer_read_advance(jd->ringbuffer[i], + available * jack_sample_size); + + /* generate silence while MPD is paused */ + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; + } + + jack_nframes_t available = mpd_jack_available(jd); + if (available > nframes) + available = nframes; + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + + jack_ringbuffer_read(jd->ringbuffer[i], + (char *)out, available * jack_sample_size); + + for (jack_nframes_t f = available; f < nframes; ++f) + /* ringbuffer underrun, fill with silence */ + out[f] = 0.0; + } + + /* generate silence for the unused source ports */ + + for (unsigned i = jd->audio_format.channels; + i < jd->num_source_ports; ++i) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; +} + +static void +mpd_jack_shutdown(void *arg) +{ + JackOutput *jd = (JackOutput *) arg; + jd->shutdown = true; +} + +static void +set_audioformat(JackOutput *jd, AudioFormat &audio_format) +{ + audio_format.sample_rate = jack_get_sample_rate(jd->client); + + if (jd->num_source_ports == 1) + audio_format.channels = 1; + else if (audio_format.channels > jd->num_source_ports) + audio_format.channels = 2; + + if (audio_format.format != SampleFormat::S16 && + audio_format.format != SampleFormat::S24_P32) + audio_format.format = SampleFormat::S24_P32; +} + +static void +mpd_jack_error(const char *msg) +{ + LogError(jack_output_domain, msg); +} + +#ifdef HAVE_JACK_SET_INFO_FUNCTION +static void +mpd_jack_info(const char *msg) +{ + LogDefault(jack_output_domain, msg); +} +#endif + +/** + * Disconnect the JACK client. + */ +static void +mpd_jack_disconnect(JackOutput *jd) +{ + assert(jd != nullptr); + assert(jd->client != nullptr); + + jack_deactivate(jd->client); + jack_client_close(jd->client); + jd->client = nullptr; +} + +/** + * Connect the JACK client and performs some basic setup + * (e.g. register callbacks). + */ +static bool +mpd_jack_connect(JackOutput *jd, Error &error) +{ + jack_status_t status; + + assert(jd != nullptr); + + jd->shutdown = false; + + jd->client = jack_client_open(jd->name, jd->options, &status, + jd->server_name); + if (jd->client == nullptr) { + error.Format(jack_output_domain, status, + "Failed to connect to JACK server, status=%d", + status); + return false; + } + + jack_set_process_callback(jd->client, mpd_jack_process, jd); + jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + jd->ports[i] = jack_port_register(jd->client, + jd->source_ports[i], + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (jd->ports[i] == nullptr) { + error.Format(jack_output_domain, + "Cannot register output port \"%s\"", + jd->source_ports[i]); + mpd_jack_disconnect(jd); + return false; + } + } + + return true; +} + +static bool +mpd_jack_test_default_device(void) +{ + return true; +} + +static unsigned +parse_port_list(const char *source, char **dest, Error &error) +{ + char **list = g_strsplit(source, ",", 0); + unsigned n = 0; + + for (n = 0; list[n] != nullptr; ++n) { + if (n >= MAX_PORTS) { + error.Set(config_domain, + "too many port names"); + return 0; + } + + dest[n] = list[n]; + } + + g_free(list); + + if (n == 0) { + error.Format(config_domain, + "at least one port name expected"); + return 0; + } + + return n; +} + +static AudioOutput * +mpd_jack_init(const config_param ¶m, Error &error) +{ + JackOutput *jd = new JackOutput(); + + if (!jd->Initialize(param, error)) { + delete jd; + return nullptr; + } + + const char *value; + + jd->options = JackNullOption; + + jd->name = param.GetBlockValue("client_name", nullptr); + if (jd->name != nullptr) + jd->options = jack_options_t(jd->options | JackUseExactName); + else + /* if there's a no configured client name, we don't + care about the JackUseExactName option */ + jd->name = "Music Player Daemon"; + + jd->server_name = param.GetBlockValue("server_name", nullptr); + if (jd->server_name != nullptr) + jd->options = jack_options_t(jd->options | JackServerName); + + if (!param.GetBlockValue("autostart", false)) + jd->options = jack_options_t(jd->options | JackNoStartServer); + + /* configure the source ports */ + + value = param.GetBlockValue("source_ports", "left,right"); + jd->num_source_ports = parse_port_list(value, + jd->source_ports, error); + if (jd->num_source_ports == 0) + return nullptr; + + /* configure the destination ports */ + + value = param.GetBlockValue("destination_ports", nullptr); + if (value == nullptr) { + /* compatibility with MPD < 0.16 */ + value = param.GetBlockValue("ports", nullptr); + if (value != nullptr) + FormatWarning(jack_output_domain, + "deprecated option 'ports' in line %d", + param.line); + } + + if (value != nullptr) { + jd->num_destination_ports = + parse_port_list(value, + jd->destination_ports, error); + if (jd->num_destination_ports == 0) + return nullptr; + } else { + jd->num_destination_ports = 0; + } + + if (jd->num_destination_ports > 0 && + jd->num_destination_ports != jd->num_source_ports) + FormatWarning(jack_output_domain, + "number of source ports (%u) mismatches the " + "number of destination ports (%u) in line %d", + jd->num_source_ports, jd->num_destination_ports, + param.line); + + jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return &jd->base; +} + +static void +mpd_jack_finish(AudioOutput *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + g_free(jd->source_ports[i]); + + for (unsigned i = 0; i < jd->num_destination_ports; ++i) + g_free(jd->destination_ports[i]); + + delete jd; +} + +static bool +mpd_jack_enable(AudioOutput *ao, Error &error) +{ + JackOutput *jd = (JackOutput *)ao; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + jd->ringbuffer[i] = nullptr; + + return mpd_jack_connect(jd, error); +} + +static void +mpd_jack_disable(AudioOutput *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + if (jd->client != nullptr) + mpd_jack_disconnect(jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] != nullptr) { + jack_ringbuffer_free(jd->ringbuffer[i]); + jd->ringbuffer[i] = nullptr; + } + } +} + +/** + * Stops the playback on the JACK connection. + */ +static void +mpd_jack_stop(JackOutput *jd) +{ + assert(jd != nullptr); + + if (jd->client == nullptr) + return; + + if (jd->shutdown) + /* the connection has failed; close it */ + mpd_jack_disconnect(jd); + else + /* the connection is alive: just stop playback */ + jack_deactivate(jd->client); +} + +static bool +mpd_jack_start(JackOutput *jd, Error &error) +{ + const char *destination_ports[MAX_PORTS], **jports; + const char *duplicate_port = nullptr; + unsigned num_destination_ports; + + assert(jd->client != nullptr); + assert(jd->audio_format.channels <= jd->num_source_ports); + + /* allocate the ring buffers on the first open(); these + persist until MPD exits. It's too unsafe to delete them + because we can never know when mpd_jack_process() gets + called */ + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] == nullptr) + jd->ringbuffer[i] = + jack_ringbuffer_create(jd->ringbuffer_size); + + /* clear the ring buffer to be sure that data from + previous playbacks are gone */ + jack_ringbuffer_reset(jd->ringbuffer[i]); + } + + if ( jack_activate(jd->client) ) { + error.Set(jack_output_domain, "cannot activate client"); + mpd_jack_stop(jd); + return false; + } + + if (jd->num_destination_ports == 0) { + /* no output ports were configured - ask libjack for + defaults */ + jports = jack_get_ports(jd->client, nullptr, nullptr, + JackPortIsPhysical | JackPortIsInput); + if (jports == nullptr) { + error.Set(jack_output_domain, "no ports found"); + mpd_jack_stop(jd); + return false; + } + + assert(*jports != nullptr); + + for (num_destination_ports = 0; + num_destination_ports < MAX_PORTS && + jports[num_destination_ports] != nullptr; + ++num_destination_ports) { + FormatDebug(jack_output_domain, + "destination_port[%u] = '%s'\n", + num_destination_ports, + jports[num_destination_ports]); + destination_ports[num_destination_ports] = + jports[num_destination_ports]; + } + } else { + /* use the configured output ports */ + + num_destination_ports = jd->num_destination_ports; + memcpy(destination_ports, jd->destination_ports, + num_destination_ports * sizeof(*destination_ports)); + + jports = nullptr; + } + + assert(num_destination_ports > 0); + + if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + /* mix stereo signal on one speaker */ + + while (num_destination_ports < jd->audio_format.channels) + destination_ports[num_destination_ports++] = + destination_ports[0]; + } else if (num_destination_ports > jd->audio_format.channels) { + if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + /* mono input file: connect the one source + channel to the both destination channels */ + duplicate_port = destination_ports[1]; + num_destination_ports = 1; + } else + /* connect only as many ports as we need */ + num_destination_ports = jd->audio_format.channels; + } + + assert(num_destination_ports <= jd->num_source_ports); + + for (unsigned i = 0; i < num_destination_ports; ++i) { + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), + destination_ports[i]); + if (ret != 0) { + error.Format(jack_output_domain, + "Not a valid JACK port: %s", + destination_ports[i]); + + if (jports != nullptr) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (duplicate_port != nullptr) { + /* mono input file: connect the one source channel to + the both destination channels */ + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + duplicate_port); + if (ret != 0) { + error.Format(jack_output_domain, + "Not a valid JACK port: %s", + duplicate_port); + + if (jports != nullptr) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (jports != nullptr) + free(jports); + + return true; +} + +static bool +mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + JackOutput *jd = (JackOutput *)ao; + + assert(jd != nullptr); + + jd->pause = false; + + if (jd->client != nullptr && jd->shutdown) + mpd_jack_disconnect(jd); + + if (jd->client == nullptr && !mpd_jack_connect(jd, error)) + return false; + + set_audioformat(jd, audio_format); + jd->audio_format = audio_format; + + if (!mpd_jack_start(jd, error)) + return false; + + return true; +} + +static void +mpd_jack_close(gcc_unused AudioOutput *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + mpd_jack_stop(jd); +} + +static unsigned +mpd_jack_delay(AudioOutput *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + return jd->base.pause && jd->pause && !jd->shutdown + ? 1000 + : 0; +} + +static inline jack_default_audio_sample_t +sample_16_to_jack(int16_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); +} + +static void +mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_16_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], + (const char *)&sample, + sizeof(sample)); + } + } +} + +static inline jack_default_audio_sample_t +sample_24_to_jack(int32_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); +} + +static void +mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_24_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], + (const char *)&sample, + sizeof(sample)); + } + } +} + +static void +mpd_jack_write_samples(JackOutput *jd, const void *src, + unsigned num_samples) +{ + switch (jd->audio_format.format) { + case SampleFormat::S16: + mpd_jack_write_samples_16(jd, (const int16_t*)src, + num_samples); + break; + + case SampleFormat::S24_P32: + mpd_jack_write_samples_24(jd, (const int32_t*)src, + num_samples); + break; + + default: + assert(false); + gcc_unreachable(); + } +} + +static size_t +mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + JackOutput *jd = (JackOutput *)ao; + const size_t frame_size = jd->audio_format.GetFrameSize(); + size_t space = 0, space1; + + jd->pause = false; + + assert(size % frame_size == 0); + size /= frame_size; + + while (true) { + if (jd->shutdown) { + error.Set(jack_output_domain, + "Refusing to play, because " + "there is no client thread"); + return 0; + } + + space = jack_ringbuffer_write_space(jd->ringbuffer[0]); + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); + if (space > space1) + /* send data symmetrically */ + space = space1; + } + + if (space >= jack_sample_size) + break; + + /* XXX do something more intelligent to + synchronize */ + g_usleep(1000); + } + + space /= jack_sample_size; + if (space < size) + size = space; + + mpd_jack_write_samples(jd, chunk, size); + return size * frame_size; +} + +static bool +mpd_jack_pause(AudioOutput *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + if (jd->shutdown) + return false; + + jd->pause = true; + + return true; +} + +const struct AudioOutputPlugin jack_output_plugin = { + "jack", + mpd_jack_test_default_device, + mpd_jack_init, + mpd_jack_finish, + mpd_jack_enable, + mpd_jack_disable, + mpd_jack_open, + mpd_jack_close, + mpd_jack_delay, + nullptr, + mpd_jack_play, + nullptr, + nullptr, + mpd_jack_pause, + nullptr, +}; diff --git a/src/output/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx new file mode 100644 index 000000000..6f1f7ecb9 --- /dev/null +++ b/src/output/plugins/JackOutputPlugin.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_JACK_OUTPUT_PLUGIN_HXX +#define MPD_JACK_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin jack_output_plugin; + +#endif diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx new file mode 100644 index 000000000..098f58926 --- /dev/null +++ b/src/output/plugins/NullOutputPlugin.cxx @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "NullOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "../Timer.hxx" + +struct NullOutput { + AudioOutput base; + + bool sync; + + Timer *timer; + + NullOutput() + :base(null_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } +}; + +static AudioOutput * +null_init(const config_param ¶m, Error &error) +{ + NullOutput *nd = new NullOutput(); + + if (!nd->Initialize(param, error)) { + delete nd; + return nullptr; + } + + nd->sync = param.GetBlockValue("sync", true); + + return &nd->base; +} + +static void +null_finish(AudioOutput *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + delete nd; +} + +static bool +null_open(AudioOutput *ao, AudioFormat &audio_format, + gcc_unused Error &error) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + nd->timer = new Timer(audio_format); + + return true; +} + +static void +null_close(AudioOutput *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (nd->sync) + delete nd->timer; +} + +static unsigned +null_delay(AudioOutput *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + return nd->sync && nd->timer->IsStarted() + ? nd->timer->GetDelay() + : 0; +} + +static size_t +null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size, + gcc_unused Error &error) +{ + NullOutput *nd = (NullOutput *)ao; + Timer *timer = nd->timer; + + if (!nd->sync) + return size; + + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + + return size; +} + +static void +null_cancel(AudioOutput *ao) +{ + NullOutput *nd = (NullOutput *)ao; + + if (!nd->sync) + return; + + nd->timer->Reset(); +} + +const struct AudioOutputPlugin null_output_plugin = { + "null", + nullptr, + null_init, + null_finish, + nullptr, + nullptr, + null_open, + null_close, + null_delay, + nullptr, + null_play, + nullptr, + null_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx new file mode 100644 index 000000000..f25f5b9f3 --- /dev/null +++ b/src/output/plugins/NullOutputPlugin.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_NULL_OUTPUT_PLUGIN_HXX +#define MPD_NULL_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin null_output_plugin; + +#endif diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx new file mode 100644 index 000000000..13ac7b35e --- /dev/null +++ b/src/output/plugins/OSXOutputPlugin.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. + */ + +#include "config.h" +#include "OSXOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#include <CoreAudio/AudioHardware.h> +#include <AudioUnit/AudioUnit.h> +#include <CoreServices/CoreServices.h> + +struct OSXOutput { + AudioOutput base; + + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + + AudioUnit au; + Mutex mutex; + Cond condition; + + DynamicFifoBuffer<uint8_t> *buffer; + + OSXOutput() + :base(osx_output_plugin) {} +}; + +static constexpr Domain osx_output_domain("osx_output"); + +static bool +osx_output_test_default_device(void) +{ + /* on a Mac, this is always the default plugin, if nothing + else is configured */ + return true; +} + +static void +osx_output_configure(OSXOutput *oo, const config_param ¶m) +{ + const char *device = param.GetBlockValue("device"); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to strdup() this? */ + oo->device_name = device; + } +} + +static AudioOutput * +osx_output_init(const config_param ¶m, Error &error) +{ + OSXOutput *oo = new OSXOutput(); + if (!oo->base.Configure(param, error)) { + delete oo; + return NULL; + } + + osx_output_configure(oo, param); + + return &oo->base; +} + +static void +osx_output_finish(AudioOutput *ao) +{ + OSXOutput *oo = (OSXOutput *)ao; + + delete oo; +} + +static bool +osx_output_set_device(OSXOutput *oo, Error &error) +{ + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = new AudioDeviceID[numdevices]; + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + FormatDebug(osx_output_domain, + "found matching device: ID=%u, name=%s", + (unsigned)deviceids[i], name); + break; + } + } + if (i == numdevices) { + FormatWarning(osx_output_domain, + "Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } + + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + FormatDebug(osx_output_domain, + "set OS X audio output device ID=%u, name=%s", + (unsigned)deviceids[i], name); + +done: + delete[] deviceids; + return ret; +} + +static OSStatus +osx_render(void *vdata, + gcc_unused AudioUnitRenderActionFlags *io_action_flags, + gcc_unused const AudioTimeStamp *in_timestamp, + gcc_unused UInt32 in_bus_number, + gcc_unused UInt32 in_number_frames, + AudioBufferList *buffer_list) +{ + OSXOutput *od = (OSXOutput *) vdata; + AudioBuffer *buffer = &buffer_list->mBuffers[0]; + size_t buffer_size = buffer->mDataByteSize; + + assert(od->buffer != NULL); + + od->mutex.lock(); + + auto src = od->buffer->Read(); + if (!src.IsEmpty()) { + if (src.size > buffer_size) + src.size = buffer_size; + + memcpy(buffer->mData, src.data, src.size); + od->buffer->Consume(src.size); + } + + od->condition.signal(); + od->mutex.unlock(); + + buffer->mDataByteSize = src.size; + + unsigned i; + for (i = 1; i < buffer_list->mNumberBuffers; ++i) { + buffer = &buffer_list->mBuffers[i]; + buffer->mDataByteSize = 0; + } + + return 0; +} + +static bool +osx_output_enable(AudioOutput *ao, Error &error) +{ + OSXOutput *oo = (OSXOutput *)ao; + + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = oo->component_subtype; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == 0) { + error.Set(osx_output_domain, + "Error finding OS X component"); + return false; + } + + OSStatus status = OpenAComponent(comp, &oo->au); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to open OS X component: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + if (!osx_output_set_device(oo, error)) { + CloseComponent(oo->au); + return false; + } + + AURenderCallbackStruct callback; + callback.inputProc = osx_render; + callback.inputProcRefCon = oo; + + ComponentResult result = + AudioUnitSetProperty(oo->au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)); + if (result != noErr) { + CloseComponent(oo->au); + error.Set(osx_output_domain, result, + "unable to set callback for OS X audio unit"); + return false; + } + + return true; +} + +static void +osx_output_disable(AudioOutput *ao) +{ + OSXOutput *oo = (OSXOutput *)ao; + + CloseComponent(oo->au); +} + +static void +osx_output_cancel(AudioOutput *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + const ScopeLock protect(od->mutex); + od->buffer->Clear(); +} + +static void +osx_output_close(AudioOutput *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioOutputUnitStop(od->au); + AudioUnitUninitialize(od->au); + + delete od->buffer; +} + +static bool +osx_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioStreamBasicDescription stream_description; + stream_description.mSampleRate = audio_format.sample_rate; + stream_description.mFormatID = kAudioFormatLinearPCM; + stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + + switch (audio_format.format) { + case SampleFormat::S8: + stream_description.mBitsPerChannel = 8; + break; + + case SampleFormat::S16: + stream_description.mBitsPerChannel = 16; + break; + + case SampleFormat::S32: + stream_description.mBitsPerChannel = 32; + break; + + default: + audio_format.format = SampleFormat::S32; + stream_description.mBitsPerChannel = 32; + break; + } + + if (IsBigEndian()) + stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; + + stream_description.mBytesPerPacket = audio_format.GetFrameSize(); + stream_description.mFramesPerPacket = 1; + stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; + stream_description.mChannelsPerFrame = audio_format.channels; + + ComponentResult result = + AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &stream_description, + sizeof(stream_description)); + if (result != noErr) { + error.Set(osx_output_domain, result, + "Unable to set format on OS X device"); + return false; + } + + OSStatus status = AudioUnitInitialize(od->au); + if (status != noErr) { + error.Format(osx_output_domain, status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + /* create a buffer of 1s */ + od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate * + audio_format.GetFrameSize()); + + status = AudioOutputUnitStart(od->au); + if (status != 0) { + AudioUnitUninitialize(od->au); + error.Format(osx_output_domain, status, + "unable to start audio output: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + return true; +} + +static size_t +osx_output_play(AudioOutput *ao, const void *chunk, size_t size, + gcc_unused Error &error) +{ + OSXOutput *od = (OSXOutput *)ao; + + const ScopeLock protect(od->mutex); + + DynamicFifoBuffer<uint8_t>::Range dest; + while (true) { + dest = od->buffer->Write(); + if (!dest.IsEmpty()) + break; + + /* wait for some free space in the buffer */ + od->condition.wait(od->mutex); + } + + if (size > dest.size) + size = dest.size; + + memcpy(dest.data, chunk, size); + od->buffer->Append(size); + + return size; +} + +const struct AudioOutputPlugin osx_output_plugin = { + "osx", + osx_output_test_default_device, + osx_output_init, + osx_output_finish, + osx_output_enable, + osx_output_disable, + osx_output_open, + osx_output_close, + nullptr, + nullptr, + osx_output_play, + nullptr, + osx_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx new file mode 100644 index 000000000..d7aed40b6 --- /dev/null +++ b/src/output/plugins/OSXOutputPlugin.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_OSX_OUTPUT_PLUGIN_HXX +#define MPD_OSX_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin osx_output_plugin; + +#endif diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx new file mode 100644 index 000000000..2f095c0a4 --- /dev/null +++ b/src/output/plugins/OpenALOutputPlugin.cxx @@ -0,0 +1,282 @@ +/* + * 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 "OpenALOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <unistd.h> + +#ifndef __APPLE__ +#include <AL/al.h> +#include <AL/alc.h> +#else +#include <OpenAL/al.h> +#include <OpenAL/alc.h> +#endif + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct OpenALOutput { + AudioOutput base; + + const char *device_name; + ALCdevice *device; + ALCcontext *context; + ALuint buffers[NUM_BUFFERS]; + unsigned filled; + ALuint source; + ALenum format; + ALuint frequency; + + OpenALOutput() + :base(openal_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); + } +}; + +static constexpr Domain openal_output_domain("openal_output"); + +static ALenum +openal_audio_format(AudioFormat &audio_format) +{ + /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or + AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit + samples, while MPD uses signed samples */ + + switch (audio_format.format) { + case SampleFormat::S16: + if (audio_format.channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format.channels == 1) + return AL_FORMAT_MONO16; + + /* fall back to mono */ + audio_format.channels = 1; + return openal_audio_format(audio_format); + + default: + /* fall back to 16 bit */ + audio_format.format = SampleFormat::S16; + return openal_audio_format(audio_format); + } +} + +gcc_pure +static inline ALint +openal_get_source_i(const OpenALOutput *od, ALenum param) +{ + ALint value; + alGetSourcei(od->source, param, &value); + return value; +} + +gcc_pure +static inline bool +openal_has_processed(const OpenALOutput *od) +{ + return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; +} + +gcc_pure +static inline ALint +openal_is_playing(const OpenALOutput *od) +{ + return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; +} + +static bool +openal_setup_context(OpenALOutput *od, Error &error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == nullptr) { + error.Format(openal_output_domain, + "Error opening OpenAL device \"%s\"", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, nullptr); + + if (od->context == nullptr) { + error.Format(openal_output_domain, + "Error creating context for \"%s\"", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + return true; +} + +static AudioOutput * +openal_init(const config_param ¶m, Error &error) +{ + const char *device_name = param.GetBlockValue("device"); + if (device_name == nullptr) { + device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + OpenALOutput *od = new OpenALOutput(); + if (!od->Initialize(param, error)) { + delete od; + return nullptr; + } + + od->device_name = device_name; + + return &od->base; +} + +static void +openal_finish(AudioOutput *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + delete od; +} + +static bool +openal_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + od->format = openal_audio_format(audio_format); + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + error.Set(openal_output_domain, "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + error.Set(openal_output_domain, "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->frequency = audio_format.sample_rate; + + return true; +} + +static void +openal_close(AudioOutput *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static unsigned +openal_delay(AudioOutput *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + return od->filled < NUM_BUFFERS || openal_has_processed(od) + ? 0 + /* we don't know exactly how long we must wait for the + next buffer to finish, so this is a random + guess: */ + : 50; +} + +static size_t +openal_play(AudioOutput *ao, const void *chunk, size_t size, + gcc_unused Error &error) +{ + OpenALOutput *od = (OpenALOutput *)ao; + ALuint buffer; + + if (alcGetCurrentContext() != od->context) { + alcMakeContextCurrent(od->context); + } + + if (od->filled < NUM_BUFFERS) { + /* fill all buffers */ + buffer = od->buffers[od->filled]; + od->filled++; + } else { + /* wait for processed buffer */ + while (!openal_has_processed(od)) + usleep(10); + + alSourceUnqueueBuffers(od->source, 1, &buffer); + } + + alBufferData(buffer, od->format, chunk, size, od->frequency); + alSourceQueueBuffers(od->source, 1, &buffer); + + if (!openal_is_playing(od)) + alSourcePlay(od->source); + + return size; +} + +static void +openal_cancel(AudioOutput *ao) +{ + OpenALOutput *od = (OpenALOutput *)ao; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + + /* force-unqueue all buffers */ + alSourcei(od->source, AL_BUFFER, 0); + od->filled = 0; +} + +const struct AudioOutputPlugin openal_output_plugin = { + "openal", + nullptr, + openal_init, + openal_finish, + nullptr, + nullptr, + openal_open, + openal_close, + openal_delay, + nullptr, + openal_play, + nullptr, + openal_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx new file mode 100644 index 000000000..a27e6b53c --- /dev/null +++ b/src/output/plugins/OpenALOutputPlugin.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_OPENAL_OUTPUT_PLUGIN_HXX +#define MPD_OPENAL_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin openal_output_plugin; + +#endif diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx new file mode 100644 index 000000000..f2618491c --- /dev/null +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -0,0 +1,775 @@ +/* + * 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 "OssOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" +#include "system/fd_util.h" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +/* We got bug reports from FreeBSD users who said that the two 24 bit + formats generate white noise on FreeBSD, but 32 bit works. This is + a workaround until we know what exactly is expected by the kernel + audio drivers. */ +#ifndef __linux__ +#undef AFMT_S24_PACKED +#undef AFMT_S24_NE +#endif + +#ifdef AFMT_S24_PACKED +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" +#endif + +struct OssOutput { + AudioOutput base; + +#ifdef AFMT_S24_PACKED + Manual<PcmExport> pcm_export; +#endif + + int fd; + const char *device; + + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + AudioFormat audio_format; + + /** + * The current OSS audio format. This is needed to reopen the + * device after cancel(). + */ + int oss_format; + + OssOutput() + :base(oss_output_plugin), + fd(-1), device(nullptr) {} + + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); + } +}; + +static constexpr Domain oss_output_domain("oss_output"); + +enum oss_stat { + OSS_STAT_NO_ERROR = 0, + OSS_STAT_NOT_CHAR_DEV = -1, + OSS_STAT_NO_PERMS = -2, + OSS_STAT_DOESN_T_EXIST = -3, + OSS_STAT_OTHER = -4, +}; + +static enum oss_stat +oss_stat_device(const char *device, int *errno_r) +{ + struct stat st; + + if (0 == stat(device, &st)) { + if (!S_ISCHR(st.st_mode)) { + return OSS_STAT_NOT_CHAR_DEV; + } + } else { + *errno_r = errno; + + switch (errno) { + case ENOENT: + case ENOTDIR: + return OSS_STAT_DOESN_T_EXIST; + case EACCES: + return OSS_STAT_NO_PERMS; + default: + return OSS_STAT_OTHER; + } + } + + return OSS_STAT_NO_ERROR; +} + +static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; + +static bool +oss_output_test_default_device(void) +{ + int fd, i; + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { + close(fd); + return true; + } + + FormatErrno(oss_output_domain, + "Error opening OSS device \"%s\"", + default_devices[i]); + } + + return false; +} + +static AudioOutput * +oss_open_default(Error &error) +{ + int err[ARRAY_SIZE(default_devices)]; + enum oss_stat ret[ARRAY_SIZE(default_devices)]; + + const config_param empty; + for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) { + ret[i] = oss_stat_device(default_devices[i], &err[i]); + if (ret[i] == OSS_STAT_NO_ERROR) { + OssOutput *od = new OssOutput(); + if (!od->Initialize(empty, error)) { + delete od; + return NULL; + } + + od->device = default_devices[i]; + return &od->base; + } + } + + for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) { + const char *dev = default_devices[i]; + switch(ret[i]) { + case OSS_STAT_NO_ERROR: + /* never reached */ + break; + case OSS_STAT_DOESN_T_EXIST: + FormatWarning(oss_output_domain, + "%s not found", dev); + break; + case OSS_STAT_NOT_CHAR_DEV: + FormatWarning(oss_output_domain, + "%s is not a character device", dev); + break; + case OSS_STAT_NO_PERMS: + FormatWarning(oss_output_domain, + "%s: permission denied", dev); + break; + case OSS_STAT_OTHER: + FormatErrno(oss_output_domain, err[i], + "Error accessing %s", dev); + } + } + + error.Set(oss_output_domain, + "error trying to open default OSS device"); + return NULL; +} + +static AudioOutput * +oss_output_init(const config_param ¶m, Error &error) +{ + const char *device = param.GetBlockValue("device"); + if (device != NULL) { + OssOutput *od = new OssOutput(); + if (!od->Initialize(param, error)) { + delete od; + return NULL; + } + + od->device = device; + return &od->base; + } + + return oss_open_default(error); +} + +static void +oss_output_finish(AudioOutput *ao) +{ + OssOutput *od = (OssOutput *)ao; + + delete od; +} + +#ifdef AFMT_S24_PACKED + +static bool +oss_output_enable(AudioOutput *ao, gcc_unused Error &error) +{ + OssOutput *od = (OssOutput *)ao; + + od->pcm_export.Construct(); + return true; +} + +static void +oss_output_disable(AudioOutput *ao) +{ + OssOutput *od = (OssOutput *)ao; + + od->pcm_export.Destruct(); +} + +#endif + +static void +oss_close(OssOutput *od) +{ + if (od->fd >= 0) + close(od->fd); + od->fd = -1; +} + +/** + * A tri-state type for oss_try_ioctl(). + */ +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates an #Error. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, Error &error) +{ + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(!error.IsDefined()); + + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; + + if (errno == EINVAL) + return UNSUPPORTED; + + error.SetErrno(msg); + return ERROR; +} + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates an #Error. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, Error &error_r) +{ + return oss_try_ioctl_r(fd, request, &value, msg, error_r); +} + +/** + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. + */ +static bool +oss_setup_channels(int fd, AudioFormat &audio_format, Error &error) +{ + const char *const msg = "Failed to set channel count"; + int channels = audio_format.channels; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format.channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + for (unsigned i = 1; i < 2; ++i) { + if (i == audio_format.channels) + /* don't try that again */ + continue; + + channels = i; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, + msg, error); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format.channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + error.Set(oss_output_domain, msg); + return false; +} + +/** + * Set up the sample rate, and attempts to find alternatives if the + * specified sample rate is not supported. + */ +static bool +oss_setup_sample_rate(int fd, AudioFormat &audio_format, + Error &error) +{ + const char *const msg = "Failed to set sample rate"; + int sample_rate = audio_format.sample_rate; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format.sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + static const int sample_rates[] = { 48000, 44100, 0 }; + for (unsigned i = 0; sample_rates[i] != 0; ++i) { + sample_rate = sample_rates[i]; + if (sample_rate == (int)audio_format.sample_rate) + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format.sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + error.Set(oss_output_domain, msg); + return false; +} + +/** + * Convert a MPD sample format to its OSS counterpart. Returns + * AFMT_QUERY if there is no direct counterpart. + */ +static int +sample_format_to_oss(SampleFormat format) +{ + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + return AFMT_QUERY; + + case SampleFormat::S8: + return AFMT_S8; + + case SampleFormat::S16: + return AFMT_S16_NE; + + case SampleFormat::S24_P32: +#ifdef AFMT_S24_NE + return AFMT_S24_NE; +#else + return AFMT_QUERY; +#endif + + case SampleFormat::S32: +#ifdef AFMT_S32_NE + return AFMT_S32_NE; +#else + return AFMT_QUERY; +#endif + } + + return AFMT_QUERY; +} + +/** + * Convert an OSS sample format to its MPD counterpart. Returns + * SampleFormat::UNDEFINED if there is no direct counterpart. + */ +static SampleFormat +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SampleFormat::S8; + + case AFMT_S16_NE: + return SampleFormat::S16; + +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: + return SampleFormat::S24_P32; +#endif + +#ifdef AFMT_S24_NE + case AFMT_S24_NE: + return SampleFormat::S24_P32; +#endif + +#ifdef AFMT_S32_NE + case AFMT_S32_NE: + return SampleFormat::S32; +#endif + + default: + return SampleFormat::UNDEFINED; + } +} + +/** + * Probe one sample format. + * + * @return the selected sample format or SampleFormat::UNDEFINED on + * error + */ +static enum oss_setup_result +oss_probe_sample_format(int fd, SampleFormat sample_format, + SampleFormat *sample_format_r, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + PcmExport &pcm_export, +#endif + Error &error) +{ + int oss_format = sample_format_to_oss(sample_format); + if (oss_format == AFMT_QUERY) + return UNSUPPORTED; + + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error); + +#ifdef AFMT_S24_PACKED + if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) { + /* if the driver doesn't support padded 24 bit, try + packed 24 bit */ + oss_format = AFMT_S24_PACKED; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error); + } +#endif + + if (result != SUCCESS) + return result; + + sample_format = sample_format_from_oss(oss_format); + if (sample_format == SampleFormat::UNDEFINED) + return UNSUPPORTED; + + *sample_format_r = sample_format; + *oss_format_r = oss_format; + +#ifdef AFMT_S24_PACKED + pcm_export.Open(sample_format, 0, false, false, + oss_format == AFMT_S24_PACKED, + oss_format == AFMT_S24_PACKED && + !IsLittleEndian()); +#endif + + return SUCCESS; +} + +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, AudioFormat &audio_format, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + PcmExport &pcm_export, +#endif + Error &error) +{ + SampleFormat mpd_format; + enum oss_setup_result result = + oss_probe_sample_format(fd, audio_format.format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error); + switch (result) { + case SUCCESS: + audio_format.format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + if (result != UNSUPPORTED) + return result == SUCCESS; + + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const SampleFormat sample_formats[] = { + SampleFormat::S24_P32, + SampleFormat::S32, + SampleFormat::S16, + SampleFormat::S8, + SampleFormat::UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format.format) + /* don't try that again */ + continue; + + result = oss_probe_sample_format(fd, mpd_format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error); + switch (result) { + case SUCCESS: + audio_format.format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + error.Set(oss_output_domain, "Failed to set sample format"); + return false; +} + +/** + * Sets up the OSS device which was opened before. + */ +static bool +oss_setup(OssOutput *od, AudioFormat &audio_format, + Error &error) +{ + return oss_setup_channels(od->fd, audio_format, error) && + oss_setup_sample_rate(od->fd, audio_format, error) && + oss_setup_sample_format(od->fd, audio_format, &od->oss_format, +#ifdef AFMT_S24_PACKED + od->pcm_export, +#endif + error); +} + +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(OssOutput *od, Error &error) +{ + assert(od->fd < 0); + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + error.FormatErrno("Error opening OSS device \"%s\"", + od->device); + return false; + } + + enum oss_setup_result result; + + const char *const msg1 = "Failed to set channel count"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, + od->audio_format.channels, msg1, error); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + error.Set(oss_output_domain, msg1); + return false; + } + + const char *const msg2 = "Failed to set sample rate"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, + od->audio_format.sample_rate, msg2, error); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + error.Set(oss_output_domain, msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + od->oss_format, + msg3, error); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + error.Set(oss_output_domain, msg3); + return false; + } + + return true; +} + +static bool +oss_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + OssOutput *od = (OssOutput *)ao; + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + error.FormatErrno("Error opening OSS device \"%s\"", + od->device); + return false; + } + + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } + + od->audio_format = audio_format; + return true; +} + +static void +oss_output_close(AudioOutput *ao) +{ + OssOutput *od = (OssOutput *)ao; + + oss_close(od); +} + +static void +oss_output_cancel(AudioOutput *ao) +{ + OssOutput *od = (OssOutput *)ao; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static size_t +oss_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + OssOutput *od = (OssOutput *)ao; + ssize_t ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && !oss_reopen(od, error)) + return 0; + +#ifdef AFMT_S24_PACKED + const auto e = od->pcm_export->Export({chunk, size}); + chunk = e.data; + size = e.size; +#endif + + while (true) { + ret = write(od->fd, chunk, size); + if (ret > 0) { +#ifdef AFMT_S24_PACKED + ret = od->pcm_export->CalcSourceSize(ret); +#endif + return ret; + } + + if (ret < 0 && errno != EINTR) { + error.FormatErrno("Write error on %s", od->device); + return 0; + } + } +} + +const struct AudioOutputPlugin oss_output_plugin = { + "oss", + oss_output_test_default_device, + oss_output_init, + oss_output_finish, +#ifdef AFMT_S24_PACKED + oss_output_enable, + oss_output_disable, +#else + nullptr, + nullptr, +#endif + oss_output_open, + oss_output_close, + nullptr, + nullptr, + oss_output_play, + nullptr, + oss_output_cancel, + nullptr, + + &oss_mixer_plugin, +}; diff --git a/src/output/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx new file mode 100644 index 000000000..f9970c8f0 --- /dev/null +++ b/src/output/plugins/OssOutputPlugin.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_OSS_OUTPUT_PLUGIN_HXX +#define MPD_OSS_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin oss_output_plugin; + +#endif diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx new file mode 100644 index 000000000..7a1f32258 --- /dev/null +++ b/src/output/plugins/PipeOutputPlugin.cxx @@ -0,0 +1,143 @@ +/* + * 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 "PipeOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <string> + +#include <stdio.h> + +struct PipeOutput { + AudioOutput base; + + std::string cmd; + FILE *fh; + + PipeOutput() + :base(pipe_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static constexpr Domain pipe_output_domain("pipe_output"); + +inline bool +PipeOutput::Configure(const config_param ¶m, Error &error) +{ + cmd = param.GetBlockValue("command", ""); + if (cmd.empty()) { + error.Set(config_domain, + "No \"command\" parameter specified"); + return false; + } + + return true; +} + +static AudioOutput * +pipe_output_init(const config_param ¶m, Error &error) +{ + PipeOutput *pd = new PipeOutput(); + + if (!pd->Initialize(param, error)) { + delete pd; + return nullptr; + } + + if (!pd->Configure(param, error)) { + delete pd; + return nullptr; + } + + return &pd->base; +} + +static void +pipe_output_finish(AudioOutput *ao) +{ + PipeOutput *pd = (PipeOutput *)ao; + + delete pd; +} + +static bool +pipe_output_open(AudioOutput *ao, + gcc_unused AudioFormat &audio_format, + Error &error) +{ + PipeOutput *pd = (PipeOutput *)ao; + + pd->fh = popen(pd->cmd.c_str(), "w"); + if (pd->fh == nullptr) { + error.FormatErrno("Error opening pipe \"%s\"", + pd->cmd.c_str()); + return false; + } + + return true; +} + +static void +pipe_output_close(AudioOutput *ao) +{ + PipeOutput *pd = (PipeOutput *)ao; + + pclose(pd->fh); +} + +static size_t +pipe_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + PipeOutput *pd = (PipeOutput *)ao; + size_t ret; + + ret = fwrite(chunk, 1, size, pd->fh); + if (ret == 0) + error.SetErrno("Write error on pipe"); + + return ret; +} + +const struct AudioOutputPlugin pipe_output_plugin = { + "pipe", + nullptr, + pipe_output_init, + pipe_output_finish, + nullptr, + nullptr, + pipe_output_open, + pipe_output_close, + nullptr, + nullptr, + pipe_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx new file mode 100644 index 000000000..bdaf2ecfd --- /dev/null +++ b/src/output/plugins/PipeOutputPlugin.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_PIPE_OUTPUT_PLUGIN_HXX +#define MPD_PIPE_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin pipe_output_plugin; + +#endif diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx new file mode 100644 index 000000000..ec3725a71 --- /dev/null +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -0,0 +1,885 @@ +/* + * 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 "PulseOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" +#include "mixer/plugins/PulseMixerPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/introspect.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> +#include <pulse/version.h> + +#include <assert.h> +#include <stddef.h> +#include <stdlib.h> + +#define MPD_PULSE_NAME "Music Player Daemon" + +struct PulseOutput { + AudioOutput base; + + const char *name; + const char *server; + const char *sink; + + PulseMixer *mixer; + + struct pa_threaded_mainloop *mainloop; + struct pa_context *context; + struct pa_stream *stream; + + size_t writable; + + PulseOutput() + :base(pulse_output_plugin) {} +}; + +static constexpr Domain pulse_output_domain("pulse_output"); + +static void +SetError(Error &error, pa_context *context, const char *msg) +{ + const int e = pa_context_errno(context); + error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e)); +} + +void +pulse_output_lock(PulseOutput &po) +{ + pa_threaded_mainloop_lock(po.mainloop); +} + +void +pulse_output_unlock(PulseOutput &po) +{ + pa_threaded_mainloop_unlock(po.mainloop); +} + +void +pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm) +{ + assert(po.mixer == nullptr); + + po.mixer = ± + + if (po.mainloop == nullptr) + return; + + pa_threaded_mainloop_lock(po.mainloop); + + if (po.context != nullptr && + pa_context_get_state(po.context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(pm, po.context); + + if (po.stream != nullptr && + pa_stream_get_state(po.stream) == PA_STREAM_READY) + pulse_mixer_on_change(pm, po.context, po.stream); + } + + pa_threaded_mainloop_unlock(po.mainloop); +} + +void +pulse_output_clear_mixer(PulseOutput &po, gcc_unused PulseMixer &pm) +{ + assert(po.mixer == &pm); + + po.mixer = nullptr; +} + +bool +pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume, + Error &error) +{ + pa_operation *o; + + if (po.context == nullptr || po.stream == nullptr || + pa_stream_get_state(po.stream) != PA_STREAM_READY) { + error.Set(pulse_output_domain, "disconnected"); + return false; + } + + o = pa_context_set_sink_input_volume(po.context, + pa_stream_get_index(po.stream), + volume, nullptr, nullptr); + if (o == nullptr) { + SetError(error, po.context, + "failed to set PulseAudio volume"); + return false; + } + + pa_operation_unref(o); + return true; +} + +/** + * \brief waits for a pulseaudio operation to finish, frees it and + * unlocks the mainloop + * \param operation the operation to wait for + * \return true if operation has finished normally (DONE state), + * false otherwise + */ +static bool +pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, + struct pa_operation *operation) +{ + pa_operation_state_t state; + + assert(mainloop != nullptr); + assert(operation != nullptr); + + state = pa_operation_get_state(operation); + while (state == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(mainloop); + state = pa_operation_get_state(operation); + } + + pa_operation_unref(operation); + + return state == PA_OPERATION_DONE; +} + +/** + * Callback function for stream operation. It just sends a signal to + * the caller thread, to wake pulse_wait_for_operation() up. + */ +static void +pulse_output_stream_success_cb(gcc_unused pa_stream *s, + gcc_unused int success, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + if (po->mixer != nullptr) + pulse_mixer_on_connect(*po->mixer, context); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + if (po->mixer != nullptr) + pulse_mixer_on_disconnect(*po->mixer); + + /* the caller thread might be waiting for these + states */ + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +pulse_output_subscribe_cb(pa_context *context, + pa_subscription_event_type_t t, + uint32_t idx, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + pa_subscription_event_type_t facility = + pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + pa_subscription_event_type_t type = + pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + + if (po->mixer != nullptr && + facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && + po->stream != nullptr && + pa_stream_get_state(po->stream) == PA_STREAM_READY && + idx == pa_stream_get_index(po->stream) && + (type == PA_SUBSCRIPTION_EVENT_NEW || + type == PA_SUBSCRIPTION_EVENT_CHANGE)) + pulse_mixer_on_change(*po->mixer, context, po->stream); +} + +/** + * Attempt to connect asynchronously to the PulseAudio server. + * + * @return true on success, false on error + */ +static bool +pulse_output_connect(PulseOutput *po, Error &error) +{ + assert(po != nullptr); + assert(po->context != nullptr); + + if (pa_context_connect(po->context, po->server, + (pa_context_flags_t)0, nullptr) < 0) { + SetError(error, po->context, + "pa_context_connect() has failed"); + return false; + } + + return true; +} + +/** + * Frees and clears the stream. + */ +static void +pulse_output_delete_stream(PulseOutput *po) +{ + assert(po != nullptr); + assert(po->stream != nullptr); + + pa_stream_set_suspended_callback(po->stream, nullptr, nullptr); + + pa_stream_set_state_callback(po->stream, nullptr, nullptr); + pa_stream_set_write_callback(po->stream, nullptr, nullptr); + + pa_stream_disconnect(po->stream); + pa_stream_unref(po->stream); + po->stream = nullptr; +} + +/** + * Frees and clears the context. + * + * Caller must lock the main loop. + */ +static void +pulse_output_delete_context(PulseOutput *po) +{ + assert(po != nullptr); + assert(po->context != nullptr); + + pa_context_set_state_callback(po->context, nullptr, nullptr); + pa_context_set_subscribe_callback(po->context, nullptr, nullptr); + + pa_context_disconnect(po->context); + pa_context_unref(po->context); + po->context = nullptr; +} + +/** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_context(PulseOutput *po, Error &error) +{ + assert(po != nullptr); + assert(po->mainloop != nullptr); + + po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), + MPD_PULSE_NAME); + if (po->context == nullptr) { + error.Set(pulse_output_domain, "pa_context_new() has failed"); + return false; + } + + pa_context_set_state_callback(po->context, + pulse_output_context_state_cb, po); + pa_context_set_subscribe_callback(po->context, + pulse_output_subscribe_cb, po); + + if (!pulse_output_connect(po, error)) { + pulse_output_delete_context(po); + return false; + } + + return true; +} + +static AudioOutput * +pulse_output_init(const config_param ¶m, Error &error) +{ + PulseOutput *po; + + setenv("PULSE_PROP_media.role", "music", true); + setenv("PULSE_PROP_application.icon_name", "mpd", true); + + po = new PulseOutput(); + if (!po->base.Configure(param, error)) { + delete po; + return nullptr; + } + + po->name = param.GetBlockValue("name", "mpd_pulse"); + po->server = param.GetBlockValue("server"); + po->sink = param.GetBlockValue("sink"); + + po->mixer = nullptr; + po->mainloop = nullptr; + po->context = nullptr; + po->stream = nullptr; + + return &po->base; +} + +static void +pulse_output_finish(AudioOutput *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + + delete po; +} + +static bool +pulse_output_enable(AudioOutput *ao, Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop == nullptr); + assert(po->context == nullptr); + + /* create the libpulse mainloop and start the thread */ + + po->mainloop = pa_threaded_mainloop_new(); + if (po->mainloop == nullptr) { + error.Set(pulse_output_domain, + "pa_threaded_mainloop_new() has failed"); + return false; + } + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_threaded_mainloop_start(po->mainloop) < 0) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = nullptr; + + error.Set(pulse_output_domain, + "pa_threaded_mainloop_start() has failed"); + return false; + } + + /* create the libpulse context and connect it */ + + if (!pulse_output_setup_context(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_stop(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = nullptr; + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_disable(AudioOutput *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop != nullptr); + + pa_threaded_mainloop_stop(po->mainloop); + if (po->context != nullptr) + pulse_output_delete_context(po); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = nullptr; +} + +/** + * Check if the context is (already) connected, and waits if not. If + * the context has been disconnected, retry to connect. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_connection(PulseOutput *po, Error &error) +{ + assert(po->mainloop != nullptr); + + pa_context_state_t state; + + if (po->context == nullptr && !pulse_output_setup_context(po, error)) + return false; + + while (true) { + state = pa_context_get_state(po->context); + switch (state) { + case PA_CONTEXT_READY: + /* nothing to do */ + return true; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* failure */ + SetError(error, po->context, "failed to connect"); + pulse_output_delete_context(po); + return false; + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + /* wait some more */ + pa_threaded_mainloop_wait(po->mainloop); + break; + } + } +} + +static void +pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + assert(stream == po->stream || po->stream == nullptr); + assert(po->mainloop != nullptr); + + /* wake up the main loop to break out of the loop in + pulse_output_play() */ + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + assert(stream == po->stream || po->stream == nullptr); + assert(po->mainloop != nullptr); + assert(po->context != nullptr); + + switch (pa_stream_get_state(stream)) { + case PA_STREAM_READY: + if (po->mixer != nullptr) + pulse_mixer_on_change(*po->mixer, po->context, stream); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + if (po->mixer != nullptr) + pulse_mixer_on_disconnect(*po->mixer); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void +pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes, + void *userdata) +{ + PulseOutput *po = (PulseOutput *)userdata; + + assert(po->mainloop != nullptr); + + po->writable = nbytes; + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +/** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, + Error &error) +{ + assert(po != nullptr); + assert(po->context != nullptr); + + po->stream = pa_stream_new(po->context, po->name, ss, nullptr); + if (po->stream == nullptr) { + SetError(error, po->context, "pa_stream_new() has failed"); + return false; + } + + pa_stream_set_suspended_callback(po->stream, + pulse_output_stream_suspended_cb, po); + + pa_stream_set_state_callback(po->stream, + pulse_output_stream_state_cb, po); + pa_stream_set_write_callback(po->stream, + pulse_output_stream_write_cb, po); + + return true; +} + +static bool +pulse_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + pa_sample_spec ss; + + assert(po->mainloop != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context != nullptr) { + switch (pa_context_get_state(po->context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* the connection was closed meanwhile; delete + it, and pulse_output_wait_connection() will + reopen it */ + pulse_output_delete_context(po); + break; + + case PA_CONTEXT_READY: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } + } + + if (!pulse_output_wait_connection(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + /* MPD doesn't support the other pulseaudio sample formats, so + we just force MPD to send us everything as 16 bit */ + audio_format.format = SampleFormat::S16; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audio_format.sample_rate; + ss.channels = audio_format.channels; + + /* create a stream .. */ + + if (!pulse_output_setup_stream(po, &ss, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + /* .. and connect it (asynchronously) */ + + if (pa_stream_connect_playback(po->stream, po->sink, + nullptr, pa_stream_flags_t(0), + nullptr, nullptr) < 0) { + pulse_output_delete_stream(po); + + SetError(error, po->context, + "pa_stream_connect_playback() has failed"); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_close(AudioOutput *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + pa_operation *o; + + assert(po->mainloop != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { + o = pa_stream_drain(po->stream, + pulse_output_stream_success_cb, po); + if (o == nullptr) { + FormatWarning(pulse_output_domain, + "pa_stream_drain() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + } else + pulse_wait_for_operation(po->mainloop, o); + } + + pulse_output_delete_stream(po); + + if (po->context != nullptr && + pa_context_get_state(po->context) != PA_CONTEXT_READY) + pulse_output_delete_context(po); + + pa_threaded_mainloop_unlock(po->mainloop); +} + +/** + * Check if the stream is (already) connected, and waits if not. The + * mainloop must be locked before calling this function. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_stream(PulseOutput *po, Error &error) +{ + while (true) { + switch (pa_stream_get_state(po->stream)) { + case PA_STREAM_READY: + return true; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + SetError(error, po->context, + "failed to connect the stream"); + return false; + + case PA_STREAM_CREATING: + pa_threaded_mainloop_wait(po->mainloop); + break; + } + } +} + +/** + * Sets cork mode on the stream. + */ +static bool +pulse_output_stream_pause(PulseOutput *po, bool pause, + Error &error) +{ + pa_operation *o; + + assert(po->mainloop != nullptr); + assert(po->context != nullptr); + assert(po->stream != nullptr); + + o = pa_stream_cork(po->stream, pause, + pulse_output_stream_success_cb, po); + if (o == nullptr) { + SetError(error, po->context, "pa_stream_cork() has failed"); + return false; + } + + if (!pulse_wait_for_operation(po->mainloop, o)) { + SetError(error, po->context, "pa_stream_cork() has failed"); + return false; + } + + return true; +} + +static unsigned +pulse_output_delay(AudioOutput *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + unsigned result = 0; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->base.pause && pa_stream_is_corked(po->stream) && + pa_stream_get_state(po->stream) == PA_STREAM_READY) + /* idle while paused */ + result = 1000; + + pa_threaded_mainloop_unlock(po->mainloop); + + return result; +} + +static size_t +pulse_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already) connected */ + + if (!pulse_output_wait_stream(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + assert(po->context != nullptr); + + /* unpause if previously paused */ + + if (pa_stream_is_corked(po->stream) && + !pulse_output_stream_pause(po, false, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + /* wait until the server allows us to write */ + + while (po->writable == 0) { + if (pa_stream_is_suspended(po->stream)) { + pa_threaded_mainloop_unlock(po->mainloop); + error.Set(pulse_output_domain, "suspended"); + return 0; + } + + pa_threaded_mainloop_wait(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(po->mainloop); + error.Set(pulse_output_domain, "disconnected"); + return 0; + } + } + + /* now write */ + + if (size > po->writable) + /* don't send more than possible */ + size = po->writable; + + po->writable -= size; + + int result = pa_stream_write(po->stream, chunk, size, nullptr, + 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(po->mainloop); + if (result < 0) { + SetError(error, po->context, "pa_stream_write() failed"); + return 0; + } + + return size; +} + +static void +pulse_output_cancel(AudioOutput *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + pa_operation *o; + + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + /* no need to flush when the stream isn't connected + yet */ + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + assert(po->context != nullptr); + + o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + if (o == nullptr) { + FormatWarning(pulse_output_domain, + "pa_stream_flush() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + pulse_wait_for_operation(po->mainloop, o); + pa_threaded_mainloop_unlock(po->mainloop); +} + +static bool +pulse_output_pause(AudioOutput *ao) +{ + PulseOutput *po = (PulseOutput *)ao; + + assert(po->mainloop != nullptr); + assert(po->stream != nullptr); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already/still) connected */ + + Error error; + if (!pulse_output_wait_stream(po, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + LogError(error); + return false; + } + + assert(po->context != nullptr); + + /* cork the stream */ + + if (!pa_stream_is_corked(po->stream) && + !pulse_output_stream_pause(po, true, error)) { + pa_threaded_mainloop_unlock(po->mainloop); + LogError(error); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static bool +pulse_output_test_default_device(void) +{ + bool success; + + const config_param empty; + PulseOutput *po = (PulseOutput *) + pulse_output_init(empty, IgnoreError()); + if (po == nullptr) + return false; + + success = pulse_output_wait_connection(po, IgnoreError()); + pulse_output_finish(&po->base); + + return success; +} + +const struct AudioOutputPlugin pulse_output_plugin = { + "pulse", + pulse_output_test_default_device, + pulse_output_init, + pulse_output_finish, + pulse_output_enable, + pulse_output_disable, + pulse_output_open, + pulse_output_close, + pulse_output_delay, + nullptr, + pulse_output_play, + nullptr, + pulse_output_cancel, + pulse_output_pause, + + &pulse_mixer_plugin, +}; diff --git a/src/output/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx new file mode 100644 index 000000000..9219780a5 --- /dev/null +++ b/src/output/plugins/PulseOutputPlugin.hxx @@ -0,0 +1,46 @@ +/* + * 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_PULSE_OUTPUT_PLUGIN_HXX +#define MPD_PULSE_OUTPUT_PLUGIN_HXX + +struct PulseOutput; +class PulseMixer; +struct pa_cvolume; +class Error; + +extern const struct AudioOutputPlugin pulse_output_plugin; + +void +pulse_output_lock(PulseOutput &po); + +void +pulse_output_unlock(PulseOutput &po); + +void +pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm); + +void +pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm); + +bool +pulse_output_set_volume(PulseOutput &po, + const pa_cvolume *volume, Error &error); + +#endif diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx new file mode 100644 index 000000000..87e23f55a --- /dev/null +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -0,0 +1,258 @@ +/* + * 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 "RecorderOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/fd_util.h" +#include "open.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +struct RecorderOutput { + AudioOutput base; + + /** + * The configured encoder plugin. + */ + Encoder *encoder; + + /** + * The destination file name. + */ + const char *path; + + /** + * The destination file descriptor. + */ + int fd; + + /** + * The buffer for encoder_read(). + */ + char buffer[32768]; + + RecorderOutput() + :base(recorder_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); + } + + bool Configure(const config_param ¶m, Error &error); + + bool WriteToFile(const void *data, size_t length, Error &error); + + /** + * Writes pending data from the encoder to the output file. + */ + bool EncoderToFile(Error &error); +}; + +static constexpr Domain recorder_output_domain("recorder_output"); + +inline bool +RecorderOutput::Configure(const config_param ¶m, Error &error) +{ + /* read configuration */ + + const char *encoder_name = + param.GetBlockValue("encoder", "vorbis"); + const auto encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == nullptr) { + error.Format(config_domain, + "No such encoder: %s", encoder_name); + return false; + } + + path = param.GetBlockValue("path"); + if (path == nullptr) { + error.Set(config_domain, "'path' not configured"); + return false; + } + + /* initialize encoder */ + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + return true; +} + +static AudioOutput * +recorder_output_init(const config_param ¶m, Error &error) +{ + RecorderOutput *recorder = new RecorderOutput(); + + if (!recorder->Initialize(param, error)) { + delete recorder; + return nullptr; + } + + if (!recorder->Configure(param, error)) { + delete recorder; + return nullptr; + } + + return &recorder->base; +} + +static void +recorder_output_finish(AudioOutput *ao) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + encoder_finish(recorder->encoder); + delete recorder; +} + +inline bool +RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error) +{ + assert(length > 0); + + const uint8_t *data = (const uint8_t *)_data, *end = data + length; + + while (true) { + ssize_t nbytes = write(fd, data, end - data); + if (nbytes > 0) { + data += nbytes; + if (data == end) + return true; + } else if (nbytes == 0) { + /* shouldn't happen for files */ + error.Set(recorder_output_domain, + "write() returned 0"); + return false; + } else if (errno != EINTR) { + error.FormatErrno("Failed to write to '%s'", path); + return false; + } + } +} + +inline bool +RecorderOutput::EncoderToFile(Error &error) +{ + assert(fd >= 0); + + while (true) { + /* read from the encoder */ + + size_t size = encoder_read(encoder, buffer, sizeof(buffer)); + if (size == 0) + return true; + + /* write everything into the file */ + + if (!WriteToFile(buffer, size, error)) + return false; + } +} + +static bool +recorder_output_open(AudioOutput *ao, + AudioFormat &audio_format, + Error &error) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + /* create the output file */ + + recorder->fd = open_cloexec(recorder->path, + O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, + 0666); + if (recorder->fd < 0) { + error.FormatErrno("Failed to create '%s'", recorder->path); + return false; + } + + /* open the encoder */ + + if (!encoder_open(recorder->encoder, audio_format, error)) { + close(recorder->fd); + unlink(recorder->path); + return false; + } + + if (!recorder->EncoderToFile(error)) { + encoder_close(recorder->encoder); + close(recorder->fd); + unlink(recorder->path); + return false; + } + + return true; +} + +static void +recorder_output_close(AudioOutput *ao) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + /* flush the encoder and write the rest to the file */ + + if (encoder_end(recorder->encoder, IgnoreError())) + recorder->EncoderToFile(IgnoreError()); + + /* now really close everything */ + + encoder_close(recorder->encoder); + + close(recorder->fd); +} + +static size_t +recorder_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + RecorderOutput *recorder = (RecorderOutput *)ao; + + return encoder_write(recorder->encoder, chunk, size, error) && + recorder->EncoderToFile(error) + ? size : 0; +} + +const struct AudioOutputPlugin recorder_output_plugin = { + "recorder", + nullptr, + recorder_output_init, + recorder_output_finish, + nullptr, + nullptr, + recorder_output_open, + recorder_output_close, + nullptr, + nullptr, + recorder_output_play, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx new file mode 100644 index 000000000..ea1044e0f --- /dev/null +++ b/src/output/plugins/RecorderOutputPlugin.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_RECORDER_OUTPUT_PLUGIN_HXX +#define MPD_RECORDER_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin recorder_output_plugin; + +#endif diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx new file mode 100644 index 000000000..ae6bdf1b1 --- /dev/null +++ b/src/output/plugins/RoarOutputPlugin.cxx @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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 "RoarOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "mixer/MixerList.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <string> + +/* libroar/services.h declares roar_service_stream::new - work around + this C++ problem */ +#define new _new +#include <roaraudio.h> +#undef new + +class RoarOutput { + AudioOutput base; + + std::string host, name; + + roar_vs_t * vss; + int err; + int role; + struct roar_connection con; + struct roar_audio_info info; + mutable Mutex mutex; + volatile bool alive; + +public: + RoarOutput() + :base(roar_output_plugin), + err(ROAR_ERROR_NONE) {} + + operator AudioOutput *() { + return &base; + } + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + void Configure(const config_param ¶m); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + void SendTag(const Tag &tag); + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); + + int GetVolume() const; + bool SetVolume(unsigned volume); +}; + +static constexpr Domain roar_output_domain("roar_output"); + +inline int +RoarOutput::GetVolume() const +{ + const ScopeLock protect(mutex); + + if (vss == nullptr || !alive) + return -1; + + float l, r; + int error; + if (roar_vs_volume_get(vss, &l, &r, &error) < 0) + return -1; + + return (l + r) * 50; +} + +int +roar_output_get_volume(RoarOutput &roar) +{ + return roar.GetVolume(); +} + +bool +RoarOutput::SetVolume(unsigned volume) +{ + assert(volume <= 100); + + const ScopeLock protect(mutex); + if (vss == nullptr || !alive) + return false; + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(vss, level, &error); + return true; +} + +bool +roar_output_set_volume(RoarOutput &roar, unsigned volume) +{ + return roar.SetVolume(volume); +} + +inline void +RoarOutput::Configure(const config_param ¶m) +{ + host = param.GetBlockValue("server", ""); + name = param.GetBlockValue("name", "MPD"); + + const char *_role = param.GetBlockValue("role", "music"); + role = _role != nullptr + ? roar_str2role(_role) + : ROAR_ROLE_MUSIC; +} + +static AudioOutput * +roar_init(const config_param ¶m, Error &error) +{ + RoarOutput *self = new RoarOutput(); + + if (!self->Initialize(param, error)) { + delete self; + return nullptr; + } + + self->Configure(param); + return *self; +} + +static void +roar_finish(AudioOutput *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + delete self; +} + +static void +roar_use_audio_format(struct roar_audio_info *info, + AudioFormat &audio_format) +{ + info->rate = audio_format.sample_rate; + info->channels = audio_format.channels; + info->codec = ROAR_CODEC_PCM_S; + + switch (audio_format.format) { + case SampleFormat::UNDEFINED: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + info->bits = 16; + audio_format.format = SampleFormat::S16; + break; + + case SampleFormat::S8: + info->bits = 8; + break; + + case SampleFormat::S16: + info->bits = 16; + break; + + case SampleFormat::S24_P32: + info->bits = 32; + audio_format.format = SampleFormat::S32; + break; + + case SampleFormat::S32: + info->bits = 32; + break; + } +} + +inline bool +RoarOutput::Open(AudioFormat &audio_format, Error &error) +{ + const ScopeLock protect(mutex); + + if (roar_simple_connect(&con, + host.empty() ? nullptr : host.c_str(), + name.c_str()) < 0) { + error.Set(roar_output_domain, + "Failed to connect to Roar server"); + return false; + } + + vss = roar_vs_new_from_con(&con, &err); + + if (vss == nullptr || err != ROAR_ERROR_NONE) { + error.Set(roar_output_domain, "Failed to connect to server"); + return false; + } + + roar_use_audio_format(&info, audio_format); + + if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) { + error.Set(roar_output_domain, "Failed to start stream"); + return false; + } + + roar_vs_role(vss, role, &err); + alive = true; + return true; +} + +static bool +roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) +{ + RoarOutput *self = (RoarOutput *)ao; + + return self->Open(audio_format, error); +} + +inline void +RoarOutput::Close() +{ + const ScopeLock protect(mutex); + + alive = false; + + if (vss != nullptr) + roar_vs_close(vss, ROAR_VS_TRUE, &err); + vss = nullptr; + roar_disconnect(&con); +} + +static void +roar_close(AudioOutput *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + self->Close(); +} + +inline void +RoarOutput::Cancel() +{ + const ScopeLock protect(mutex); + + if (vss == nullptr) + return; + + roar_vs_t *_vss = vss; + vss = nullptr; + roar_vs_close(_vss, ROAR_VS_TRUE, &err); + alive = false; + + _vss = roar_vs_new_from_con(&con, &err); + if (_vss == nullptr) + return; + + if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) { + roar_vs_close(_vss, ROAR_VS_TRUE, &err); + LogError(roar_output_domain, "Failed to start stream"); + return; + } + + roar_vs_role(_vss, role, &err); + vss = _vss; + alive = true; +} + +static void +roar_cancel(AudioOutput *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + self->Cancel(); +} + +inline size_t +RoarOutput::Play(const void *chunk, size_t size, Error &error) +{ + if (vss == nullptr) { + error.Set(roar_output_domain, "Connection is invalid"); + return 0; + } + + ssize_t nbytes = roar_vs_write(vss, chunk, size, &err); + if (nbytes <= 0) { + error.Set(roar_output_domain, "Failed to play data"); + return 0; + } + + return nbytes; +} + +static size_t +roar_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + RoarOutput *self = (RoarOutput *)ao; + return self->Play(chunk, size, error); +} + +static const char* +roar_tag_convert(TagType type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return nullptr; + } +} + +inline void +RoarOutput::SendTag(const Tag &tag) +{ + if (vss == nullptr) + return; + + const ScopeLock protect(mutex); + + size_t cnt = 1; + struct roar_keyval vals[32]; + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60); + + vals[0].key = const_cast<char *>("LENGTH"); + vals[0].value = timebuf; + + for (const auto &item : tag) { + if (cnt >= 32) + break; + + bool is_uuid = false; + const char *key = roar_tag_convert(item.type, + &is_uuid); + if (key != nullptr) { + vals[cnt].key = const_cast<char *>(key); + + if (is_uuid) { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + item.value); + vals[cnt].value = uuid_buf[cnt]; + } else { + vals[cnt].value = const_cast<char *>(item.value); + } + + cnt++; + } + } + + roar_vs_meta(vss, vals, cnt, &(err)); +} + +static void +roar_send_tag(AudioOutput *ao, const Tag *meta) +{ + RoarOutput *self = (RoarOutput *)ao; + self->SendTag(*meta); +} + +const struct AudioOutputPlugin roar_output_plugin = { + "roar", + nullptr, + roar_init, + roar_finish, + nullptr, + nullptr, + roar_open, + roar_close, + nullptr, + roar_send_tag, + roar_play, + nullptr, + roar_cancel, + nullptr, + &roar_mixer_plugin, +}; diff --git a/src/output/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx new file mode 100644 index 000000000..5f5a9246e --- /dev/null +++ b/src/output/plugins/RoarOutputPlugin.hxx @@ -0,0 +1,33 @@ +/* + * 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_ROAR_OUTPUT_PLUGIN_H +#define MPD_ROAR_OUTPUT_PLUGIN_H + +class RoarOutput; + +extern const struct AudioOutputPlugin roar_output_plugin; + +int +roar_output_get_volume(RoarOutput &roar); + +bool +roar_output_set_volume(RoarOutput &roar, unsigned volume); + +#endif diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx new file mode 100644 index 000000000..0341e1cf7 --- /dev/null +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -0,0 +1,537 @@ +/* + * 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 "ShoutOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <shout/shout.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2; + +struct ShoutOutput final { + AudioOutput base; + + shout_t *shout_conn; + shout_metadata_t *shout_meta; + + Encoder *encoder; + + float quality; + int bitrate; + + int timeout; + + uint8_t buffer[32768]; + + ShoutOutput() + :base(shout_output_plugin), + shout_conn(shout_new()), + shout_meta(shout_metadata_new()), + quality(-2.0), + bitrate(-1), + timeout(DEFAULT_CONN_TIMEOUT) {} + + ~ShoutOutput() { + if (shout_meta != nullptr) + shout_metadata_free(shout_meta); + if (shout_conn != nullptr) + shout_free(shout_conn); + } + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Configure(const config_param ¶m, Error &error); +}; + +static int shout_init_count; + +static constexpr Domain shout_output_domain("shout_output"); + +static const EncoderPlugin * +shout_encoder_plugin_get(const char *name) +{ + if (strcmp(name, "ogg") == 0) + name = "vorbis"; + else if (strcmp(name, "mp3") == 0) + name = "lame"; + + return encoder_plugin_get(name); +} + +gcc_pure +static const char * +require_block_string(const config_param ¶m, const char *name) +{ + const char *value = param.GetBlockValue(name); + if (value == nullptr) + FormatFatalError("no \"%s\" defined for shout device defined " + "at line %u\n", name, param.line); + + return value; +} + +inline bool +ShoutOutput::Configure(const config_param ¶m, Error &error) +{ + + const AudioFormat audio_format = base.config_audio_format; + if (!audio_format.IsFullyDefined()) { + error.Set(config_domain, + "Need full audio format specification"); + return nullptr; + } + + const char *host = require_block_string(param, "host"); + const char *mount = require_block_string(param, "mount"); + unsigned port = param.GetBlockValue("port", 0u); + if (port == 0) { + error.Set(config_domain, "shout port must be configured"); + return false; + } + + const char *passwd = require_block_string(param, "password"); + const char *name = require_block_string(param, "name"); + + bool is_public = param.GetBlockValue("public", false); + + const char *user = param.GetBlockValue("user", "source"); + + const char *value = param.GetBlockValue("quality"); + if (value != nullptr) { + char *test; + quality = strtod(value, &test); + + if (*test != '\0' || quality < -1.0 || quality > 10.0) { + error.Format(config_domain, + "shout quality \"%s\" is not a number in the " + "range -1 to 10", + value); + return false; + } + + if (param.GetBlockValue("bitrate") != nullptr) { + error.Set(config_domain, + "quality and bitrate are " + "both defined"); + return false; + } + } else { + value = param.GetBlockValue("bitrate"); + if (value == nullptr) { + error.Set(config_domain, + "neither bitrate nor quality defined"); + return false; + } + + char *test; + bitrate = strtol(value, &test, 10); + + if (*test != '\0' || bitrate <= 0) { + error.Set(config_domain, + "bitrate must be a positive integer"); + return false; + } + } + + const char *encoding = param.GetBlockValue("encoding", "ogg"); + const auto encoder_plugin = shout_encoder_plugin_get(encoding); + if (encoder_plugin == nullptr) { + error.Format(config_domain, + "couldn't find shout encoder plugin \"%s\"", + encoding); + return false; + } + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + unsigned shout_format; + if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) + shout_format = SHOUT_FORMAT_MP3; + else + shout_format = SHOUT_FORMAT_OGG; + + unsigned protocol; + value = param.GetBlockValue("protocol"); + if (value != nullptr) { + if (0 == strcmp(value, "shoutcast") && + 0 != strcmp(encoding, "mp3")) { + error.Format(config_domain, + "you cannot stream \"%s\" to shoutcast, use mp3", + encoding); + return false; + } else if (0 == strcmp(value, "shoutcast")) + protocol = SHOUT_PROTOCOL_ICY; + else if (0 == strcmp(value, "icecast1")) + protocol = SHOUT_PROTOCOL_XAUDIOCAST; + else if (0 == strcmp(value, "icecast2")) + protocol = SHOUT_PROTOCOL_HTTP; + else { + error.Format(config_domain, + "shout protocol \"%s\" is not \"shoutcast\" or " + "\"icecast1\"or \"icecast2\"", + value); + return false; + } + } else { + protocol = SHOUT_PROTOCOL_HTTP; + } + + if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS || + shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS || + shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS || + shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS || + shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS || + shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS || + shout_set_format(shout_conn, shout_format) + != SHOUTERR_SUCCESS || + shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS || + shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + /* optional paramters */ + timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT); + + value = param.GetBlockValue("genre"); + if (value != nullptr && shout_set_genre(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + value = param.GetBlockValue("description"); + if (value != nullptr && shout_set_description(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + value = param.GetBlockValue("url"); + if (value != nullptr && shout_set_url(shout_conn, value)) { + error.Set(shout_output_domain, shout_get_error(shout_conn)); + return false; + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%u", audio_format.channels); + shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate); + + shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp); + + if (quality >= -1.0) { + snprintf(temp, sizeof(temp), "%2.2f", quality); + shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY, + temp); + } else { + snprintf(temp, sizeof(temp), "%d", bitrate); + shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE, + temp); + } + } + + return true; +} + +static AudioOutput * +my_shout_init_driver(const config_param ¶m, Error &error) +{ + ShoutOutput *sd = new ShoutOutput(); + if (!sd->Initialize(param, error)) { + delete sd; + return nullptr; + } + + if (!sd->Configure(param, error)) { + delete sd; + return nullptr; + } + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + return &sd->base; +} + +static bool +handle_shout_error(ShoutOutput *sd, int err, Error &error) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + error.Format(shout_output_domain, err, + "Lost shout connection to %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + + default: + error.Format(shout_output_domain, err, + "connection to %s:%i error: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } + + return true; +} + +static bool +write_page(ShoutOutput *sd, Error &error) +{ + assert(sd->encoder != nullptr); + + while (true) { + size_t nbytes = encoder_read(sd->encoder, + sd->buffer, sizeof(sd->buffer)); + if (nbytes == 0) + return true; + + int err = shout_send(sd->shout_conn, sd->buffer, nbytes); + if (!handle_shout_error(sd, err, error)) + return false; + } + + return true; +} + +static void close_shout_conn(ShoutOutput * sd) +{ + if (sd->encoder != nullptr) { + if (encoder_end(sd->encoder, IgnoreError())) + write_page(sd, IgnoreError()); + + encoder_close(sd->encoder); + } + + if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && + shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { + FormatWarning(shout_output_domain, + "problem closing connection to shout server: %s", + shout_get_error(sd->shout_conn)); + } +} + +static void +my_shout_finish_driver(AudioOutput *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + encoder_finish(sd->encoder); + + delete sd; + + shout_init_count--; + + if (shout_init_count == 0) + shout_shutdown(); +} + +static void +my_shout_drop_buffered_audio(AudioOutput *ao) +{ + gcc_unused + ShoutOutput *sd = (ShoutOutput *)ao; + + /* needs to be implemented for shout */ +} + +static void +my_shout_close_device(AudioOutput *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + close_shout_conn(sd); +} + +static bool +shout_connect(ShoutOutput *sd, Error &error) +{ + switch (shout_open(sd->shout_conn)) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + return true; + + default: + error.Format(shout_output_domain, + "problem opening connection to shout server %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } +} + +static bool +my_shout_open_device(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + if (!shout_connect(sd, error)) + return false; + + if (!encoder_open(sd->encoder, audio_format, error)) { + shout_close(sd->shout_conn); + return false; + } + + if (!write_page(sd, error)) { + encoder_close(sd->encoder); + shout_close(sd->shout_conn); + return false; + } + + return true; +} + +static unsigned +my_shout_delay(AudioOutput *ao) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + int delay = shout_delay(sd->shout_conn); + if (delay < 0) + delay = 0; + + return delay; +} + +static size_t +my_shout_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + return encoder_write(sd->encoder, chunk, size, error) && + write_page(sd, error) + ? size + : 0; +} + +static bool +my_shout_pause(AudioOutput *ao) +{ + static char silence[1020]; + + return my_shout_play(ao, silence, sizeof(silence), IgnoreError()); +} + +static void +shout_tag_to_metadata(const Tag *tag, char *dest, size_t size) +{ + char artist[size]; + char title[size]; + + artist[0] = 0; + title[0] = 0; + + for (const auto &item : *tag) { + switch (item.type) { + case TAG_ARTIST: + strncpy(artist, item.value, size); + break; + case TAG_TITLE: + strncpy(title, item.value, size); + break; + + default: + break; + } + } + + snprintf(dest, size, "%s - %s", artist, title); +} + +static void my_shout_set_tag(AudioOutput *ao, + const Tag *tag) +{ + ShoutOutput *sd = (ShoutOutput *)ao; + + if (sd->encoder->plugin.tag != nullptr) { + /* encoder plugin supports stream tags */ + + Error error; + if (!encoder_pre_tag(sd->encoder, error) || + !write_page(sd, error) || + !encoder_tag(sd->encoder, tag, error)) { + LogError(error); + return; + } + } else { + /* no stream tag support: fall back to icy-metadata */ + char song[1024]; + shout_tag_to_metadata(tag, song, sizeof(song)); + + shout_metadata_add(sd->shout_meta, "song", song); + if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, + sd->shout_meta)) { + LogWarning(shout_output_domain, + "error setting shout metadata"); + } + } + + write_page(sd, IgnoreError()); +} + +const struct AudioOutputPlugin shout_output_plugin = { + "shout", + nullptr, + my_shout_init_driver, + my_shout_finish_driver, + nullptr, + nullptr, + my_shout_open_device, + my_shout_close_device, + my_shout_delay, + my_shout_set_tag, + my_shout_play, + nullptr, + my_shout_drop_buffered_audio, + my_shout_pause, + nullptr, +}; diff --git a/src/output/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx new file mode 100644 index 000000000..9f706fc3b --- /dev/null +++ b/src/output/plugins/ShoutOutputPlugin.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_SHOUT_OUTPUT_PLUGIN_HXX +#define MPD_SHOUT_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin shout_output_plugin; + +#endif diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx new file mode 100644 index 000000000..30745f97c --- /dev/null +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -0,0 +1,198 @@ +/* + * 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 "SolarisOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" + +#include <sys/stropts.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef __sun +#include <sys/audio.h> +#else + +/* some fake declarations that allow build this plugin on systems + other than Solaris, just to see if it compiles */ + +#define AUDIO_GETINFO 0 +#define AUDIO_SETINFO 0 +#define AUDIO_ENCODING_LINEAR 0 + +struct audio_info { + struct { + unsigned sample_rate, channels, precision, encoding; + } play; +}; + +#endif + +struct SolarisOutput { + AudioOutput base; + + /* configuration */ + const char *device; + + int fd; + + SolarisOutput() + :base(solaris_output_plugin) {} + + bool Initialize(const config_param ¶m, Error &error_r) { + return base.Configure(param, error_r); + } +}; + +static bool +solaris_output_test_default_device(void) +{ + struct stat st; + + return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) && + access("/dev/audio", W_OK) == 0; +} + +static AudioOutput * +solaris_output_init(const config_param ¶m, Error &error_r) +{ + SolarisOutput *so = new SolarisOutput(); + if (!so->Initialize(param, error_r)) { + delete so; + return nullptr; + } + + so->device = param.GetBlockValue("device", "/dev/audio"); + + return &so->base; +} + +static void +solaris_output_finish(AudioOutput *ao) +{ + SolarisOutput *so = (SolarisOutput *)ao; + + delete so; +} + +static bool +solaris_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + SolarisOutput *so = (SolarisOutput *)ao; + struct audio_info info; + int ret, flags; + + /* support only 16 bit mono/stereo for now; nothing else has + been tested */ + audio_format.format = SampleFormat::S16; + + /* open the device in non-blocking mode */ + + so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0); + if (so->fd < 0) { + error.FormatErrno("Failed to open %s", + so->device); + return false; + } + + /* restore blocking mode */ + + flags = fcntl(so->fd, F_GETFL); + if (flags > 0 && (flags & O_NONBLOCK) != 0) + fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK); + + /* configure the audio device */ + + ret = ioctl(so->fd, AUDIO_GETINFO, &info); + if (ret < 0) { + error.SetErrno("AUDIO_GETINFO failed"); + close(so->fd); + return false; + } + + info.play.sample_rate = audio_format.sample_rate; + info.play.channels = audio_format.channels; + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + + ret = ioctl(so->fd, AUDIO_SETINFO, &info); + if (ret < 0) { + error.SetErrno("AUDIO_SETINFO failed"); + close(so->fd); + return false; + } + + return true; +} + +static void +solaris_output_close(AudioOutput *ao) +{ + SolarisOutput *so = (SolarisOutput *)ao; + + close(so->fd); +} + +static size_t +solaris_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + SolarisOutput *so = (SolarisOutput *)ao; + ssize_t nbytes; + + nbytes = write(so->fd, chunk, size); + if (nbytes <= 0) { + error.SetErrno("Write failed"); + return 0; + } + + return nbytes; +} + +static void +solaris_output_cancel(AudioOutput *ao) +{ + SolarisOutput *so = (SolarisOutput *)ao; + + ioctl(so->fd, I_FLUSH); +} + +const struct AudioOutputPlugin solaris_output_plugin = { + "solaris", + solaris_output_test_default_device, + solaris_output_init, + solaris_output_finish, + nullptr, + nullptr, + solaris_output_open, + solaris_output_close, + nullptr, + nullptr, + solaris_output_play, + nullptr, + solaris_output_cancel, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx new file mode 100644 index 000000000..3f9ede7a6 --- /dev/null +++ b/src/output/plugins/SolarisOutputPlugin.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_SOLARIS_OUTPUT_PLUGIN_HXX +#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin solaris_output_plugin; + +#endif diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx new file mode 100644 index 000000000..e5c5a6f0c --- /dev/null +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -0,0 +1,352 @@ +/* + * 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 "WinmmOutputPlugin.hxx" +#include "../OutputAPI.hxx" +#include "pcm/PcmBuffer.hxx" +#include "mixer/MixerList.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" + +#include <stdlib.h> +#include <string.h> + +struct WinmmBuffer { + PcmBuffer buffer; + + WAVEHDR hdr; +}; + +struct WinmmOutput { + AudioOutput base; + + UINT device_id; + HWAVEOUT handle; + + /** + * This event is triggered by Windows when a buffer is + * finished. + */ + HANDLE event; + + WinmmBuffer buffers[8]; + unsigned next_buffer; + + WinmmOutput() + :base(winmm_output_plugin) {} +}; + +static constexpr Domain winmm_output_domain("winmm_output"); + +HWAVEOUT +winmm_output_get_handle(WinmmOutput &output) +{ + return output.handle; +} + +static bool +winmm_output_test_default_device(void) +{ + return waveOutGetNumDevs() > 0; +} + +static bool +get_device_id(const char *device_name, UINT *device_id, Error &error) +{ + /* if device is not specified use wave mapper */ + if (device_name == nullptr) { + *device_id = WAVE_MAPPER; + return true; + } + + UINT numdevs = waveOutGetNumDevs(); + + /* check for device id */ + char *endptr; + UINT id = strtoul(device_name, &endptr, 0); + if (endptr > device_name && *endptr == 0) { + if (id >= numdevs) + goto fail; + *device_id = id; + return true; + } + + /* check for device name */ + for (UINT i = 0; i < numdevs; i++) { + WAVEOUTCAPS caps; + MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) + continue; + /* szPname is only 32 chars long, so it is often truncated. + Use partial match to work around this. */ + if (strstr(device_name, caps.szPname) == device_name) { + *device_id = i; + return true; + } + } + +fail: + error.Format(winmm_output_domain, + "device \"%s\" is not found", device_name); + return false; +} + +static AudioOutput * +winmm_output_init(const config_param ¶m, Error &error) +{ + WinmmOutput *wo = new WinmmOutput(); + if (!wo->base.Configure(param, error)) { + delete wo; + return nullptr; + } + + const char *device = param.GetBlockValue("device"); + if (!get_device_id(device, &wo->device_id, error)) { + delete wo; + return nullptr; + } + + return &wo->base; +} + +static void +winmm_output_finish(AudioOutput *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + delete wo; +} + +static bool +winmm_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + wo->event = CreateEvent(nullptr, false, false, nullptr); + if (wo->event == nullptr) { + error.Set(winmm_output_domain, "CreateEvent() failed"); + return false; + } + + switch (audio_format.format) { + case SampleFormat::S8: + case SampleFormat::S16: + break; + + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + case SampleFormat::DSD: + case SampleFormat::UNDEFINED: + /* we havn't tested formats other than S16 */ + audio_format.format = SampleFormat::S16; + break; + } + + if (audio_format.channels > 2) + /* same here: more than stereo was not tested */ + audio_format.channels = 2; + + WAVEFORMATEX format; + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = audio_format.channels; + format.nSamplesPerSec = audio_format.sample_rate; + format.nBlockAlign = audio_format.GetFrameSize(); + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.wBitsPerSample = audio_format.GetSampleSize() * 8; + format.cbSize = 0; + + MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format, + (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); + if (result != MMSYSERR_NOERROR) { + CloseHandle(wo->event); + error.Set(winmm_output_domain, "waveOutOpen() failed"); + return false; + } + + for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) { + memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); + } + + wo->next_buffer = 0; + + return true; +} + +static void +winmm_output_close(AudioOutput *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) + wo->buffers[i].buffer.Clear(); + + waveOutClose(wo->handle); + + CloseHandle(wo->event); +} + +/** + * Copy data into a buffer, and prepare the wave header. + */ +static bool +winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, + const void *data, size_t size, + Error &error) +{ + void *dest = buffer->buffer.Get(size); + assert(dest != nullptr); + + memcpy(dest, data, size); + + memset(&buffer->hdr, 0, sizeof(buffer->hdr)); + buffer->hdr.lpData = (LPSTR)dest; + buffer->hdr.dwBufferLength = size; + + MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + error.Set(winmm_output_domain, result, + "waveOutPrepareHeader() failed"); + return false; + } + + return true; +} + +/** + * Wait until the buffer is finished. + */ +static bool +winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, + Error &error) +{ + if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) + /* already finished */ + return true; + + while (true) { + MMRESULT result = waveOutUnprepareHeader(wo->handle, + &buffer->hdr, + sizeof(buffer->hdr)); + if (result == MMSYSERR_NOERROR) + return true; + else if (result != WAVERR_STILLPLAYING) { + error.Set(winmm_output_domain, result, + "waveOutUnprepareHeader() failed"); + return false; + } + + /* wait some more */ + WaitForSingleObject(wo->event, INFINITE); + } +} + +static size_t +winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + /* get the next buffer from the ring and prepare it */ + WinmmBuffer *buffer = &wo->buffers[wo->next_buffer]; + if (!winmm_drain_buffer(wo, buffer, error) || + !winmm_set_buffer(wo, buffer, chunk, size, error)) + return 0; + + /* enqueue the buffer */ + MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + error.Set(winmm_output_domain, result, + "waveOutWrite() failed"); + return 0; + } + + /* mark our buffer as "used" */ + wo->next_buffer = (wo->next_buffer + 1) % + ARRAY_SIZE(wo->buffers); + + return size; +} + +static bool +winmm_drain_all_buffers(WinmmOutput *wo, Error &error) +{ + for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) + return false; + + for (unsigned i = 0; i < wo->next_buffer; ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error)) + return false; + + return true; +} + +static void +winmm_stop(WinmmOutput *wo) +{ + waveOutReset(wo->handle); + + for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) { + WinmmBuffer *buffer = &wo->buffers[i]; + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + } +} + +static void +winmm_output_drain(AudioOutput *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + if (!winmm_drain_all_buffers(wo, IgnoreError())) + winmm_stop(wo); +} + +static void +winmm_output_cancel(AudioOutput *ao) +{ + WinmmOutput *wo = (WinmmOutput *)ao; + + winmm_stop(wo); +} + +const struct AudioOutputPlugin winmm_output_plugin = { + "winmm", + winmm_output_test_default_device, + winmm_output_init, + winmm_output_finish, + nullptr, + nullptr, + winmm_output_open, + winmm_output_close, + nullptr, + nullptr, + winmm_output_play, + winmm_output_drain, + winmm_output_cancel, + nullptr, + &winmm_mixer_plugin, +}; diff --git a/src/output/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx new file mode 100644 index 000000000..50fae4f2f --- /dev/null +++ b/src/output/plugins/WinmmOutputPlugin.hxx @@ -0,0 +1,42 @@ +/* + * 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_WINMM_OUTPUT_PLUGIN_HXX +#define MPD_WINMM_OUTPUT_PLUGIN_HXX + +#include "check.h" + +#ifdef ENABLE_WINMM_OUTPUT + +#include "Compiler.h" + +#include <windows.h> +#include <mmsystem.h> + +struct WinmmOutput; + +extern const struct AudioOutputPlugin winmm_output_plugin; + +gcc_pure +HWAVEOUT +winmm_output_get_handle(WinmmOutput &output); + +#endif + +#endif diff --git a/src/output/plugins/httpd/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx new file mode 100644 index 000000000..3797c3d26 --- /dev/null +++ b/src/output/plugins/httpd/HttpdClient.cxx @@ -0,0 +1,484 @@ +/* + * 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 "HttpdClient.hxx" +#include "HttpdInternal.hxx" +#include "util/ASCII.hxx" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "system/SocketError.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +HttpdClient::~HttpdClient() +{ + if (state == RESPONSE) { + if (current_page != nullptr) + current_page->Unref(); + + ClearQueue(); + } + + if (metadata) + metadata->Unref(); + + if (IsDefined()) + BufferedSocket::Close(); +} + +void +HttpdClient::Close() +{ + httpd.RemoveClient(*this); +} + +void +HttpdClient::LockClose() +{ + const ScopeLock protect(httpd.mutex); + Close(); +} + +void +HttpdClient::BeginResponse() +{ + assert(state != RESPONSE); + + state = RESPONSE; + current_page = nullptr; + + if (!head_method) + httpd.SendHeader(*this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (memcmp(line, "HEAD /", 6) == 0) { + line += 6; + head_method = true; + } else if (memcmp(line, "GET /", 5) == 0) { + line += 5; + } else { + /* only GET is supported */ + LogWarning(httpd_output_domain, + "malformed request line from client"); + return false; + } + + line = strchr(line, ' '); + if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + + if (head_method) + return false; + + BeginResponse(); + return true; + } + + /* after the request line, request headers follow */ + state = HEADERS; + return true; + } else { + if (*line == 0) { + /* empty line: request is finished */ + + BeginResponse(); + return true; + } + + if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) || + StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) { + /* Send as dlna */ + dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + metadata_supported = false; + metadata_requested = false; + return true; + } + + /* expect more request headers */ + return true; + } +} + +/** + * Sends the status line and response headers to the client. + */ +bool +HttpdClient::SendResponse() +{ + char buffer[1024], *allocated = nullptr; + const char *response; + + assert(state == RESPONSE); + + if (dlna_streaming_requested) { + snprintf(buffer, sizeof(buffer), + "HTTP/1.1 206 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" + "Connection: close\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" + "\r\n", + httpd.content_type); + response = buffer; + + } else if (metadata_requested) { + response = allocated = + icy_server_metadata_header(httpd.name, httpd.genre, + httpd.website, + httpd.content_type, + metaint); + } else { /* revert to a normal HTTP request */ + snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + httpd.content_type); + response = buffer; + } + + ssize_t nbytes = SocketMonitor::Write(response, strlen(response)); + delete[] allocated; + if (gcc_unlikely(nbytes < 0)) { + const SocketErrorMessage msg; + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + Close(); + return false; + } + + return true; +} + +HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop, + bool _metadata_supported) + :BufferedSocket(_fd, _loop), + httpd(_httpd), + state(REQUEST), + queue_size(0), + head_method(false), + dlna_streaming_requested(false), + metadata_supported(_metadata_supported), + metadata_requested(false), metadata_sent(true), + metaint(8192), /*TODO: just a std value */ + metadata(nullptr), + metadata_current_position(0), metadata_fill(0) +{ +} + +void +HttpdClient::ClearQueue() +{ + assert(state == RESPONSE); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + +#ifndef NDEBUG + assert(queue_size >= page->size); + queue_size -= page->size; +#endif + + page->Unref(); + } + + assert(queue_size == 0); +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + ClearQueue(); + + if (current_page == nullptr) + CancelWrite(); +} + +ssize_t +HttpdClient::TryWritePage(const Page &page, size_t position) +{ + assert(position < page.size); + + return Write(page.data + position, page.size - position); +} + +ssize_t +HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n) +{ + return n >= 0 + ? Write(page.data + position, n) + : TryWritePage(page, position); +} + +ssize_t +HttpdClient::GetBytesTillMetaData() const +{ + if (metadata_requested && + current_page->size - current_position > metaint - metadata_fill) + return metaint - metadata_fill; + + return -1; +} + +inline bool +HttpdClient::TryWrite() +{ + const ScopeLock protect(httpd.mutex); + + assert(state == RESPONSE); + + if (current_page == nullptr) { + if (pages.empty()) { + /* another thread has removed the event source + while this thread was waiting for + httpd.mutex */ + CancelWrite(); + return true; + } + + current_page = pages.front(); + pages.pop(); + current_position = 0; + + assert(queue_size >= current_page->size); + queue_size -= current_page->size; + } + + const ssize_t bytes_to_write = GetBytesTillMetaData(); + if (bytes_to_write == 0) { + if (!metadata_sent) { + ssize_t nbytes = TryWritePage(*metadata, + metadata_current_position); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_current_position += nbytes; + + if (metadata->size - metadata_current_position == 0) { + metadata_fill = 0; + metadata_current_position = 0; + metadata_sent = true; + } + } else { + char empty_data = 0; + + ssize_t nbytes = Write(&empty_data, 1); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + metadata_fill = 0; + metadata_current_position = 0; + } + } else { + ssize_t nbytes = + TryWritePageN(*current_page, current_position, + bytes_to_write); + if (nbytes < 0) { + auto e = GetSocketError(); + if (IsSocketErrorAgain(e)) + return true; + + if (!IsSocketErrorClosed(e)) { + SocketErrorMessage msg(e); + FormatWarning(httpd_output_domain, + "failed to write to client: %s", + (const char *)msg); + } + + Close(); + return false; + } + + current_position += nbytes; + assert(current_position <= current_page->size); + + if (metadata_requested) + metadata_fill += nbytes; + + if (current_position >= current_page->size) { + current_page->Unref(); + current_page = nullptr; + + if (pages.empty()) + /* all pages are sent: remove the + event source */ + CancelWrite(); + } + } + + return true; +} + +void +HttpdClient::PushPage(Page *page) +{ + if (state != RESPONSE) + /* the client is still writing the HTTP request */ + return; + + if (queue_size > 256 * 1024) { + FormatDebug(httpd_output_domain, + "client is too slow, flushing its queue"); + ClearQueue(); + } + + page->Ref(); + pages.push(page); + queue_size += page->size; + + ScheduleWrite(); +} + +void +HttpdClient::PushMetaData(Page *page) +{ + assert(page != nullptr); + + if (metadata) { + metadata->Unref(); + metadata = nullptr; + } + + page->Ref(); + metadata = page; + metadata_sent = false; +} + +bool +HttpdClient::OnSocketReady(unsigned flags) +{ + if (!BufferedSocket::OnSocketReady(flags)) + return false; + + if (flags & WRITE) + if (!TryWrite()) + return false; + + return true; +} + +BufferedSocket::InputResult +HttpdClient::OnSocketInput(void *data, size_t length) +{ + if (state == RESPONSE) { + LogWarning(httpd_output_domain, + "unexpected input from client"); + LockClose(); + return InputResult::CLOSED; + } + + char *line = (char *)data; + char *newline = (char *)memchr(line, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + ConsumeInput(newline + 1 - line); + + if (newline > line && newline[-1] == '\r') + --newline; + + /* terminate the string at the end of the line */ + *newline = 0; + + if (!HandleLine(line)) { + LockClose(); + return InputResult::CLOSED; + } + + if (state == RESPONSE) { + if (!SendResponse()) + return InputResult::CLOSED; + + if (head_method) { + LockClose(); + return InputResult::CLOSED; + } + } + + return InputResult::AGAIN; +} + +void +HttpdClient::OnSocketError(Error &&error) +{ + LogError(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/plugins/httpd/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx new file mode 100644 index 000000000..f94f05769 --- /dev/null +++ b/src/output/plugins/httpd/HttpdClient.hxx @@ -0,0 +1,193 @@ +/* + * 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_OUTPUT_HTTPD_CLIENT_HXX +#define MPD_OUTPUT_HTTPD_CLIENT_HXX + +#include "event/BufferedSocket.hxx" +#include "Compiler.h" + +#include <queue> +#include <list> + +#include <stddef.h> + +class HttpdOutput; +class Page; + +class HttpdClient final : BufferedSocket { + /** + * The httpd output object this client is connected to. + */ + HttpdOutput &httpd; + + /** + * The current state of the client. + */ + enum { + /** reading the request line */ + REQUEST, + + /** reading the request headers */ + HEADERS, + + /** sending the HTTP response */ + RESPONSE, + } state; + + /** + * A queue of #Page objects to be sent to the client. + */ + std::queue<Page *, std::list<Page *>> pages; + + /** + * The sum of all page sizes in #pages. + */ + size_t queue_size; + + /** + * The #page which is currently being sent to the client. + */ + Page *current_page; + + /** + * The amount of bytes which were already sent from + * #current_page. + */ + size_t current_position; + + /** + * Is this a HEAD request? + */ + bool head_method; + + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + + /* ICY */ + + /** + * Do we support sending Icy-Metadata to the client? This is + * disabled if the httpd audio output uses encoder tags. + */ + bool metadata_supported; + + /** + * If we should sent icy metadata. + */ + bool metadata_requested; + + /** + * If the current metadata was already sent to the client. + */ + bool metadata_sent; + + /** + * The amount of streaming data between each metadata block + */ + unsigned metaint; + + /** + * The metadata as #Page which is currently being sent to the client. + */ + Page *metadata; + + /* + * The amount of bytes which were already sent from the metadata. + */ + size_t metadata_current_position; + + /** + * The amount of streaming data sent to the client + * since the last icy information was sent. + */ + unsigned metadata_fill; + +public: + /** + * @param httpd the HTTP output device + * @param fd the socket file descriptor + */ + HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop, + bool _metadata_supported); + + /** + * Note: this does not remove the client from the + * #HttpdOutput object. + */ + ~HttpdClient(); + + /** + * Frees the client and removes it from the server's client list. + */ + void Close(); + + void LockClose(); + + /** + * Clears the page queue. + */ + void CancelQueue(); + + /** + * Handle a line of the HTTP request. + */ + bool HandleLine(const char *line); + + /** + * Switch the client to the "RESPONSE" state. + */ + void BeginResponse(); + + /** + * Sends the status line and response headers to the client. + */ + bool SendResponse(); + + gcc_pure + ssize_t GetBytesTillMetaData() const; + + ssize_t TryWritePage(const Page &page, size_t position); + ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n); + + bool TryWrite(); + + /** + * Appends a page to the client's queue. + */ + void PushPage(Page *page); + + /** + * Sends the passed metadata. + */ + void PushMetaData(Page *page); + +private: + void ClearQueue(); + +protected: + virtual bool OnSocketReady(unsigned flags) override; + virtual InputResult OnSocketInput(void *data, size_t length) override; + virtual void OnSocketError(Error &&error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx new file mode 100644 index 000000000..5c113520d --- /dev/null +++ b/src/output/plugins/httpd/HttpdInternal.hxx @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Internal declarations for the "httpd" audio output plugin. + */ + +#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H +#define MPD_OUTPUT_HTTPD_INTERNAL_H + +#include "output/Internal.hxx" +#include "output/Timer.hxx" +#include "thread/Mutex.hxx" +#include "event/ServerSocket.hxx" +#include "event/DeferredMonitor.hxx" +#include "util/Cast.hxx" + +#ifdef _LIBCPP_VERSION +/* can't use incomplete template arguments with libc++ */ +#include "HttpdClient.hxx" +#endif + +#include <forward_list> +#include <queue> +#include <list> + +struct config_param; +class Error; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; +struct Encoder; +struct Tag; + +class HttpdOutput final : ServerSocket, DeferredMonitor { + AudioOutput base; + + /** + * True if the audio output is open and accepts client + * connections. + */ + bool open; + + /** + * The configured encoder plugin. + */ + Encoder *encoder; + + /** + * Number of bytes which were fed into the encoder, without + * ever receiving new output. This is used to estimate + * whether MPD should manually flush the encoder, to avoid + * buffer underruns in the client. + */ + size_t unflushed_input; + +public: + /** + * The MIME type produced by the #encoder. + */ + const char *content_type; + + /** + * This mutex protects the listener socket and the client + * list. + */ + mutable Mutex mutex; + + /** + * This condition gets signalled when an item is removed from + * #pages. + */ + Cond cond; + +private: + /** + * A #Timer object to synchronize this output with the + * wallclock. + */ + Timer *timer; + + /** + * The header page, which is sent to every client on connect. + */ + Page *header; + + /** + * The metadata, which is sent to every client. + */ + Page *metadata; + + /** + * The page queue, i.e. pages from the encoder to be + * broadcasted to all clients. This container is necessary to + * pass pages from the OutputThread to the IOThread. It is + * protected by #mutex, and removing signals #cond. + */ + std::queue<Page *, std::list<Page *>> pages; + + public: + /** + * The configured name. + */ + char const *name; + /** + * The configured genre. + */ + char const *genre; + /** + * The configured website address. + */ + char const *website; + +private: + /** + * A linked list containing all clients which are currently + * connected. + */ + std::forward_list<HttpdClient> clients; + + /** + * A temporary buffer for the httpd_output_read_page() + * function. + */ + char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + unsigned clients_max, clients_cnt; + +public: + HttpdOutput(EventLoop &_loop); + ~HttpdOutput(); + +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) + constexpr +#endif + static HttpdOutput *Cast(AudioOutput *ao) { + return &ContainerCast(*ao, &HttpdOutput::base); + } + + using DeferredMonitor::GetEventLoop; + + bool Init(const config_param ¶m, Error &error); + + bool Configure(const config_param ¶m, Error &error); + + AudioOutput *InitAndConfigure(const config_param ¶m, + Error &error) { + if (!Init(param, error)) + return nullptr; + + if (!Configure(param, error)) + return nullptr; + + return &base; + } + + bool Bind(Error &error); + void Unbind(); + + /** + * Caller must lock the mutex. + */ + bool OpenEncoder(AudioFormat &audio_format, Error &error); + + /** + * Caller must lock the mutex. + */ + bool Open(AudioFormat &audio_format, Error &error); + + /** + * Caller must lock the mutex. + */ + void Close(); + + /** + * Check whether there is at least one client. + * + * Caller must lock the mutex. + */ + gcc_pure + bool HasClients() const { + return !clients.empty(); + } + + /** + * Check whether there is at least one client. + */ + gcc_pure + bool LockHasClients() const { + const ScopeLock protect(mutex); + return HasClients(); + } + + void AddClient(int fd); + + /** + * Removes a client from the httpd_output.clients linked list. + */ + void RemoveClient(HttpdClient &client); + + /** + * Sends the encoder header to the client. This is called + * right after the response headers have been sent. + */ + void SendHeader(HttpdClient &client) const; + + gcc_pure + unsigned Delay() const; + + /** + * Reads data from the encoder (as much as available) and + * returns it as a new #page object. + */ + Page *ReadPage(); + + /** + * Broadcasts a page struct to all clients. + * + * Mutext must not be locked. + */ + void BroadcastPage(Page *page); + + /** + * Broadcasts data from the encoder to all clients. + */ + void BroadcastFromEncoder(); + + bool EncodeAndPlay(const void *chunk, size_t size, Error &error); + + void SendTag(const Tag *tag); + + size_t Play(const void *chunk, size_t size, Error &error); + + void CancelAllClients(); + +private: + virtual void RunDeferred() override; + + virtual void OnAccept(int fd, const sockaddr &address, + size_t address_length, int uid) override; +}; + +extern const class Domain httpd_output_domain; + +#endif diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx new file mode 100644 index 000000000..e3ba7727d --- /dev/null +++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx @@ -0,0 +1,601 @@ +/* + * 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 "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" +#include "output/OutputAPI.hxx" +#include "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "system/Resolver.hxx" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "system/fd_util.h" +#include "IOThread.hxx" +#include "event/Call.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> + +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#ifdef HAVE_LIBWRAP +#include <sys/socket.h> /* needed for AF_UNIX */ +#include <tcpd.h> +#endif + +const Domain httpd_output_domain("httpd_output"); + +inline +HttpdOutput::HttpdOutput(EventLoop &_loop) + :ServerSocket(_loop), DeferredMonitor(_loop), + base(httpd_output_plugin), + encoder(nullptr), unflushed_input(0), + metadata(nullptr) +{ +} + +HttpdOutput::~HttpdOutput() +{ + if (metadata != nullptr) + metadata->Unref(); + + if (encoder != nullptr) + encoder_finish(encoder); + +} + +inline bool +HttpdOutput::Bind(Error &error) +{ + open = false; + + bool result = false; + BlockingCall(GetEventLoop(), [this, &error, &result](){ + result = ServerSocket::Open(error); + }); + return result; +} + +inline void +HttpdOutput::Unbind() +{ + assert(!open); + + BlockingCall(GetEventLoop(), [this](){ + ServerSocket::Close(); + }); +} + +inline bool +HttpdOutput::Configure(const config_param ¶m, Error &error) +{ + /* read configuration */ + name = param.GetBlockValue("name", "Set name in config"); + genre = param.GetBlockValue("genre", "Set genre in config"); + website = param.GetBlockValue("website", "Set website in config"); + + unsigned port = param.GetBlockValue("port", 8000u); + + const char *encoder_name = + param.GetBlockValue("encoder", "vorbis"); + const auto encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == nullptr) { + error.Format(httpd_output_domain, + "No such encoder: %s", encoder_name); + return false; + } + + clients_max = param.GetBlockValue("max_clients", 0u); + + /* set up bind_to_address */ + + const char *bind_to_address = param.GetBlockValue("bind_to_address"); + bool success = bind_to_address != nullptr && + strcmp(bind_to_address, "any") != 0 + ? AddHost(bind_to_address, port, error) + : AddPort(port, error); + if (!success) + return false; + + /* initialize encoder */ + + encoder = encoder_init(*encoder_plugin, param, error); + if (encoder == nullptr) + return false; + + /* determine content type */ + content_type = encoder_get_mime_type(encoder); + if (content_type == nullptr) + content_type = "application/octet-stream"; + + return true; +} + +inline bool +HttpdOutput::Init(const config_param ¶m, Error &error) +{ + return base.Configure(param, error); +} + +static AudioOutput * +httpd_output_init(const config_param ¶m, Error &error) +{ + HttpdOutput *httpd = new HttpdOutput(io_thread_get()); + + AudioOutput *result = httpd->InitAndConfigure(param, error); + if (result == nullptr) + delete httpd; + + return result; +} + +static void +httpd_output_finish(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + delete httpd; +} + +/** + * Creates a new #HttpdClient object and adds it into the + * HttpdOutput.clients linked list. + */ +inline void +HttpdOutput::AddClient(int fd) +{ + clients.emplace_front(*this, fd, GetEventLoop(), + encoder->plugin.tag == nullptr); + ++clients_cnt; + + /* pass metadata to client */ + if (metadata != nullptr) + clients.front().PushMetaData(metadata); +} + +void +HttpdOutput::RunDeferred() +{ + /* this method runs in the IOThread; it broadcasts pages from + our own queue to all clients */ + + const ScopeLock protect(mutex); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + + for (auto &client : clients) + client.PushPage(page); + + page->Unref(); + } + + /* wake up the client that may be waiting for the queue to be + flushed */ + cond.broadcast(); +} + +void +HttpdOutput::OnAccept(int fd, const sockaddr &address, + size_t address_length, gcc_unused int uid) +{ + /* the listener socket has become readable - a client has + connected */ + +#ifdef HAVE_LIBWRAP + if (address.sa_family != AF_UNIX) { + const auto hostaddr = sockaddr_to_string(&address, + address_length); + // TODO: shall we obtain the program name from argv[0]? + const char *progname = "mpd"; + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + FormatWarning(httpd_output_domain, + "libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr.c_str()); + close_socket(fd); + return; + } + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + const ScopeLock protect(mutex); + + if (fd >= 0) { + /* can we allow additional client */ + if (open && (clients_max == 0 || clients_cnt < clients_max)) + AddClient(fd); + else + close_socket(fd); + } else if (fd < 0 && errno != EINTR) { + LogErrno(httpd_output_domain, "accept() failed"); + } +} + +Page * +HttpdOutput::ReadPage() +{ + if (unflushed_input >= 65536) { + /* we have fed a lot of input into the encoder, but it + didn't give anything back yet - flush now to avoid + buffer underruns */ + encoder_flush(encoder, IgnoreError()); + unflushed_input = 0; + } + + size_t size = 0; + do { + size_t nbytes = encoder_read(encoder, + buffer + size, + sizeof(buffer) - size); + if (nbytes == 0) + break; + + unflushed_input = 0; + + size += nbytes; + } while (size < sizeof(buffer)); + + if (size == 0) + return nullptr; + + return Page::Copy(buffer, size); +} + +static bool +httpd_output_enable(AudioOutput *ao, Error &error) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Bind(error); +} + +static void +httpd_output_disable(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + httpd->Unbind(); +} + +inline bool +HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error) +{ + if (!encoder_open(encoder, audio_format, error)) + return false; + + /* we have to remember the encoder header, i.e. the first + bytes of encoder output after opening it, because it has to + be sent to every new client */ + header = ReadPage(); + + unflushed_input = 0; + + return true; +} + +inline bool +HttpdOutput::Open(AudioFormat &audio_format, Error &error) +{ + assert(!open); + assert(clients.empty()); + + /* open the encoder */ + + if (!OpenEncoder(audio_format, error)) + return false; + + /* initialize other attributes */ + + clients_cnt = 0; + timer = new Timer(audio_format); + + open = true; + + return true; +} + +static bool +httpd_output_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + const ScopeLock protect(httpd->mutex); + return httpd->Open(audio_format, error); +} + +inline void +HttpdOutput::Close() +{ + assert(open); + + open = false; + + delete timer; + + BlockingCall(GetEventLoop(), [this](){ + clients.clear(); + }); + + if (header != nullptr) + header->Unref(); + + encoder_close(encoder); +} + +static void +httpd_output_close(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + const ScopeLock protect(httpd->mutex); + httpd->Close(); +} + +void +HttpdOutput::RemoveClient(HttpdClient &client) +{ + assert(clients_cnt > 0); + + for (auto prev = clients.before_begin(), i = std::next(prev);; + prev = i, i = std::next(prev)) { + assert(i != clients.end()); + if (&*i == &client) { + clients.erase_after(prev); + clients_cnt--; + break; + } + } +} + +void +HttpdOutput::SendHeader(HttpdClient &client) const +{ + if (header != nullptr) + client.PushPage(header); +} + +inline unsigned +HttpdOutput::Delay() const +{ + if (!LockHasClients() && base.pause) { + /* if there's no client and this output is paused, + then httpd_output_pause() will not do anything, it + will not fill the buffer and it will not update the + timer; therefore, we reset the timer here */ + timer->Reset(); + + /* some arbitrary delay that is long enough to avoid + consuming too much CPU, and short enough to notice + new clients quickly enough */ + return 1000; + } + + return timer->IsStarted() + ? timer->GetDelay() + : 0; +} + +static unsigned +httpd_output_delay(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Delay(); +} + +void +HttpdOutput::BroadcastPage(Page *page) +{ + assert(page != nullptr); + + mutex.lock(); + pages.push(page); + page->Ref(); + mutex.unlock(); + + DeferredMonitor::Schedule(); +} + +void +HttpdOutput::BroadcastFromEncoder() +{ + /* synchronize with the IOThread */ + mutex.lock(); + while (!pages.empty()) + cond.wait(mutex); + + Page *page; + while ((page = ReadPage()) != nullptr) + pages.push(page); + + mutex.unlock(); + + DeferredMonitor::Schedule(); +} + +inline bool +HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error) +{ + if (!encoder_write(encoder, chunk, size, error)) + return false; + + unflushed_input += size; + + BroadcastFromEncoder(); + return true; +} + +inline size_t +HttpdOutput::Play(const void *chunk, size_t size, Error &error) +{ + if (LockHasClients()) { + if (!EncodeAndPlay(chunk, size, error)) + return 0; + } + + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + + return size; +} + +static size_t +httpd_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Play(chunk, size, error); +} + +static bool +httpd_output_pause(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + if (httpd->LockHasClients()) { + static const char silence[1020] = { 0 }; + return httpd_output_play(ao, silence, sizeof(silence), + IgnoreError()) > 0; + } else { + return true; + } +} + +inline void +HttpdOutput::SendTag(const Tag *tag) +{ + assert(tag != nullptr); + + if (encoder->plugin.tag != nullptr) { + /* embed encoder tags */ + + /* flush the current stream, and end it */ + + encoder_pre_tag(encoder, IgnoreError()); + BroadcastFromEncoder(); + + /* send the tag to the encoder - which starts a new + stream now */ + + encoder_tag(encoder, tag, IgnoreError()); + + /* the first page generated by the encoder will now be + used as the new "header" page, which is sent to all + new clients */ + + Page *page = ReadPage(); + if (page != nullptr) { + if (header != nullptr) + header->Unref(); + header = page; + BroadcastPage(page); + } + } else { + /* use Icy-Metadata */ + + if (metadata != nullptr) + metadata->Unref(); + + static constexpr TagType types[] = { + TAG_ALBUM, TAG_ARTIST, TAG_TITLE, + TAG_NUM_OF_ITEM_TYPES + }; + + metadata = icy_server_metadata_page(*tag, &types[0]); + if (metadata != nullptr) { + const ScopeLock protect(mutex); + for (auto &client : clients) + client.PushMetaData(metadata); + } + } +} + +static void +httpd_output_tag(AudioOutput *ao, const Tag *tag) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + httpd->SendTag(tag); +} + +inline void +HttpdOutput::CancelAllClients() +{ + const ScopeLock protect(mutex); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + page->Unref(); + } + + for (auto &client : clients) + client.CancelQueue(); + + cond.broadcast(); +} + +static void +httpd_output_cancel(AudioOutput *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + BlockingCall(io_thread_get(), [httpd](){ + httpd->CancelAllClients(); + }); +} + +const struct AudioOutputPlugin httpd_output_plugin = { + "httpd", + nullptr, + httpd_output_init, + httpd_output_finish, + httpd_output_enable, + httpd_output_disable, + httpd_output_open, + httpd_output_close, + httpd_output_delay, + httpd_output_tag, + httpd_output_play, + nullptr, + httpd_output_cancel, + httpd_output_pause, + nullptr, +}; diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx new file mode 100644 index 000000000..df99e2b43 --- /dev/null +++ b/src/output/plugins/httpd/HttpdOutputPlugin.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_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin httpd_output_plugin; + +#endif diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx new file mode 100644 index 000000000..146df23d1 --- /dev/null +++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx @@ -0,0 +1,134 @@ +/* + * 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 "IcyMetaDataServer.hxx" +#include "Page.hxx" +#include "tag/Tag.hxx" +#include "util/FormatString.hxx" + +#include <glib.h> + +#include <string.h> + +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint) +{ + return FormatNew("ICY 200 OK\r\n" + "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */ + "icy-notice2:MPD - The music player daemon<BR>\r\n" + "icy-name: %s\r\n" /* TODO */ + "icy-genre: %s\r\n" /* TODO */ + "icy-url: %s\r\n" /* TODO */ + "icy-pub:1\r\n" + "icy-metaint:%d\r\n" + /* TODO "icy-br:%d\r\n" */ + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + name, + genre, + url, + metaint, + /* bitrate, */ + content_type); +} + +static char * +icy_server_metadata_string(const char *stream_title, const char* stream_url) +{ + gchar *icy_metadata; + guint meta_length; + + // The leading n is a placeholder for the length information + icy_metadata = FormatNew("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); + + meta_length = strlen(icy_metadata); + + meta_length--; // subtract placeholder + + meta_length = ((int)meta_length / 16) + 1; + + icy_metadata[0] = meta_length; + + if (meta_length > 255) { + delete[] icy_metadata; + return nullptr; + } + + return icy_metadata; +} + +Page * +icy_server_metadata_page(const Tag &tag, const TagType *types) +{ + const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; + gint last_item, item; + guint position; + gchar *icy_string; + gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - + // "StreamTitle='';StreamUrl='';" + // = 4081 - 28 + stream_title[0] = '\0'; + + last_item = -1; + + while (*types != TAG_NUM_OF_ITEM_TYPES) { + const gchar *tag_item = tag.GetValue(*types++); + if (tag_item) + tag_items[++last_item] = tag_item; + } + + position = item = 0; + while (position < sizeof(stream_title) && item <= last_item) { + gint length = 0; + + length = g_strlcpy(stream_title + position, + tag_items[item++], + sizeof(stream_title) - position); + + position += length; + + if (item <= last_item) { + length = g_strlcpy(stream_title + position, + " - ", + sizeof(stream_title) - position); + + position += length; + } + } + + icy_string = icy_server_metadata_string(stream_title, ""); + + if (icy_string == nullptr) + return nullptr; + + Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1); + + delete[] icy_string; + + return icy_metadata; +} diff --git a/src/output/plugins/httpd/IcyMetaDataServer.hxx b/src/output/plugins/httpd/IcyMetaDataServer.hxx new file mode 100644 index 000000000..773b46641 --- /dev/null +++ b/src/output/plugins/httpd/IcyMetaDataServer.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICY_META_DATA_SERVER_HXX +#define MPD_ICY_META_DATA_SERVER_HXX + +#include "tag/TagType.h" + +struct Tag; +class Page; + +/** + * Free the return value with delete[]. + */ +char* +icy_server_metadata_header(const char *name, + const char *genre, const char *url, + const char *content_type, int metaint); + +Page * +icy_server_metadata_page(const Tag &tag, const TagType *types); + +#endif diff --git a/src/output/plugins/httpd/Page.cxx b/src/output/plugins/httpd/Page.cxx new file mode 100644 index 000000000..e22134bbc --- /dev/null +++ b/src/output/plugins/httpd/Page.cxx @@ -0,0 +1,70 @@ +/* + * 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 "Page.hxx" +#include "util/Alloc.hxx" + +#include <new> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +Page * +Page::Create(size_t size) +{ + void *p = xalloc(sizeof(Page) + size - + sizeof(Page::data)); + return ::new(p) Page(size); +} + +Page * +Page::Copy(const void *data, size_t size) +{ + assert(data != nullptr); + + Page *page = Create(size); + memcpy(page->data, data, size); + return page; +} + +Page * +Page::Concat(const Page &a, const Page &b) +{ + Page *page = Create(a.size + b.size); + + memcpy(page->data, a.data, a.size); + memcpy(page->data + a.size, b.data, b.size); + + return page; +} + +bool +Page::Unref() +{ + bool unused = ref.Decrement(); + + if (unused) { + this->Page::~Page(); + free(this); + } + + return unused; +} diff --git a/src/output/plugins/httpd/Page.hxx b/src/output/plugins/httpd/Page.hxx new file mode 100644 index 000000000..95f35d06a --- /dev/null +++ b/src/output/plugins/httpd/Page.hxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This is a library which manages reference counted buffers. + */ + +#ifndef MPD_PAGE_HXX +#define MPD_PAGE_HXX + +#include "util/RefCount.hxx" + +#include <stddef.h> + +/** + * A dynamically allocated buffer which keeps track of its reference + * count. This is useful for passing buffers around, when several + * instances hold references to one buffer. + */ +class Page { + /** + * The number of references to this buffer. This library uses + * atomic functions to access it, i.e. no locks are required. + * As soon as this attribute reaches zero, the buffer is + * freed. + */ + RefCount ref; + +public: + /** + * The size of this buffer in bytes. + */ + const size_t size; + + /** + * Dynamic array containing the buffer data. + */ + unsigned char data[sizeof(long)]; + +protected: + Page(size_t _size):size(_size) {} + ~Page() = default; + + /** + * Allocates a new #Page object, without filling the data + * element. + */ + static Page *Create(size_t size); + +public: + /** + * Creates a new #page object, and copies data from the + * specified buffer. It is initialized with a reference count + * of 1. + * + * @param data the source buffer + * @param size the size of the source buffer + */ + static Page *Copy(const void *data, size_t size); + + /** + * Concatenates two pages to a new page. + * + * @param a the first page + * @param b the second page, which is appended + */ + static Page *Concat(const Page &a, const Page &b); + + /** + * Increases the reference counter. + */ + void Ref() { + ref.Increment(); + } + + /** + * Decreases the reference counter. If it reaches zero, the #page is + * freed. + * + * @return true if the #page has been freed + */ + bool Unref(); +}; + +#endif diff --git a/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx new file mode 100644 index 000000000..c7dd4ccca --- /dev/null +++ b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP +#define SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP + +#include <SLES/OpenSLES_Android.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLAndroidSimpleBufferQueueItf + * variable. + */ + class AndroidSimpleBufferQueue { + SLAndroidSimpleBufferQueueItf queue; + + public: + AndroidSimpleBufferQueue() = default; + explicit AndroidSimpleBufferQueue(SLAndroidSimpleBufferQueueItf _queue) + :queue(_queue) {} + + SLresult Enqueue(const void *pBuffer, SLuint32 size) { + return (*queue)->Enqueue(queue, pBuffer, size); + } + + SLresult Clear() { + return (*queue)->Clear(queue); + } + + SLresult GetState(SLAndroidSimpleBufferQueueState *pState) { + return (*queue)->GetState(queue, pState); + } + + SLresult RegisterCallback(slAndroidSimpleBufferQueueCallback callback, + void *pContext) { + return (*queue)->RegisterCallback(queue, callback, pContext); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/Engine.hxx b/src/output/plugins/sles/Engine.hxx new file mode 100644 index 000000000..7c6e3cf50 --- /dev/null +++ b/src/output/plugins/sles/Engine.hxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_ENGINE_HPP +#define SLES_ENGINE_HPP + +#include <SLES/OpenSLES.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLEngineItf variable. + */ + class Engine { + SLEngineItf engine; + + public: + Engine() = default; + explicit Engine(SLEngineItf _engine):engine(_engine) {} + + SLresult CreateAudioPlayer(SLObjectItf *pPlayer, + SLDataSource *pAudioSrc, SLDataSink *pAudioSnk, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean *pInterfaceRequired) { + return (*engine)->CreateAudioPlayer(engine, pPlayer, + pAudioSrc, pAudioSnk, + numInterfaces, pInterfaceIds, + pInterfaceRequired); + } + + SLresult CreateOutputMix(SLObjectItf *pMix, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean *pInterfaceRequired) { + return (*engine)->CreateOutputMix(engine, pMix, + numInterfaces, pInterfaceIds, + pInterfaceRequired); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/Object.hxx b/src/output/plugins/sles/Object.hxx new file mode 100644 index 000000000..852d62d0d --- /dev/null +++ b/src/output/plugins/sles/Object.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_OBJECT_HPP +#define SLES_OBJECT_HPP + +#include <SLES/OpenSLES.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLObjectItf variable. + */ + class Object { + SLObjectItf object; + + public: + Object() = default; + explicit Object(SLObjectItf _object):object(_object) {} + + operator SLObjectItf() { + return object; + } + + SLresult Realize(bool async) { + return (*object)->Realize(object, async); + } + + void Destroy() { + (*object)->Destroy(object); + } + + SLresult GetInterface(const SLInterfaceID iid, void *pInterface) { + return (*object)->GetInterface(object, iid, pInterface); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/Play.hxx b/src/output/plugins/sles/Play.hxx new file mode 100644 index 000000000..c760151ef --- /dev/null +++ b/src/output/plugins/sles/Play.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SLES_PLAY_HPP +#define SLES_PLAY_HPP + +#include <SLES/OpenSLES.h> + +namespace SLES { + /** + * OO wrapper for an OpenSL/ES SLPlayItf variable. + */ + class Play { + SLPlayItf play; + + public: + Play() = default; + explicit Play(SLPlayItf _play):play(_play) {} + + SLresult SetPlayState(SLuint32 state) { + return (*play)->SetPlayState(play, state); + } + }; +} + +#endif diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx new file mode 100644 index 000000000..85fd9f2f2 --- /dev/null +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -0,0 +1,539 @@ +/* + * 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 "SlesOutputPlugin.hxx" +#include "Object.hxx" +#include "Engine.hxx" +#include "Play.hxx" +#include "AndroidSimpleBufferQueue.hxx" +#include "../../OutputAPI.hxx" +#include "util/Macros.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> + +class SlesOutput { + static constexpr unsigned N_BUFFERS = 3; + static constexpr size_t BUFFER_SIZE = 65536; + + AudioOutput base; + + SLES::Object engine_object, mix_object, play_object; + SLES::Play play; + SLES::AndroidSimpleBufferQueue queue; + + /** + * This mutex protects the attributes "next" and "filled". It + * is only needed while playback is launched, when the initial + * buffers are being enqueued in the caller thread, while + * another thread may invoke the registered callback. + */ + Mutex mutex; + + Cond cond; + + bool pause, cancel; + + /** + * The number of buffers queued to OpenSLES. + */ + unsigned n_queued; + + /** + * The index of the next buffer to be enqueued. + */ + unsigned next; + + /** + * Does the "next" buffer already contain synthesised samples? + * This can happen when PCMSynthesiser::Synthesise() has been + * called, but the OpenSL/ES buffer queue was full. The + * buffer will then be postponed. + */ + unsigned filled; + + /** + * An array of buffers. It's one more than being managed by + * OpenSL/ES, and the one not enqueued (see attribute #next) + * will be written to. + */ + uint8_t buffers[N_BUFFERS][BUFFER_SIZE]; + +public: + SlesOutput() + :base(sles_output_plugin) {} + + operator AudioOutput *() { + return &base; + } + + bool Initialize(const config_param ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Configure(const config_param ¶m, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + unsigned Delay() { + return pause && !cancel ? 100 : 0; + } + + size_t Play(const void *chunk, size_t size, Error &error); + + void Drain(); + void Cancel(); + bool Pause(); + +private: + void PlayedCallback(); + + /** + * OpenSL/ES callback which gets invoked when a buffer has + * been consumed. It synthesises and enqueues the next + * buffer. + */ + static void PlayedCallback(gcc_unused SLAndroidSimpleBufferQueueItf caller, + void *pContext) + { + SlesOutput &sles = *(SlesOutput *)pContext; + sles.PlayedCallback(); + } +}; + +static constexpr Domain sles_domain("sles"); + +inline bool +SlesOutput::Configure(const config_param &, Error &) +{ + return true; +} + +inline bool +SlesOutput::Open(AudioFormat &audio_format, Error &error) +{ + SLresult result; + SLObjectItf _object; + + result = slCreateEngine(&_object, 0, nullptr, 0, + nullptr, nullptr); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), "slCreateEngine() failed"); + return false; + } + + engine_object = SLES::Object(_object); + + result = engine_object.Realize(false); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), "Engine.Realize() failed"); + engine_object.Destroy(); + return false; + } + + SLEngineItf _engine; + result = engine_object.GetInterface(SL_IID_ENGINE, &_engine); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Engine.GetInterface(IID_ENGINE) failed"); + engine_object.Destroy(); + return false; + } + + SLES::Engine engine(_engine); + + result = engine.CreateOutputMix(&_object, 0, nullptr, nullptr); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Engine.CreateOutputMix() failed"); + engine_object.Destroy(); + return false; + } + + mix_object = SLES::Object(_object); + + result = mix_object.Realize(false); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Mix.Realize() failed"); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + N_BUFFERS, + }; + + if (audio_format.channels > 2) + audio_format.channels = 1; + + SLDataFormat_PCM format_pcm; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = audio_format.channels; + /* from the Android NDK docs: "Note that the field samplesPerSec is + actually in units of milliHz, despite the misleading name." */ + format_pcm.samplesPerSec = audio_format.sample_rate * 1000u; + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.channelMask = audio_format.channels == 1 + ? SL_SPEAKER_FRONT_CENTER + : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + format_pcm.endianness = IsLittleEndian() + ? SL_BYTEORDER_LITTLEENDIAN + : SL_BYTEORDER_BIGENDIAN; + + SLDataSource audioSrc = { &loc_bufq, &format_pcm }; + + SLDataLocator_OutputMix loc_outmix = { + SL_DATALOCATOR_OUTPUTMIX, + mix_object, + }; + + SLDataSink audioSnk = { + &loc_outmix, + nullptr, + }; + + const SLInterfaceID ids2[] = { + SL_IID_PLAY, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION, + }; + + static constexpr SLboolean req2[] = { + SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE, + }; + + result = engine.CreateAudioPlayer(&_object, &audioSrc, &audioSnk, + ARRAY_SIZE(ids2), ids2, req2); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Engine.CreateAudioPlayer() failed"); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + play_object = SLES::Object(_object); + + SLAndroidConfigurationItf android_config; + if (play_object.GetInterface(SL_IID_ANDROIDCONFIGURATION, + &android_config) == SL_RESULT_SUCCESS) { + SLint32 stream_type = SL_ANDROID_STREAM_MEDIA; + (*android_config)->SetConfiguration(android_config, + SL_ANDROID_KEY_STREAM_TYPE, + &stream_type, + sizeof(stream_type)); + } + + result = play_object.Realize(false); + + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.Realize() failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + SLPlayItf _play; + result = play_object.GetInterface(SL_IID_PLAY, &_play); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.GetInterface(IID_PLAY) failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + play = SLES::Play(_play); + + SLAndroidSimpleBufferQueueItf _queue; + result = play_object.GetInterface(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &_queue); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.GetInterface(IID_ANDROIDSIMPLEBUFFERQUEUE) failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + queue = SLES::AndroidSimpleBufferQueue(_queue); + result = queue.RegisterCallback(PlayedCallback, (void *)this); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.RegisterCallback() failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + result = play.SetPlayState(SL_PLAYSTATE_PLAYING); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.SetPlayState(PLAYING) failed"); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); + return false; + } + + pause = cancel = false; + n_queued = 0; + next = 0; + filled = 0; + + // TODO: support other sample formats + audio_format.format = SampleFormat::S16; + + return true; +} + +inline void +SlesOutput::Close() +{ + play.SetPlayState(SL_PLAYSTATE_STOPPED); + play_object.Destroy(); + mix_object.Destroy(); + engine_object.Destroy(); +} + +inline size_t +SlesOutput::Play(const void *chunk, size_t size, Error &error) +{ + cancel = false; + + if (pause) { + SLresult result = play.SetPlayState(SL_PLAYSTATE_PLAYING); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "Play.SetPlayState(PLAYING) failed"); + return false; + } + + pause = false; + } + + const ScopeLock protect(mutex); + + assert(filled < BUFFER_SIZE); + + while (n_queued == N_BUFFERS) { + assert(filled == 0); + cond.wait(mutex); + } + + size_t nbytes = std::min(BUFFER_SIZE - filled, size); + memcpy(buffers[next] + filled, chunk, nbytes); + filled += nbytes; + if (filled < BUFFER_SIZE) + return nbytes; + + SLresult result = queue.Enqueue(buffers[next], BUFFER_SIZE); + if (result != SL_RESULT_SUCCESS) { + error.Set(sles_domain, int(result), + "AndroidSimpleBufferQueue.Enqueue() failed"); + return 0; + } + + ++n_queued; + next = (next + 1) % N_BUFFERS; + filled = 0; + + return nbytes; +} + +inline void +SlesOutput::Drain() +{ + const ScopeLock protect(mutex); + + assert(filled < BUFFER_SIZE); + + while (n_queued > 0) + cond.wait(mutex); +} + +inline void +SlesOutput::Cancel() +{ + pause = true; + cancel = true; + + SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED); + if (result != SL_RESULT_SUCCESS) + FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed"); + + result = queue.Clear(); + if (result != SL_RESULT_SUCCESS) + FormatWarning(sles_domain, + "AndroidSimpleBufferQueue.Clear() failed"); + + const ScopeLock protect(mutex); + n_queued = 0; + filled = 0; +} + +inline bool +SlesOutput::Pause() +{ + cancel = false; + + if (pause) + return true; + + pause = true; + + SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED); + if (result != SL_RESULT_SUCCESS) { + FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed"); + return false; + } + + return true; +} + +inline void +SlesOutput::PlayedCallback() +{ + const ScopeLock protect(mutex); + assert(n_queued > 0); + --n_queued; + cond.signal(); +} + +static bool +sles_test_default_device() +{ + /* this is the default output plugin on Android, and it should + be available in any case */ + return true; +} + +static AudioOutput * +sles_output_init(const config_param ¶m, Error &error) +{ + SlesOutput *sles = new SlesOutput(); + + if (!sles->Initialize(param, error) || + !sles->Configure(param, error)) { + delete sles; + return nullptr; + } + + return *sles; +} + +static void +sles_output_finish(AudioOutput *ao) +{ + SlesOutput *sles = (SlesOutput *)ao; + + delete sles; +} + +static bool +sles_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Open(audio_format, error); +} + +static void +sles_output_close(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + sles.Close(); +} + +static unsigned +sles_output_delay(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Delay(); +} + +static size_t +sles_output_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Play(chunk, size, error); +} + +static void +sles_output_drain(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + sles.Drain(); +} + +static void +sles_output_cancel(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + sles.Cancel(); +} + +static bool +sles_output_pause(AudioOutput *ao) +{ + SlesOutput &sles = *(SlesOutput *)ao; + + return sles.Pause(); +} + +const struct AudioOutputPlugin sles_output_plugin = { + "sles", + sles_test_default_device, + sles_output_init, + sles_output_finish, + nullptr, + nullptr, + sles_output_open, + sles_output_close, + sles_output_delay, + nullptr, + sles_output_play, + sles_output_drain, + sles_output_cancel, + sles_output_pause, + nullptr, +}; diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx new file mode 100644 index 000000000..5424dec2e --- /dev/null +++ b/src/output/plugins/sles/SlesOutputPlugin.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_SLES_OUTPUT_PLUGIN_HXX +#define MPD_SLES_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin sles_output_plugin; + +#endif diff --git a/src/pcm/ChannelsConverter.cxx b/src/pcm/ChannelsConverter.cxx new file mode 100644 index 000000000..f93f4f677 --- /dev/null +++ b/src/pcm/ChannelsConverter.cxx @@ -0,0 +1,97 @@ +/* + * 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 "ChannelsConverter.hxx" +#include "PcmChannels.hxx" +#include "Domain.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +bool +PcmChannelsConverter::Open(SampleFormat _format, + unsigned _src_channels, unsigned _dest_channels, + gcc_unused Error &error) +{ + assert(_format != SampleFormat::UNDEFINED); + + switch (_format) { + case SampleFormat::S16: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + break; + + default: + error.Format(pcm_domain, + "PCM channel conversion for %s is not implemented", + sample_format_to_string(format)); + return false; + } + + format = _format; + src_channels = _src_channels; + dest_channels = _dest_channels; + return true; +} + +void +PcmChannelsConverter::Close() +{ +#ifndef NDEBUG + format = SampleFormat::UNDEFINED; +#endif +} + +ConstBuffer<void> +PcmChannelsConverter::Convert(ConstBuffer<void> src, gcc_unused Error &error) +{ + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::S8: + case SampleFormat::DSD: + assert(false); + gcc_unreachable(); + + case SampleFormat::S16: + return pcm_convert_channels_16(buffer, dest_channels, + src_channels, + ConstBuffer<int16_t>::FromVoid(src)).ToVoid(); + + case SampleFormat::S24_P32: + return pcm_convert_channels_24(buffer, dest_channels, + src_channels, + ConstBuffer<int32_t>::FromVoid(src)).ToVoid(); + + case SampleFormat::S32: + return pcm_convert_channels_32(buffer, dest_channels, + src_channels, + ConstBuffer<int32_t>::FromVoid(src)).ToVoid(); + + case SampleFormat::FLOAT: + return pcm_convert_channels_float(buffer, dest_channels, + src_channels, + ConstBuffer<float>::FromVoid(src)).ToVoid(); + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/ChannelsConverter.hxx b/src/pcm/ChannelsConverter.hxx new file mode 100644 index 000000000..1374f9f5d --- /dev/null +++ b/src/pcm/ChannelsConverter.hxx @@ -0,0 +1,83 @@ +/* + * 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_PCM_CHANNELS_CONVERTER_HXX +#define MPD_PCM_CHANNELS_CONVERTER_HXX + +#include "check.h" +#include "AudioFormat.hxx" +#include "PcmBuffer.hxx" + +#ifndef NDEBUG +#include <assert.h> +#endif + +class Error; +template<typename T> struct ConstBuffer; + +/** + * A class that converts samples from one format to another. + */ +class PcmChannelsConverter { + SampleFormat format; + unsigned src_channels, dest_channels; + + PcmBuffer buffer; + +public: +#ifndef NDEBUG + PcmChannelsConverter() + :format(SampleFormat::UNDEFINED) {} + + ~PcmChannelsConverter() { + assert(format == SampleFormat::UNDEFINED); + } +#endif + + /** + * Opens the object, prepare for Convert(). + * + * @param format the sample format + * @param src_channels the number of source channels + * @param dest_channels the number of destination channels + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat format, + unsigned src_channels, unsigned dest_channels, + Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close(); + + /** + * Convert a block of PCM data. + * + * @param src the input buffer + * @param error location to store the error + * @return the destination buffer on success, + * ConstBuffer::Null() on error + */ + gcc_pure + ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error); +}; + +#endif diff --git a/src/pcm/ConfiguredResampler.cxx b/src/pcm/ConfiguredResampler.cxx new file mode 100644 index 000000000..f6aec3f95 --- /dev/null +++ b/src/pcm/ConfiguredResampler.cxx @@ -0,0 +1,101 @@ +/* + * 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 "ConfiguredResampler.hxx" +#include "FallbackResampler.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +#ifdef HAVE_LIBSAMPLERATE +#include "LibsamplerateResampler.hxx" +#endif + +#ifdef HAVE_SOXR +#include "SoxrResampler.hxx" +#endif + +#include <string.h> + +enum class SelectedResampler { + FALLBACK, + +#ifdef HAVE_LIBSAMPLERATE + LIBSAMPLERATE, +#endif + +#ifdef HAVE_SOXR + SOXR, +#endif +}; + +static SelectedResampler selected_resampler = SelectedResampler::FALLBACK; + +bool +pcm_resampler_global_init(Error &error) +{ + const char *converter = + config_get_string(CONF_SAMPLERATE_CONVERTER, ""); + + if (strcmp(converter, "internal") == 0) + return true; + +#ifdef HAVE_SOXR + if (memcmp(converter, "soxr", 4) == 0) { + selected_resampler = SelectedResampler::SOXR; + return pcm_resample_soxr_global_init(converter, error); + } +#endif + +#ifdef HAVE_LIBSAMPLERATE + selected_resampler = SelectedResampler::LIBSAMPLERATE; + return pcm_resample_lsr_global_init(converter, error); +#endif + + if (*converter == 0) + return true; + + error.Format(config_domain, + "The samplerate_converter '%s' is not available", + converter); + return false; +} + +PcmResampler * +pcm_resampler_create() +{ + switch (selected_resampler) { + case SelectedResampler::FALLBACK: + return new FallbackPcmResampler(); + +#ifdef HAVE_LIBSAMPLERATE + case SelectedResampler::LIBSAMPLERATE: + return new LibsampleratePcmResampler(); +#endif + +#ifdef HAVE_SOXR + case SelectedResampler::SOXR: + return new SoxrPcmResampler(); +#endif + } + + gcc_unreachable(); +} diff --git a/src/pcm/ConfiguredResampler.hxx b/src/pcm/ConfiguredResampler.hxx new file mode 100644 index 000000000..2b14b381e --- /dev/null +++ b/src/pcm/ConfiguredResampler.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIGURED_RESAMPLER_HXX +#define MPD_CONFIGURED_RESAMPLER_HXX + +#include "check.h" + +class Error; +class PcmResampler; + +bool +pcm_resampler_global_init(Error &error); + +/** + * Create a #PcmResampler instance from the implementation class + * configured in mpd.conf. + */ +PcmResampler * +pcm_resampler_create(); + +#endif diff --git a/src/pcm/Domain.cxx b/src/pcm/Domain.cxx new file mode 100644 index 000000000..ecd5c22a4 --- /dev/null +++ b/src/pcm/Domain.cxx @@ -0,0 +1,23 @@ +/* + * 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 "Domain.hxx" +#include "util/Domain.hxx" + +const Domain pcm_domain("pcm"); diff --git a/src/pcm/Domain.hxx b/src/pcm/Domain.hxx new file mode 100644 index 000000000..781d5c71b --- /dev/null +++ b/src/pcm/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PCM_DOMAIN_HXX +#define PCM_DOMAIN_HXX + +class Domain; + +extern const Domain pcm_domain; + +#endif diff --git a/src/pcm/FallbackResampler.cxx b/src/pcm/FallbackResampler.cxx new file mode 100644 index 000000000..bd3f20d86 --- /dev/null +++ b/src/pcm/FallbackResampler.cxx @@ -0,0 +1,147 @@ +/* + * 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 "FallbackResampler.hxx" + +#include <assert.h> + +AudioFormat +FallbackPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate, + gcc_unused Error &error) +{ + assert(af.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + switch (af.format) { + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + + case SampleFormat::S8: + af.format = SampleFormat::S16; + break; + + case SampleFormat::S16: + case SampleFormat::FLOAT: + case SampleFormat::S24_P32: + case SampleFormat::S32: + break; + + case SampleFormat::DSD: + af.format = SampleFormat::FLOAT; + break; + } + + format = af; + out_rate = new_sample_rate; + + AudioFormat result = af; + result.sample_rate = new_sample_rate; + return result; +} + +void +FallbackPcmResampler::Close() +{ +} + +template<typename T> +static ConstBuffer<T> +pcm_resample_fallback(PcmBuffer &buffer, + unsigned channels, + unsigned src_rate, + ConstBuffer<T> src, + unsigned dest_rate) +{ + unsigned dest_pos = 0; + unsigned src_frames = src.size / channels; + unsigned dest_frames = + (src_frames * dest_rate + src_rate - 1) / src_rate; + unsigned dest_samples = dest_frames * channels; + size_t dest_size = dest_samples * sizeof(*src.data); + T *dest_buffer = (T *)buffer.Get(dest_size); + + assert((src.size % channels) == 0); + + switch (channels) { + case 1: + while (dest_pos < dest_samples) { + unsigned src_pos = dest_pos * src_rate / dest_rate; + + dest_buffer[dest_pos++] = src[src_pos]; + } + break; + case 2: + while (dest_pos < dest_samples) { + unsigned src_pos = dest_pos * src_rate / dest_rate; + src_pos &= ~1; + + dest_buffer[dest_pos++] = src[src_pos]; + dest_buffer[dest_pos++] = src[src_pos + 1]; + } + break; + } + + return { dest_buffer, dest_samples }; +} + +template<typename T> +static ConstBuffer<void> +pcm_resample_fallback_void(PcmBuffer &buffer, + unsigned channels, + unsigned src_rate, + ConstBuffer<void> src, + unsigned dest_rate) +{ + const auto typed_src = ConstBuffer<T>::FromVoid(src); + return pcm_resample_fallback(buffer, channels, src_rate, typed_src, + dest_rate).ToVoid(); +} + +ConstBuffer<void> +FallbackPcmResampler::Resample(ConstBuffer<void> src, gcc_unused Error &error) +{ + switch (format.format) { + case SampleFormat::UNDEFINED: + case SampleFormat::S8: + case SampleFormat::DSD: + assert(false); + gcc_unreachable(); + + case SampleFormat::S16: + return pcm_resample_fallback_void<int16_t>(buffer, + format.channels, + format.sample_rate, + src, + out_rate); + + case SampleFormat::FLOAT: + case SampleFormat::S24_P32: + case SampleFormat::S32: + return pcm_resample_fallback_void<int32_t>(buffer, + format.channels, + format.sample_rate, + src, + out_rate); + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/FallbackResampler.hxx b/src/pcm/FallbackResampler.hxx new file mode 100644 index 000000000..0b7f9d57b --- /dev/null +++ b/src/pcm/FallbackResampler.hxx @@ -0,0 +1,45 @@ +/* + * 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_PCM_FALLBACK_RESAMPLER_HXX +#define MPD_PCM_FALLBACK_RESAMPLER_HXX + +#include "Resampler.hxx" +#include "PcmBuffer.hxx" +#include "AudioFormat.hxx" + +/** + * A naive resampler that is used when no external library was found + * (or when the user explicitly asks for bad quality). + */ +class FallbackPcmResampler final : public PcmResampler { + AudioFormat format; + unsigned out_rate; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) override; +}; + +#endif diff --git a/src/pcm/FloatConvert.hxx b/src/pcm/FloatConvert.hxx new file mode 100644 index 000000000..93e867159 --- /dev/null +++ b/src/pcm/FloatConvert.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_FLOAT_CONVERT_HXX +#define MPD_PCM_FLOAT_CONVERT_HXX + +#include "Traits.hxx" + +/** + * Convert from float to an integer sample format. + */ +template<SampleFormat F, class Traits=SampleTraits<F>> +struct FloatToIntegerSampleConvert { + typedef SampleTraits<SampleFormat::FLOAT> SrcTraits; + typedef Traits DstTraits; + + typedef typename SrcTraits::value_type SV; + typedef typename SrcTraits::long_type SL; + typedef typename DstTraits::value_type DV; + + static constexpr SV factor = 1 << (DstTraits::BITS - 1); + + gcc_const + static DV Convert(SV src) { + return PcmClamp<F, Traits>(SL(src * factor)); + } +}; + +/** + * Convert from an integer sample format to float. + */ +template<SampleFormat F, class Traits=SampleTraits<F>> +struct IntegerToFloatSampleConvert { + typedef SampleTraits<SampleFormat::FLOAT> DstTraits; + typedef Traits SrcTraits; + + typedef typename SrcTraits::value_type SV; + typedef typename DstTraits::value_type DV; + + static constexpr DV factor = 0.5 / (1 << (SrcTraits::BITS - 2)); + + gcc_const + static DV Convert(SV src) { + return DV(src) * factor; + } +}; + +#endif diff --git a/src/pcm/FormatConverter.cxx b/src/pcm/FormatConverter.cxx new file mode 100644 index 000000000..64e2d8594 --- /dev/null +++ b/src/pcm/FormatConverter.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FormatConverter.hxx" +#include "PcmFormat.hxx" +#include "Domain.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +bool +PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format, + gcc_unused Error &error) +{ + assert(_src_format != SampleFormat::UNDEFINED); + assert(_dest_format != SampleFormat::UNDEFINED); + + src_format = _src_format; + dest_format = _dest_format; + return true; +} + +void +PcmFormatConverter::Close() +{ +#ifndef NDEBUG + src_format = SampleFormat::UNDEFINED; + dest_format = SampleFormat::UNDEFINED; +#endif +} + +ConstBuffer<void> +PcmFormatConverter::Convert(ConstBuffer<void> src, Error &error) +{ + switch (dest_format) { + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + + case SampleFormat::S8: + case SampleFormat::DSD: + error.Format(pcm_domain, + "PCM conversion from %s to %s is not implemented", + sample_format_to_string(src_format), + sample_format_to_string(dest_format)); + return nullptr; + + case SampleFormat::S16: + return pcm_convert_to_16(buffer, dither, + src_format, + src).ToVoid(); + + case SampleFormat::S24_P32: + return pcm_convert_to_24(buffer, + src_format, + src).ToVoid(); + + case SampleFormat::S32: + return pcm_convert_to_32(buffer, + src_format, + src).ToVoid(); + + case SampleFormat::FLOAT: + return pcm_convert_to_float(buffer, + src_format, + src).ToVoid(); + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/FormatConverter.hxx b/src/pcm/FormatConverter.hxx new file mode 100644 index 000000000..3d8b6fb75 --- /dev/null +++ b/src/pcm/FormatConverter.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_FORMAT_CONVERTER_HXX +#define MPD_PCM_FORMAT_CONVERTER_HXX + +#include "check.h" +#include "AudioFormat.hxx" +#include "PcmBuffer.hxx" +#include "PcmDither.hxx" + +#ifndef NDEBUG +#include <assert.h> +#endif + +class Error; +template<typename T> struct ConstBuffer; + +/** + * A class that converts samples from one format to another. + */ +class PcmFormatConverter { + SampleFormat src_format, dest_format; + + PcmBuffer buffer; + PcmDither dither; + +public: +#ifndef NDEBUG + PcmFormatConverter() + :src_format(SampleFormat::UNDEFINED), + dest_format(SampleFormat::UNDEFINED) {} + + ~PcmFormatConverter() { + assert(src_format == SampleFormat::UNDEFINED); + assert(dest_format == SampleFormat::UNDEFINED); + } +#endif + + /** + * Opens the object, prepare for Convert(). + * + * @param src_format the sample format of incoming data + * @param dest_format the sample format of outgoing data + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat src_format, SampleFormat dest_format, + Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close(); + + /** + * Convert a block of PCM data. + * + * @param src the input buffer + * @param error location to store the error + * @return the destination buffer on success, + * ConstBuffer::Null() on error + */ + gcc_pure + ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error); +}; + +#endif diff --git a/src/pcm/GlueResampler.cxx b/src/pcm/GlueResampler.cxx new file mode 100644 index 000000000..0f5fe0271 --- /dev/null +++ b/src/pcm/GlueResampler.cxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "GlueResampler.hxx" +#include "ConfiguredResampler.hxx" +#include "Resampler.hxx" + +#include <assert.h> + +GluePcmResampler::GluePcmResampler() + :resampler(pcm_resampler_create()) {} + +GluePcmResampler::~GluePcmResampler() +{ + delete resampler; +} + +bool +GluePcmResampler::Open(AudioFormat src_format, unsigned new_sample_rate, + Error &error) +{ + assert(src_format.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + AudioFormat requested_format = src_format; + AudioFormat dest_format = resampler->Open(requested_format, + new_sample_rate, + error); + if (!dest_format.IsValid()) + return false; + + assert(requested_format.channels == src_format.channels); + assert(dest_format.channels == src_format.channels); + assert(dest_format.sample_rate == new_sample_rate); + + if (requested_format.format != src_format.format && + !format_converter.Open(src_format.format, requested_format.format, + error)) + return false; + + src_sample_format = src_format.format; + requested_sample_format = requested_format.format; + output_sample_format = dest_format.format; + return true; +} + +void +GluePcmResampler::Close() +{ + if (requested_sample_format != src_sample_format) + format_converter.Close(); + + resampler->Close(); +} + +ConstBuffer<void> +GluePcmResampler::Resample(ConstBuffer<void> src, Error &error) +{ + assert(!src.IsNull()); + + if (requested_sample_format != src_sample_format) { + src = format_converter.Convert(src, error); + if (src.IsNull()) + return nullptr; + } + + return resampler->Resample(src, error); +} diff --git a/src/pcm/GlueResampler.hxx b/src/pcm/GlueResampler.hxx new file mode 100644 index 000000000..aff07823e --- /dev/null +++ b/src/pcm/GlueResampler.hxx @@ -0,0 +1,63 @@ +/* + * 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_GLUE_RESAMPLER_HXX +#define MPD_GLUE_RESAMPLER_HXX + +#include "check.h" +#include "AudioFormat.hxx" +#include "FormatConverter.hxx" + +class Error; +class PcmResampler; +template<typename T> struct ConstBuffer; + +/** + * A glue class that integrates a #PcmResampler and automatically + * converts source data to the sample format required by the + * #PcmResampler instance. + */ +class GluePcmResampler { + PcmResampler *const resampler; + + SampleFormat src_sample_format, requested_sample_format; + SampleFormat output_sample_format; + + /** + * This object converts input data to the sample format + * requested by the #PcmResampler. + */ + PcmFormatConverter format_converter; + +public: + GluePcmResampler(); + ~GluePcmResampler(); + + bool Open(AudioFormat src_format, unsigned new_sample_rate, + Error &error); + void Close(); + + SampleFormat GetOutputSampleFormat() const { + return output_sample_format; + } + + ConstBuffer<void> Resample(ConstBuffer<void> src, Error &error); +}; + +#endif diff --git a/src/pcm/LibsamplerateResampler.cxx b/src/pcm/LibsamplerateResampler.cxx new file mode 100644 index 000000000..8b22f1e32 --- /dev/null +++ b/src/pcm/LibsamplerateResampler.cxx @@ -0,0 +1,163 @@ +/* + * 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 "LibsamplerateResampler.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +static constexpr Domain libsamplerate_domain("libsamplerate"); + +static int lsr_converter = SRC_SINC_FASTEST; + +static bool +lsr_parse_converter(const char *s) +{ + assert(s != nullptr); + + if (*s == 0) + return true; + + char *endptr; + long l = strtol(s, &endptr, 10); + if (*endptr == 0 && src_get_name(l) != nullptr) { + lsr_converter = l; + return true; + } + + size_t length = strlen(s); + for (int i = 0;; ++i) { + const char *name = src_get_name(i); + if (name == nullptr) + break; + + if (StringEqualsCaseASCII(s, name, length)) { + lsr_converter = i; + return true; + } + } + + return false; +} + +bool +pcm_resample_lsr_global_init(const char *converter, Error &error) +{ + if (!lsr_parse_converter(converter)) { + error.Format(libsamplerate_domain, + "unknown samplerate converter '%s'", converter); + return false; + } + + FormatDebug(libsamplerate_domain, + "libsamplerate converter '%s'", + src_get_name(lsr_converter)); + + return true; +} + +AudioFormat +LibsampleratePcmResampler::Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) +{ + assert(af.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + src_rate = af.sample_rate; + dest_rate = new_sample_rate; + channels = af.channels; + + /* libsamplerate works with floating point samples */ + af.format = SampleFormat::FLOAT; + + int src_error; + state = src_new(lsr_converter, channels, &src_error); + if (!state) { + error.Format(libsamplerate_domain, src_error, + "libsamplerate initialization has failed: %s", + src_strerror(src_error)); + return AudioFormat::Undefined(); + } + + memset(&data, 0, sizeof(data)); + + data.src_ratio = double(new_sample_rate) / double(af.sample_rate); + FormatDebug(libsamplerate_domain, + "setting samplerate conversion ratio to %.2lf", + data.src_ratio); + src_set_ratio(state, data.src_ratio); + + AudioFormat result = af; + result.sample_rate = new_sample_rate; + return result; +} + +void +LibsampleratePcmResampler::Close() +{ + state = src_delete(state); +} + +static bool +src_process(SRC_STATE *state, SRC_DATA *data, Error &error) +{ + int result = src_process(state, data); + if (result != 0) { + error.Format(libsamplerate_domain, result, + "libsamplerate has failed: %s", + src_strerror(result)); + return false; + } + + return true; +} + +inline ConstBuffer<float> +LibsampleratePcmResampler::Resample2(ConstBuffer<float> src, Error &error) +{ + assert(src.size % channels == 0); + + const unsigned src_frames = src.size / channels; + const unsigned dest_frames = + (src_frames * dest_rate + src_rate - 1) / src_rate; + size_t data_out_size = dest_frames * sizeof(float) * channels; + + data.data_in = const_cast<float *>(src.data); + data.data_out = (float *)buffer.Get(data_out_size); + data.input_frames = src_frames; + data.output_frames = dest_frames; + + if (!src_process(state, &data, error)) + return nullptr; + + return ConstBuffer<float>(data.data_out, + data.output_frames_gen * channels); +} + +ConstBuffer<void> +LibsampleratePcmResampler::Resample(ConstBuffer<void> src, Error &error) +{ + return Resample2(ConstBuffer<float>::FromVoid(src), error).ToVoid(); +} diff --git a/src/pcm/LibsamplerateResampler.hxx b/src/pcm/LibsamplerateResampler.hxx new file mode 100644 index 000000000..86d74d95a --- /dev/null +++ b/src/pcm/LibsamplerateResampler.hxx @@ -0,0 +1,55 @@ +/* + * 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_PCM_LIBSAMPLERATE_RESAMPLER_HXX +#define MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX + +#include "Resampler.hxx" +#include "PcmBuffer.hxx" +#include "AudioFormat.hxx" + +#include <samplerate.h> + +/** + * A resampler using libsamplerate. + */ +class LibsampleratePcmResampler final : public PcmResampler { + unsigned src_rate, dest_rate; + unsigned channels; + + SRC_STATE *state; + SRC_DATA data; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) override; + +private: + ConstBuffer<float> Resample2(ConstBuffer<float> src, Error &error); +}; + +bool +pcm_resample_lsr_global_init(const char *converter, Error &error); + +#endif diff --git a/src/pcm/Neon.hxx b/src/pcm/Neon.hxx new file mode 100644 index 000000000..7109778ab --- /dev/null +++ b/src/pcm/Neon.hxx @@ -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. + */ + +#ifndef MPD_PCM_NEON_HXX +#define MPD_PCM_NEON_HXX + +#include "Traits.hxx" + +#include <arm_neon.h> + +/** + * Call a NEON intrinsic for each element in the vector. + * + * @param func the NEON intrinsic + * @param result the vector variable that gets assigned the result + * @param vector the input vector + */ +#define neon_x4_u(func, result, vector) do { \ + result.val[0] = func(vector.val[0]); \ + result.val[1] = func(vector.val[1]); \ + result.val[2] = func(vector.val[2]); \ + result.val[3] = func(vector.val[3]); \ +} while (0) + +/** + * Call a NEON intrinsic for each element in the vector. + * + * @param func the NEON intrinsic + * @param result the vector variable that gets assigned the result + * @param vector the input vector + */ +#define neon_x4_b(func, result, vector, ...) do { \ + result.val[0] = func(vector.val[0], __VA_ARGS__); \ + result.val[1] = func(vector.val[1], __VA_ARGS__); \ + result.val[2] = func(vector.val[2], __VA_ARGS__); \ + result.val[3] = func(vector.val[3], __VA_ARGS__); \ +} while (0) + +/** + * Convert floating point samples to 16 bit signed integer using ARM NEON. + */ +struct NeonFloatTo16 { + static constexpr SampleFormat src_format = SampleFormat::FLOAT; + static constexpr SampleFormat dst_format = SampleFormat::S16; + typedef SampleTraits<src_format> SrcTraits; + typedef SampleTraits<dst_format> DstTraits; + + typedef typename SrcTraits::value_type SV; + typedef typename DstTraits::value_type DV; + + static constexpr size_t BLOCK_SIZE = 16; + + void Convert(int16_t *dst, const float *src, const size_t n) const { + for (unsigned i = 0; i < n / BLOCK_SIZE; + ++i, src += BLOCK_SIZE, dst += BLOCK_SIZE) { + /* load 16 float samples into 4 quad + registers */ + float32x4x4_t value = vld4q_f32(src); + + /* convert to 32 bit integer */ + int32x4x4_t ivalue; + neon_x4_b(vcvtq_n_s32_f32, ivalue, value, + 30); + + /* convert to 16 bit integer with saturation + and rounding */ + int16x4x4_t nvalue; + neon_x4_b(vqrshrn_n_s32, nvalue, ivalue, + 30 - DstTraits::BITS + 1); + + /* store result */ + vst4_s16(dst, nvalue); + } + } +}; + +#endif diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx index 578c579be..7bba2de47 100644 --- a/src/pcm/PcmBuffer.cxx +++ b/src/pcm/PcmBuffer.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,7 +19,6 @@ #include "config.h" #include "PcmBuffer.hxx" -#include "poison.h" void * PcmBuffer::Get(size_t new_size) diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx index 717e24938..f56a85985 100644 --- a/src/pcm/PcmBuffer.hxx +++ b/src/pcm/PcmBuffer.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 @@ -49,6 +49,12 @@ public: */ gcc_malloc void *Get(size_t size); + + template<typename T> + gcc_malloc + T *GetT(size_t n) { + return (T *)Get(n * sizeof(T)); + } }; #endif diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx index eb69985c1..276f31045 100644 --- a/src/pcm/PcmChannels.cxx +++ b/src/pcm/PcmChannels.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 @@ -20,7 +20,9 @@ #include "config.h" #include "PcmChannels.hxx" #include "PcmBuffer.hxx" -#include "PcmUtils.hxx" +#include "Traits.hxx" +#include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include <assert.h> @@ -37,254 +39,143 @@ MonoToStereo(D dest, S src, S end) } -static void -pcm_convert_channels_16_2_to_1(int16_t *gcc_restrict dest, - const int16_t *gcc_restrict src, - const int16_t *gcc_restrict src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::value_type +StereoToMono(typename Traits::value_type _a, + typename Traits::value_type _b) { - while (src < src_end) { - int32_t a = *src++, b = *src++; + typename Traits::sum_type a(_a); + typename Traits::sum_type b(_b); - *dest++ = (a + b) / 2; - } + return typename Traits::value_type((a + b) / 2); } -static void -pcm_convert_channels_16_n_to_2(int16_t *gcc_restrict dest, - unsigned src_channels, - const int16_t *gcc_restrict src, - const int16_t *gcc_restrict src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::pointer_type +StereoToMono(typename Traits::pointer_type dest, + typename Traits::const_pointer_type src, + typename Traits::const_pointer_type end) { - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int16_t value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (int)src_channels; + while (src != end) { + const auto a = *src++; + const auto b = *src++; - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; + *dest++ = StereoToMono<F, Traits>(a, b); } -} - -const int16_t * -pcm_convert_channels_16(PcmBuffer &buffer, - unsigned dest_channels, - unsigned src_channels, const int16_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int16_t *dest = (int16_t *)buffer.Get(dest_size); - const int16_t *src_end = pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - MonoToStereo(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_16_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_16_n_to_2(dest, src_channels, src, - src_end); - else - return nullptr; return dest; } -static void -pcm_convert_channels_24_2_to_1(int32_t *gcc_restrict dest, - const int32_t *gcc_restrict src, - const int32_t *gcc_restrict src_end) -{ - while (src < src_end) { - int32_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_24_n_to_2(int32_t *gcc_restrict dest, - unsigned src_channels, - const int32_t *gcc_restrict src, - const int32_t *gcc_restrict src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::pointer_type +NToStereo(typename Traits::pointer_type dest, + unsigned src_channels, + typename Traits::const_pointer_type src, + typename Traits::const_pointer_type end) { - unsigned c; + assert((end - src) % src_channels == 0); - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) + while (src != end) { + typename Traits::sum_type sum = *src++; + for (unsigned c = 1; c < src_channels; ++c) sum += *src++; - value = sum / (int)src_channels; - /* XXX this is actually only mono ... */ + typename Traits::value_type value(sum / int(src_channels)); + + /* TODO: this is actually only mono ... */ *dest++ = value; *dest++ = value; } -} - -const int32_t * -pcm_convert_channels_24(PcmBuffer &buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r) -{ - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - int32_t *dest = (int32_t *)buffer.Get(dest_size); - const int32_t *src_end = (const int32_t *) - pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - MonoToStereo(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_24_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_24_n_to_2(dest, src_channels, src, - src_end); - else - return nullptr; return dest; } -static void -pcm_convert_channels_32_2_to_1(int32_t *gcc_restrict dest, - const int32_t *gcc_restrict src, - const int32_t *gcc_restrict src_end) -{ - while (src < src_end) { - int64_t a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } -} - -static void -pcm_convert_channels_32_n_to_2(int32_t *dest, - unsigned src_channels, const int32_t *src, - const int32_t *src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::pointer_type +NToM(typename Traits::pointer_type dest, + unsigned dest_channels, + unsigned src_channels, + typename Traits::const_pointer_type src, + typename Traits::const_pointer_type end) { - unsigned c; + assert((end - src) % src_channels == 0); - assert(src_channels > 0); - - while (src < src_end) { - int64_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) + while (src != end) { + typename Traits::sum_type sum = *src++; + for (unsigned c = 1; c < src_channels; ++c) sum += *src++; - value = sum / (int64_t)src_channels; - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; + typename Traits::value_type value(sum / int(src_channels)); + + /* TODO: this is actually only mono ... */ + for (unsigned c = 0; c < dest_channels; ++c) + *dest++ = value; } + + return dest; } -const int32_t * -pcm_convert_channels_32(PcmBuffer &buffer, - unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r) +template<SampleFormat F, class Traits=SampleTraits<F>> +static ConstBuffer<typename Traits::value_type> +ConvertChannels(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<typename Traits::value_type> src) { - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; + assert(src.size % src_channels == 0); - int32_t *dest = (int32_t *)buffer.Get(dest_size); - const int32_t *src_end = (const int32_t *) - pcm_end_pointer(src, src_size); + const size_t dest_size = src.size / src_channels * dest_channels; + auto dest = buffer.GetT<typename Traits::value_type>(dest_size); if (src_channels == 1 && dest_channels == 2) - MonoToStereo(dest, src, src_end); + MonoToStereo(dest, src.begin(), src.end()); else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_32_2_to_1(dest, src, src_end); + StereoToMono<F>(dest, src.begin(), src.end()); else if (dest_channels == 2) - pcm_convert_channels_32_n_to_2(dest, src_channels, src, - src_end); + NToStereo<F>(dest, src_channels, src.begin(), src.end()); else - return nullptr; + NToM<F>(dest, dest_channels, + src_channels, src.begin(), src.end()); - return dest; + return { dest, dest_size }; } -static void -pcm_convert_channels_float_2_to_1(float *gcc_restrict dest, - const float *gcc_restrict src, - const float *gcc_restrict src_end) +ConstBuffer<int16_t> +pcm_convert_channels_16(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<int16_t> src) { - while (src < src_end) { - double a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } + return ConvertChannels<SampleFormat::S16>(buffer, dest_channels, + src_channels, src); } -static void -pcm_convert_channels_float_n_to_2(float *dest, - unsigned src_channels, const float *src, - const float *src_end) +ConstBuffer<int32_t> +pcm_convert_channels_24(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<int32_t> src) { - unsigned c; - - assert(src_channels > 0); - - while (src < src_end) { - double sum = 0; - float value; - - for (c = 0; c < src_channels; ++c) - sum += *src++; - value = sum / (double)src_channels; + return ConvertChannels<SampleFormat::S24_P32>(buffer, dest_channels, + src_channels, src); +} - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } +ConstBuffer<int32_t> +pcm_convert_channels_32(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<int32_t> src) +{ + return ConvertChannels<SampleFormat::S32>(buffer, dest_channels, + src_channels, src); } -const float * +ConstBuffer<float> pcm_convert_channels_float(PcmBuffer &buffer, unsigned dest_channels, - unsigned src_channels, const float *src, - size_t src_size, size_t *dest_size_r) + unsigned src_channels, + ConstBuffer<float> src) { - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; - - float *dest = (float *)buffer.Get(dest_size); - const float *src_end = (const float *)pcm_end_pointer(src, src_size); - - if (src_channels == 1 && dest_channels == 2) - MonoToStereo(dest, src, src_end); - else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_float_2_to_1(dest, src, src_end); - else if (dest_channels == 2) - pcm_convert_channels_float_n_to_2(dest, src_channels, src, - src_end); - else - return nullptr; - - return dest; + return ConvertChannels<SampleFormat::FLOAT>(buffer, dest_channels, + src_channels, src); } diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx index c67822825..6ad093c3b 100644 --- a/src/pcm/PcmChannels.hxx +++ b/src/pcm/PcmChannels.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 @@ -24,6 +24,7 @@ #include <stddef.h> class PcmBuffer; +template<typename T> struct ConstBuffer; /** * Changes the number of channels in 16 bit PCM data. @@ -32,15 +33,13 @@ class PcmBuffer; * @param dest_channels the number of channels requested * @param src_channels the number of channels in the source buffer * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const int16_t * +ConstBuffer<int16_t> pcm_convert_channels_16(PcmBuffer &buffer, unsigned dest_channels, - unsigned src_channels, const int16_t *src, - size_t src_size, size_t *dest_size_r); + unsigned src_channels, + ConstBuffer<int16_t> src); /** * Changes the number of channels in 24 bit PCM data (aligned at 32 @@ -50,15 +49,13 @@ pcm_convert_channels_16(PcmBuffer &buffer, * @param dest_channels the number of channels requested * @param src_channels the number of channels in the source buffer * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const int32_t * +ConstBuffer<int32_t> pcm_convert_channels_24(PcmBuffer &buffer, unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r); + unsigned src_channels, + ConstBuffer<int32_t> src); /** * Changes the number of channels in 32 bit PCM data. @@ -67,15 +64,13 @@ pcm_convert_channels_24(PcmBuffer &buffer, * @param dest_channels the number of channels requested * @param src_channels the number of channels in the source buffer * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const int32_t * +ConstBuffer<int32_t> pcm_convert_channels_32(PcmBuffer &buffer, unsigned dest_channels, - unsigned src_channels, const int32_t *src, - size_t src_size, size_t *dest_size_r); + unsigned src_channels, + ConstBuffer<int32_t> src); /** * Changes the number of channels in 32 bit float PCM data. @@ -84,14 +79,12 @@ pcm_convert_channels_32(PcmBuffer &buffer, * @param dest_channels the number of channels requested * @param src_channels the number of channels in the source buffer * @param src the source PCM buffer - * @param src_size the number of bytes in #src - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const float * +ConstBuffer<float> pcm_convert_channels_float(PcmBuffer &buffer, unsigned dest_channels, - unsigned src_channels, const float *src, - size_t src_size, size_t *dest_size_r); + unsigned src_channels, + ConstBuffer<float> src); #endif diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx index 8eafe527c..ba9a691fc 100644 --- a/src/pcm/PcmConvert.cxx +++ b/src/pcm/PcmConvert.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,289 +19,147 @@ #include "config.h" #include "PcmConvert.hxx" -#include "PcmChannels.hxx" -#include "PcmFormat.hxx" +#include "Domain.hxx" +#include "ConfiguredResampler.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "util/ConstBuffer.hxx" #include <assert.h> #include <math.h> -const Domain pcm_convert_domain("pcm_convert"); +bool +pcm_convert_global_init(Error &error) +{ + return pcm_resampler_global_init(error); +} PcmConvert::PcmConvert() { +#ifndef NDEBUG + src_format.Clear(); + dest_format.Clear(); +#endif } PcmConvert::~PcmConvert() { + assert(!src_format.IsValid()); + assert(!dest_format.IsValid()); } -void -PcmConvert::Reset() +bool +PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format, + Error &error) { - dsd.Reset(); - resampler.Reset(); -} + assert(!src_format.IsValid()); + assert(!dest_format.IsValid()); + assert(_src_format.IsValid()); + assert(_dest_format.IsValid()); -inline const int16_t * -PcmConvert::Convert16(const AudioFormat src_format, - const void *src_buffer, size_t src_size, - const AudioFormat dest_format, size_t *dest_size_r, - Error &error) -{ - const int16_t *buf; - size_t len; + src_format = _src_format; + dest_format = _dest_format; + + AudioFormat format = src_format; + if (format.format == SampleFormat::DSD) + format.format = SampleFormat::FLOAT; - assert(dest_format.format == SampleFormat::S16); + enable_resampler = format.sample_rate != dest_format.sample_rate; + if (enable_resampler) { + if (!resampler.Open(format, dest_format.sample_rate, error)) + return false; - buf = pcm_convert_to_16(format_buffer, dither, - src_format.format, - src_buffer, src_size, - &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to 16 bit is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; + format.format = resampler.GetOutputSampleFormat(); + format.sample_rate = dest_format.sample_rate; } - if (src_format.channels != dest_format.channels) { - buf = pcm_convert_channels_16(channels_buffer, - dest_format.channels, - src_format.channels, - buf, len, &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); - return nullptr; - } + enable_format = format.format != dest_format.format; + if (enable_format && + !format_converter.Open(format.format, dest_format.format, error)) { + if (enable_resampler) + resampler.Close(); + return false; } - if (src_format.sample_rate != dest_format.sample_rate) { - buf = resampler.Resample16(dest_format.channels, - src_format.sample_rate, buf, len, - dest_format.sample_rate, &len, - error); - if (buf == nullptr) - return nullptr; + format.format = dest_format.format; + + enable_channels = format.channels != dest_format.channels; + if (enable_channels && + !channels_converter.Open(format.format, format.channels, + dest_format.channels, error)) { + if (enable_format) + format_converter.Close(); + if (enable_resampler) + resampler.Close(); + return false; } - *dest_size_r = len; - return buf; + return true; } -inline const int32_t * -PcmConvert::Convert24(const AudioFormat src_format, - const void *src_buffer, size_t src_size, - const AudioFormat dest_format, size_t *dest_size_r, - Error &error) +void +PcmConvert::Close() { - const int32_t *buf; - size_t len; - - assert(dest_format.format == SampleFormat::S24_P32); - - buf = pcm_convert_to_24(format_buffer, - src_format.format, - src_buffer, src_size, &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to 24 bit is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; - } + if (enable_channels) + channels_converter.Close(); + if (enable_format) + format_converter.Close(); + if (enable_resampler) + resampler.Close(); - if (src_format.channels != dest_format.channels) { - buf = pcm_convert_channels_24(channels_buffer, - dest_format.channels, - src_format.channels, - buf, len, &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); - return nullptr; - } - } - - if (src_format.sample_rate != dest_format.sample_rate) { - buf = resampler.Resample24(dest_format.channels, - src_format.sample_rate, buf, len, - dest_format.sample_rate, &len, - error); - if (buf == nullptr) - return nullptr; - } + dsd.Reset(); - *dest_size_r = len; - return buf; +#ifndef NDEBUG + src_format.Clear(); + dest_format.Clear(); +#endif } -inline const int32_t * -PcmConvert::Convert32(const AudioFormat src_format, - const void *src_buffer, size_t src_size, - const AudioFormat dest_format, size_t *dest_size_r, - Error &error) +ConstBuffer<void> +PcmConvert::Convert(ConstBuffer<void> buffer, Error &error) { - const int32_t *buf; - size_t len; - - assert(dest_format.format == SampleFormat::S32); - - buf = pcm_convert_to_32(format_buffer, - src_format.format, - src_buffer, src_size, &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to 32 bit is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; - } - - if (src_format.channels != dest_format.channels) { - buf = pcm_convert_channels_32(channels_buffer, - dest_format.channels, - src_format.channels, - buf, len, &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); + AudioFormat format = src_format; + + if (format.format == SampleFormat::DSD) { + auto s = ConstBuffer<uint8_t>::FromVoid(buffer); + auto d = dsd.ToFloat(format.channels, + false, s); + if (d.IsNull()) { + error.Set(pcm_domain, + "DSD to PCM conversion failed"); return nullptr; } - } - if (src_format.sample_rate != dest_format.sample_rate) { - buf = resampler.Resample32(dest_format.channels, - src_format.sample_rate, buf, len, - dest_format.sample_rate, &len, - error); - if (buf == nullptr) - return buf; + buffer = d.ToVoid(); + format.format = SampleFormat::FLOAT; } - *dest_size_r = len; - return buf; -} - -inline const float * -PcmConvert::ConvertFloat(const AudioFormat src_format, - const void *src_buffer, size_t src_size, - const AudioFormat dest_format, size_t *dest_size_r, - Error &error) -{ - const float *buffer = (const float *)src_buffer; - size_t size = src_size; - - assert(dest_format.format == SampleFormat::FLOAT); - - /* convert to float now */ + if (enable_resampler) { + buffer = resampler.Resample(buffer, error); + if (buffer.IsNull()) + return nullptr; - buffer = pcm_convert_to_float(format_buffer, - src_format.format, - buffer, size, &size); - if (buffer == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to float is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; + format.format = resampler.GetOutputSampleFormat(); + format.sample_rate = dest_format.sample_rate; } - /* convert channels */ - - if (src_format.channels != dest_format.channels) { - buffer = pcm_convert_channels_float(channels_buffer, - dest_format.channels, - src_format.channels, - buffer, size, &size); - if (buffer == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); + if (enable_format) { + buffer = format_converter.Convert(buffer, error); + if (buffer.IsNull()) return nullptr; - } - } - /* resample with float, because this is the best format for - libsamplerate */ - - if (src_format.sample_rate != dest_format.sample_rate) { - buffer = resampler.ResampleFloat(dest_format.channels, - src_format.sample_rate, - buffer, size, - dest_format.sample_rate, - &size, error); - if (buffer == nullptr) - return nullptr; + format.format = dest_format.format; } - *dest_size_r = size; - return buffer; -} - -const void * -PcmConvert::Convert(AudioFormat src_format, - const void *src, size_t src_size, - const AudioFormat dest_format, - size_t *dest_size_r, - Error &error) -{ - AudioFormat float_format; - if (src_format.format == SampleFormat::DSD) { - size_t f_size; - const float *f = dsd.ToFloat(src_format.channels, - false, (const uint8_t *)src, - src_size, &f_size); - if (f == nullptr) { - error.Set(pcm_convert_domain, - "DSD to PCM conversion failed"); + if (enable_channels) { + buffer = channels_converter.Convert(buffer, error); + if (buffer.IsNull()) return nullptr; - } - - float_format = src_format; - float_format.format = SampleFormat::FLOAT; - src_format = float_format; - src = f; - src_size = f_size; + format.channels = dest_format.channels; } - switch (dest_format.format) { - case SampleFormat::S16: - return Convert16(src_format, src, src_size, - dest_format, dest_size_r, - error); - - case SampleFormat::S24_P32: - return Convert24(src_format, src, src_size, - dest_format, dest_size_r, - error); - - case SampleFormat::S32: - return Convert32(src_format, src, src_size, - dest_format, dest_size_r, - error); - - case SampleFormat::FLOAT: - return ConvertFloat(src_format, src, src_size, - dest_format, dest_size_r, - error); - - default: - error.Format(pcm_convert_domain, - "PCM conversion to %s is not implemented", - sample_format_to_string(dest_format.format)); - return nullptr; - } + return buffer; } diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx index 40f785179..9d63e07c9 100644 --- a/src/pcm/PcmConvert.hxx +++ b/src/pcm/PcmConvert.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 @@ -20,15 +20,18 @@ #ifndef PCM_CONVERT_HXX #define PCM_CONVERT_HXX -#include "PcmDither.hxx" #include "PcmDsd.hxx" -#include "PcmResample.hxx" #include "PcmBuffer.hxx" +#include "FormatConverter.hxx" +#include "ChannelsConverter.hxx" +#include "GlueResampler.hxx" +#include "AudioFormat.hxx" #include <stddef.h> -struct AudioFormat; +template<typename T> struct ConstBuffer; class Error; +class Domain; /** * This object is statically allocated (within another struct), and @@ -38,72 +41,44 @@ class Error; class PcmConvert { PcmDsd dsd; - PcmResampler resampler; + GluePcmResampler resampler; + PcmFormatConverter format_converter; + PcmChannelsConverter channels_converter; - PcmDither dither; + AudioFormat src_format, dest_format; - /** the buffer for converting the sample format */ - PcmBuffer format_buffer; - - /** the buffer for converting the channel count */ - PcmBuffer channels_buffer; + bool enable_resampler, enable_format, enable_channels; public: PcmConvert(); ~PcmConvert(); + /** + * Prepare the object. Call Close() when done. + */ + bool Open(AudioFormat _src_format, AudioFormat _dest_format, + Error &error); /** - * Reset the pcm_convert_state object. Use this at the - * boundary between two distinct songs and each time the - * format changes. + * Close the object after it was prepared with Open(). After + * that, it may be reused by calling Open() again. */ - void Reset(); + void Close(); /** * Converts PCM data between two audio formats. * * @param src_format the source audio format * @param src the source PCM buffer - * @param src_size the size of #src in bytes * @param dest_format the requested destination audio format - * @param dest_size_r returns the number of bytes of the destination buffer - * @param error_r location to store the error occurring, or NULL to + * @param error_r location to store the error occurring, or nullptr to * ignore errors - * @return the destination buffer, or NULL on error + * @return the destination buffer, or nullptr on error */ - const void *Convert(AudioFormat src_format, - const void *src, size_t src_size, - AudioFormat dest_format, - size_t *dest_size_r, - Error &error); - -private: - const int16_t *Convert16(AudioFormat src_format, - const void *src_buffer, size_t src_size, - AudioFormat dest_format, - size_t *dest_size_r, - Error &error); - - const int32_t *Convert24(AudioFormat src_format, - const void *src_buffer, size_t src_size, - AudioFormat dest_format, - size_t *dest_size_r, - Error &error); - - const int32_t *Convert32(AudioFormat src_format, - const void *src_buffer, size_t src_size, - AudioFormat dest_format, - size_t *dest_size_r, - Error &error); - - const float *ConvertFloat(AudioFormat src_format, - const void *src_buffer, size_t src_size, - AudioFormat dest_format, - size_t *dest_size_r, - Error &error); + ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error); }; -extern const class Domain pcm_convert_domain; +bool +pcm_convert_global_init(Error &error); #endif diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx index 98d0d443e..7b2a9e900 100644 --- a/src/pcm/PcmDither.cxx +++ b/src/pcm/PcmDither.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 @@ -20,18 +20,14 @@ #include "config.h" #include "PcmDither.hxx" #include "PcmPrng.hxx" +#include "Traits.hxx" -inline int16_t -PcmDither::Dither24To16(int_fast32_t sample) +template<typename T, T MIN, T MAX, unsigned scale_bits> +inline T +PcmDither::Dither(T sample) { - constexpr unsigned from_bits = 24; - constexpr unsigned to_bits = 16; - constexpr unsigned scale_bits = from_bits - to_bits; - constexpr int_fast32_t round = 1 << (scale_bits - 1); - constexpr int_fast32_t mask = (1 << scale_bits) - 1; - constexpr int_fast32_t ONE = 1 << (from_bits - 1); - constexpr int_fast32_t MIN = -ONE; - constexpr int_fast32_t MAX = ONE - 1; + constexpr T round = 1 << (scale_bits - 1); + constexpr T mask = (1 << scale_bits) - 1; sample += error[0] - error[1] + error[2]; @@ -39,9 +35,9 @@ PcmDither::Dither24To16(int_fast32_t sample) error[1] = error[0] / 2; /* round */ - int_fast32_t output = sample + round; + T output = sample + round; - int_fast32_t rnd = pcm_prng(random); + const T rnd = pcm_prng(random); output += (rnd & mask) - (random & mask); random = rnd; @@ -63,27 +59,59 @@ PcmDither::Dither24To16(int_fast32_t sample) error[0] = sample - output; - return (int16_t)(output >> scale_bits); + return output >> scale_bits; } -void -PcmDither::Dither24To16(int16_t *dest, const int32_t *src, - const int32_t *src_end) +template<typename ST, unsigned SBITS, unsigned DBITS> +inline ST +PcmDither::DitherShift(ST sample) +{ + static_assert(sizeof(ST) * 8 > SBITS, "Source type too small"); + static_assert(SBITS > DBITS, "Non-positive scale_bits"); + + static constexpr ST MIN = -(ST(1) << (SBITS - 1)); + static constexpr ST MAX = (ST(1) << (SBITS - 1)) - 1; + + return Dither<ST, MIN, MAX, SBITS - DBITS>(sample); +} + +template<typename ST, typename DT> +inline typename DT::value_type +PcmDither::DitherConvert(typename ST::value_type sample) +{ + static_assert(ST::BITS > DT::BITS, + "Sample formats cannot be dithered"); + + constexpr unsigned scale_bits = ST::BITS - DT::BITS; + + return Dither<typename ST::sum_type, ST::MIN, ST::MAX, + scale_bits>(sample); +} + +template<typename ST, typename DT> +inline void +PcmDither::DitherConvert(typename DT::pointer_type dest, + typename ST::const_pointer_type src, + typename ST::const_pointer_type src_end) { while (src < src_end) - *dest++ = Dither24To16(*src++); + *dest++ = DitherConvert<ST, DT>(*src++); } -inline int16_t -PcmDither::Dither32To16(int_fast32_t sample) +inline void +PcmDither::Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end) { - return Dither24To16(sample >> 8); + typedef SampleTraits<SampleFormat::S24_P32> ST; + typedef SampleTraits<SampleFormat::S16> DT; + DitherConvert<ST, DT>(dest, src, src_end); } -void +inline void PcmDither::Dither32To16(int16_t *dest, const int32_t *src, const int32_t *src_end) { - while (src < src_end) - *dest++ = Dither32To16(*src++); + typedef SampleTraits<SampleFormat::S32> ST; + typedef SampleTraits<SampleFormat::S16> DT; + DitherConvert<ST, DT>(dest, src, src_end); } diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx index 106382307..54b0f7315 100644 --- a/src/pcm/PcmDither.hxx +++ b/src/pcm/PcmDither.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 @@ -22,6 +22,8 @@ #include <stdint.h> +enum class SampleFormat : uint8_t; + class PcmDither { int32_t error[3]; int32_t random; @@ -30,6 +32,18 @@ public: constexpr PcmDither() :error{0, 0, 0}, random(0) {} + /** + * Shift the given sample by #SBITS-#DBITS to the right, and + * apply dithering. + * + * @param ST the input sample type + * @param SBITS the input bit width + * @param DBITS the output bit width + * @param sample the input sample value + */ + template<typename ST, unsigned SBITS, unsigned DBITS> + ST DitherShift(ST sample); + void Dither24To16(int16_t *dest, const int32_t *src, const int32_t *src_end); @@ -37,8 +51,34 @@ public: const int32_t *src_end); private: - int16_t Dither24To16(int_fast32_t sample); - int16_t Dither32To16(int_fast32_t sample); + /** + * Shift the given sample by #scale_bits to the right, and + * apply dithering. + * + * @param T the input sample type + * @param MIN the minimum input sample value + * @param MAX the maximum input sample value + * @param scale_bits the number of bits to be discarded + * @param sample the input sample value + */ + template<typename T, T MIN, T MAX, unsigned scale_bits> + T Dither(T sample); + + /** + * Convert the given sample from one sample format to another, + * discarding bits. + * + * @param ST the input #SampleTraits class + * @param ST the output #SampleTraits class + * @param sample the input sample value + */ + template<typename ST, typename DT> + typename DT::value_type DitherConvert(typename ST::value_type sample); + + template<typename ST, typename DT> + void DitherConvert(typename DT::pointer_type dest, + typename ST::const_pointer_type src, + typename ST::const_pointer_type src_end); }; #endif diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx index 4db274635..ee549658d 100644 --- a/src/pcm/PcmDsd.cxx +++ b/src/pcm/PcmDsd.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 @@ -21,11 +21,11 @@ #include "PcmDsd.hxx" #include "dsd2pcm/dsd2pcm.h" #include "util/Macros.hxx" +#include "util/ConstBuffer.hxx" #include <algorithm> #include <assert.h> -#include <string.h> PcmDsd::PcmDsd() { @@ -47,22 +47,20 @@ PcmDsd::Reset() dsd2pcm_reset(dsd2pcm[i]); } -const float * +ConstBuffer<float> PcmDsd::ToFloat(unsigned channels, bool lsbfirst, - const uint8_t *src, size_t src_size, - size_t *dest_size_r) + ConstBuffer<uint8_t> src) { - assert(src != nullptr); - assert(src_size > 0); - assert(src_size % channels == 0); + assert(!src.IsNull()); + assert(!src.IsEmpty()); + assert(src.size % channels == 0); assert(channels <= ARRAY_SIZE(dsd2pcm)); - const unsigned num_samples = src_size; - const unsigned num_frames = src_size / channels; + const unsigned num_samples = src.size; + const unsigned num_frames = src.size / channels; float *dest; const size_t dest_size = num_samples * sizeof(*dest); - *dest_size_r = dest_size; dest = (float *)buffer.Get(dest_size); for (unsigned c = 0; c < channels; ++c) { @@ -73,9 +71,9 @@ PcmDsd::ToFloat(unsigned channels, bool lsbfirst, } dsd2pcm_translate(dsd2pcm[c], num_frames, - src + c, channels, + src.data + c, channels, lsbfirst, dest + c, channels); } - return dest; + return { dest, num_samples }; } diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx index 26ee11b13..cb3ef1fd6 100644 --- a/src/pcm/PcmDsd.hxx +++ b/src/pcm/PcmDsd.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 @@ -25,22 +25,24 @@ #include <stdint.h> +template<typename T> struct ConstBuffer; + /** * Wrapper for the dsd2pcm library. */ -struct PcmDsd { +class PcmDsd { PcmBuffer buffer; struct dsd2pcm_ctx_s *dsd2pcm[32]; +public: PcmDsd(); ~PcmDsd(); void Reset(); - const float *ToFloat(unsigned channels, bool lsbfirst, - const uint8_t *src, size_t src_size, - size_t *dest_size_r); + ConstBuffer<float> ToFloat(unsigned channels, bool lsbfirst, + ConstBuffer<uint8_t> src); }; #endif diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx index 2d0f33a15..9b854ad07 100644 --- a/src/pcm/PcmDsdUsb.cxx +++ b/src/pcm/PcmDsdUsb.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 @@ -21,6 +21,9 @@ #include "PcmDsdUsb.hxx" #include "PcmBuffer.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" + +#include <assert.h> constexpr static inline uint32_t @@ -36,18 +39,16 @@ pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b) return 0xfffa0000 | (a << 8) | b; } - -const uint32_t * +ConstBuffer<uint32_t> pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels, - const uint8_t *src, size_t src_size, - size_t *dest_size_r) + ConstBuffer<uint8_t> _src) { assert(audio_valid_channel_count(channels)); - assert(src != NULL); - assert(src_size > 0); - assert(src_size % channels == 0); + assert(!_src.IsNull()); + assert(_src.size > 0); + assert(_src.size % channels == 0); - const unsigned num_src_samples = src_size; + const unsigned num_src_samples = _src.size; const unsigned num_src_frames = num_src_samples / channels; /* this rounds down and discards the last odd frame; not @@ -55,11 +56,10 @@ pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels, const unsigned num_frames = num_src_frames / 2; const unsigned num_samples = num_frames * channels; - const size_t dest_size = num_samples * 4; - *dest_size_r = dest_size; - uint32_t *const dest0 = (uint32_t *)buffer.Get(dest_size), + uint32_t *const dest0 = (uint32_t *)buffer.GetT<uint32_t>(num_samples), *dest = dest0; + auto src = _src.data; for (unsigned i = num_frames / 2; i > 0; --i) { for (unsigned c = channels; c > 0; --c) { /* each 24 bit sample has 16 DSD sample bits @@ -92,5 +92,5 @@ pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels, src += channels; } - return dest0; + return { dest0, num_samples }; } diff --git a/src/pcm/PcmDsdUsb.hxx b/src/pcm/PcmDsdUsb.hxx index 3b7121465..5e05c009b 100644 --- a/src/pcm/PcmDsdUsb.hxx +++ b/src/pcm/PcmDsdUsb.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 @@ -26,6 +26,7 @@ #include <stddef.h> class PcmBuffer; +template<typename T> struct ConstBuffer; /** * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for @@ -33,9 +34,8 @@ class PcmBuffer; * dCS and others: * http://www.sonore.us/DoP_openStandard_1v1.pdf */ -const uint32_t * +ConstBuffer<uint32_t> pcm_dsd_to_usb(PcmBuffer &buffer, unsigned channels, - const uint8_t *src, size_t src_size, - size_t *dest_size_r); + ConstBuffer<uint8_t> src); #endif diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx index f6ce1e661..5f567f3c6 100644 --- a/src/pcm/PcmExport.cxx +++ b/src/pcm/PcmExport.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,6 +22,9 @@ #include "PcmDsdUsb.hxx" #include "PcmPack.hxx" #include "util/ByteReverse.hxx" +#include "util/ConstBuffer.hxx" + +#include <iterator> void PcmExport::Open(SampleFormat sample_format, unsigned _channels, @@ -71,59 +74,47 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const return audio_format.GetFrameSize(); } -const void * -PcmExport::Export(const void *data, size_t size, size_t &dest_size_r) +ConstBuffer<void> +PcmExport::Export(ConstBuffer<void> data) { if (dsd_usb) data = pcm_dsd_to_usb(dsd_buffer, channels, - (const uint8_t *)data, size, &size); + ConstBuffer<uint8_t>::FromVoid(data)) + .ToVoid(); if (pack24) { - assert(size % 4 == 0); - - const size_t num_samples = size / 4; + const auto src = ConstBuffer<int32_t>::FromVoid(data); + const size_t num_samples = src.size; const size_t dest_size = num_samples * 3; - - const uint8_t *src8 = (const uint8_t *)data; - const uint8_t *src_end8 = src8 + size; uint8_t *dest = (uint8_t *)pack_buffer.Get(dest_size); assert(dest != nullptr); - pcm_pack_24(dest, (const int32_t *)src8, - (const int32_t *)src_end8); + pcm_pack_24(dest, src.begin(), src.end()); - data = dest; - size = dest_size; + data.data = dest; + data.size = dest_size; } else if (shift8) { - assert(size % 4 == 0); - - const uint8_t *src8 = (const uint8_t *)data; - const uint8_t *src_end8 = src8 + size; - const uint32_t *src = (const uint32_t *)src8; - const uint32_t *const src_end = (const uint32_t *)src_end8; + const auto src = ConstBuffer<int32_t>::FromVoid(data); - uint32_t *dest = (uint32_t *)pack_buffer.Get(size); - data = dest; + uint32_t *dest = (uint32_t *)pack_buffer.Get(data.size); + data.data = dest; - while (src < src_end) - *dest++ = *src++ << 8; + for (auto i : src) + *dest++ = i << 8; } - if (reverse_endian > 0) { assert(reverse_endian >= 2); - uint8_t *dest = (uint8_t *)reverse_buffer.Get(size); - assert(dest != nullptr); + const auto src = ConstBuffer<uint8_t>::FromVoid(data); - const uint8_t *src = (const uint8_t *)data; - const uint8_t *src_end = src + size; - reverse_bytes(dest, src, src_end, reverse_endian); + uint8_t *dest = (uint8_t *)reverse_buffer.Get(data.size); + assert(dest != nullptr); + data.data = dest; - data = dest; + reverse_bytes(dest, src.begin(), src.end(), reverse_endian); } - dest_size_r = size; return data; } diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx index bd18c0534..75050e5c2 100644 --- a/src/pcm/PcmExport.hxx +++ b/src/pcm/PcmExport.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 @@ -25,6 +25,7 @@ #include "AudioFormat.hxx" struct AudioFormat; +template<typename T> struct ConstBuffer; /** * An object that handles export of PCM samples to some instance @@ -108,12 +109,9 @@ struct PcmExport { * * @param state an initialized and open pcm_export_state object * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer (may be a pointer to the source buffer) */ - const void *Export(const void *src, size_t src_size, - size_t &dest_size_r); + ConstBuffer<void> Export(ConstBuffer<void> src); /** * Converts the number of consumed bytes from the pcm_export() diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx index 4565c71c6..4cabc05a0 100644 --- a/src/pcm/PcmFormat.cxx +++ b/src/pcm/PcmFormat.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,171 +19,150 @@ #include "config.h" #include "PcmFormat.hxx" -#include "PcmDither.hxx" #include "PcmBuffer.hxx" #include "PcmUtils.hxx" +#include "Traits.hxx" +#include "FloatConvert.hxx" +#include "ShiftConvert.hxx" +#include "util/ConstBuffer.hxx" -#include <type_traits> +#include "PcmDither.cxx" // including the .cxx file to get inlined templates -template<SampleFormat F> -struct SampleTraits {}; +/** + * Wrapper for a class that converts one sample at a time into one + * that converts a buffer at a time. + */ +template<typename C> +struct PerSampleConvert : C { + typedef typename C::SrcTraits SrcTraits; + typedef typename C::DstTraits DstTraits; + + void Convert(typename DstTraits::pointer_type gcc_restrict out, + typename SrcTraits::const_pointer_type gcc_restrict in, + size_t n) const { + for (size_t i = 0; i != n; ++i) + out[i] = C::Convert(in[i]); + } +}; -template<> -struct SampleTraits<SampleFormat::S8> { - typedef int8_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; +struct Convert8To16 + : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S8, + SampleFormat::S16>> {}; - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = sizeof(value_type) * 8; -}; +struct Convert24To16 { + typedef SampleTraits<SampleFormat::S24_P32> SrcTraits; + typedef SampleTraits<SampleFormat::S16> DstTraits; -template<> -struct SampleTraits<SampleFormat::S16> { - typedef int16_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; + PcmDither &dither; - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = sizeof(value_type) * 8; + Convert24To16(PcmDither &_dither):dither(_dither) {} + + void Convert(int16_t *out, const int32_t *in, size_t n) { + dither.Dither24To16(out, in, in + n); + } }; -template<> -struct SampleTraits<SampleFormat::S32> { - typedef int32_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; +struct Convert32To16 { + typedef SampleTraits<SampleFormat::S32> SrcTraits; + typedef SampleTraits<SampleFormat::S16> DstTraits; - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = sizeof(value_type) * 8; -}; + PcmDither &dither; -template<> -struct SampleTraits<SampleFormat::S24_P32> { - typedef int32_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; + Convert32To16(PcmDither &_dither):dither(_dither) {} - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = 24; + void Convert(int16_t *out, const int32_t *in, size_t n) { + dither.Dither32To16(out, in, in + n); + } }; -static void -pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) { - *out++ = *in++ << 8; - } -} +template<SampleFormat F, class Traits=SampleTraits<F>> +struct PortableFloatToInteger + : PerSampleConvert<FloatToIntegerSampleConvert<F, Traits>> {}; -static void -pcm_convert_24_to_16(PcmDither &dither, - int16_t *out, const int32_t *in, const int32_t *in_end) -{ - dither.Dither24To16(out, in, in_end); -} +template<SampleFormat F, class Traits=SampleTraits<F>> +struct FloatToInteger : PortableFloatToInteger<F, Traits> {}; -static void -pcm_convert_32_to_16(PcmDither &dither, - int16_t *out, const int32_t *in, const int32_t *in_end) -{ - dither.Dither32To16(out, in, in_end); -} +/** + * A template class that attempts to use the "optimized" algorithm for + * large portions of the buffer, and calls the "portable" algorithm" + * for the rest when the last block is not full. + */ +template<typename Optimized, typename Portable> +class GlueOptimizedConvert : Optimized, Portable { +public: + typedef typename Portable::SrcTraits SrcTraits; + typedef typename Portable::DstTraits DstTraits; + + void Convert(typename DstTraits::pointer_type out, + typename SrcTraits::const_pointer_type in, + size_t n) const { + Optimized::Convert(out, in, n); + + /* use the "portable" algorithm for the trailing + samples */ + size_t remaining = n % Optimized::BLOCK_SIZE; + size_t done = n - remaining; + Portable::Convert(out + done, in + done, remaining); + } +}; -template<SampleFormat F, class Traits=SampleTraits<F>> -static void -ConvertFromFloat(typename Traits::pointer_type dest, - const float *src, const float *end) -{ - constexpr auto bits = Traits::BITS; +#ifdef __ARM_NEON__ +#include "Neon.hxx" - const float factor = 1 << (bits - 1); +template<> +struct FloatToInteger<SampleFormat::S16, SampleTraits<SampleFormat::S16>> + : GlueOptimizedConvert<NeonFloatTo16, + PortableFloatToInteger<SampleFormat::S16>> {}; - while (src != end) { - int sample(*src++ * factor); - *dest++ = PcmClamp<typename Traits::value_type, int, bits>(sample); - } -} +#endif -template<SampleFormat F, class Traits=SampleTraits<F>> -static void -ConvertFromFloat(typename Traits::pointer_type dest, - const float *src, size_t size) +template<class C> +static ConstBuffer<typename C::DstTraits::value_type> +AllocateConvert(PcmBuffer &buffer, C convert, + ConstBuffer<typename C::SrcTraits::value_type> src) { - ConvertFromFloat<F, Traits>(dest, src, - pcm_end_pointer(src, size)); + auto dest = buffer.GetT<typename C::DstTraits::value_type>(src.size); + convert.Convert(dest, src.data, src.size); + return { dest, src.size }; } template<SampleFormat F, class Traits=SampleTraits<F>> -static typename Traits::pointer_type -AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<typename Traits::value_type> +AllocateFromFloat(PcmBuffer &buffer, ConstBuffer<float> src) { - constexpr size_t src_sample_size = sizeof(*src); - assert(src_size % src_sample_size == 0); - - const size_t num_samples = src_size / src_sample_size; - *dest_size_r = num_samples * sizeof(typename Traits::value_type); - auto dest = (typename Traits::pointer_type)buffer.Get(*dest_size_r); - ConvertFromFloat<F, Traits>(dest, src, src_size); - return dest; + return AllocateConvert(buffer, FloatToInteger<F, Traits>(), src); } -static int16_t * -pcm_allocate_8_to_16(PcmBuffer &buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int16_t> +pcm_allocate_8_to_16(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - int16_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = (int16_t *)buffer.Get(*dest_size_r); - pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert8To16(), src); } -static int16_t * +static ConstBuffer<int16_t> pcm_allocate_24p32_to_16(PcmBuffer &buffer, PcmDither &dither, - const int32_t *src, size_t src_size, - size_t *dest_size_r) + ConstBuffer<int32_t> src) { - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = (int16_t *)buffer.Get(*dest_size_r); - pcm_convert_24_to_16(dither, dest, src, - pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert24To16(dither), src); } -static int16_t * +static ConstBuffer<int16_t> pcm_allocate_32_to_16(PcmBuffer &buffer, PcmDither &dither, - const int32_t *src, size_t src_size, - size_t *dest_size_r) + ConstBuffer<int32_t> src) { - int16_t *dest; - *dest_size_r = src_size / 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = (int16_t *)buffer.Get(*dest_size_r); - pcm_convert_32_to_16(dither, dest, src, - pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert32To16(dither), src); } -static int16_t * -pcm_allocate_float_to_16(PcmBuffer &buffer, - const float *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int16_t> +pcm_allocate_float_to_16(PcmBuffer &buffer, ConstBuffer<float> src) { - return AllocateFromFloat<SampleFormat::S16>(buffer, src, src_size, - dest_size_r); + return AllocateFromFloat<SampleFormat::S16>(buffer, src); } -const int16_t * +ConstBuffer<int16_t> pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { - assert(src_size % sample_format_size(src_format) == 0); - switch (src_format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: @@ -191,104 +170,67 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, case SampleFormat::S8: return pcm_allocate_8_to_16(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: - *dest_size_r = src_size; - return (const int16_t *)src; + return ConstBuffer<int16_t>::FromVoid(src); case SampleFormat::S24_P32: return pcm_allocate_24p32_to_16(buffer, dither, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::S32: return pcm_allocate_32_to_16(buffer, dither, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::FLOAT: return pcm_allocate_float_to_16(buffer, - (const float *)src, src_size, - dest_size_r); + ConstBuffer<float>::FromVoid(src)); } return nullptr; } -static void -pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 16; -} +struct Convert8To24 + : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S8, + SampleFormat::S24_P32>> {}; -static void -pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 8; -} +struct Convert16To24 + : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S16, + SampleFormat::S24_P32>> {}; -static void -pcm_convert_32_to_24(int32_t *gcc_restrict out, - const int32_t *gcc_restrict in, - const int32_t *gcc_restrict in_end) +static ConstBuffer<int32_t> +pcm_allocate_8_to_24(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - while (in < in_end) - *out++ = *in++ >> 8; + return AllocateConvert(buffer, Convert8To24(), src); } -static int32_t * -pcm_allocate_8_to_24(PcmBuffer &buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_16_to_24(PcmBuffer &buffer, ConstBuffer<int16_t> src) { - int32_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = (int32_t *)buffer.Get(*dest_size_r); - pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert16To24(), src); } -static int32_t * -pcm_allocate_16_to_24(PcmBuffer &buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) -{ - int32_t *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = (int32_t *)buffer.Get(*dest_size_r); - pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; -} +struct Convert32To24 + : PerSampleConvert<RightShiftSampleConvert<SampleFormat::S32, + SampleFormat::S24_P32>> {}; -static int32_t * -pcm_allocate_32_to_24(PcmBuffer &buffer, - const int32_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_32_to_24(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - *dest_size_r = src_size; - int32_t *dest = (int32_t *)buffer.Get(*dest_size_r); - pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert32To24(), src); } -static int32_t * -pcm_allocate_float_to_24(PcmBuffer &buffer, - const float *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_float_to_24(PcmBuffer &buffer, ConstBuffer<float> src) { - return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src, src_size, - dest_size_r); + return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src); } -const int32_t * +ConstBuffer<int32_t> pcm_convert_to_24(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { - assert(src_size % sample_format_size(src_format) == 0); - switch (src_format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: @@ -296,110 +238,67 @@ pcm_convert_to_24(PcmBuffer &buffer, case SampleFormat::S8: return pcm_allocate_8_to_24(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: return pcm_allocate_16_to_24(buffer, - (const int16_t *)src, src_size, - dest_size_r); + ConstBuffer<int16_t>::FromVoid(src)); case SampleFormat::S24_P32: - *dest_size_r = src_size; - return (const int32_t *)src; + return ConstBuffer<int32_t>::FromVoid(src); case SampleFormat::S32: return pcm_allocate_32_to_24(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::FLOAT: return pcm_allocate_float_to_24(buffer, - (const float *)src, src_size, - dest_size_r); + ConstBuffer<float>::FromVoid(src)); } return nullptr; } -static void -pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 24; -} +struct Convert8To32 + : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S8, + SampleFormat::S32>> {}; -static void -pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) -{ - while (in < in_end) - *out++ = *in++ << 16; -} +struct Convert16To32 + : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S16, + SampleFormat::S32>> {}; -static void -pcm_convert_24_to_32(int32_t *gcc_restrict out, - const int32_t *gcc_restrict in, - const int32_t *gcc_restrict in_end) -{ - while (in < in_end) - *out++ = *in++ << 8; -} +struct Convert24To32 + : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S24_P32, + SampleFormat::S32>> {}; -static int32_t * -pcm_allocate_8_to_32(PcmBuffer &buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_8_to_32(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - int32_t *dest; - *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); - dest = (int32_t *)buffer.Get(*dest_size_r); - pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert8To32(), src); } -static int32_t * -pcm_allocate_16_to_32(PcmBuffer &buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_16_to_32(PcmBuffer &buffer, ConstBuffer<int16_t> src) { - int32_t *dest; - *dest_size_r = src_size * 2; - assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); - dest = (int32_t *)buffer.Get(*dest_size_r); - pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert16To32(), src); } -static int32_t * -pcm_allocate_24p32_to_32(PcmBuffer &buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_24p32_to_32(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - *dest_size_r = src_size; - int32_t *dest = (int32_t *)buffer.Get(*dest_size_r); - pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); - return dest; + return AllocateConvert(buffer, Convert24To32(), src); } -static int32_t * -pcm_allocate_float_to_32(PcmBuffer &buffer, - const float *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_float_to_32(PcmBuffer &buffer, ConstBuffer<float> src) { - /* convert to S24_P32 first */ - int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, - dest_size_r); - - /* convert to 32 bit in-place */ - pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); - return dest; + return AllocateFromFloat<SampleFormat::S32>(buffer, src); } -const int32_t * +ConstBuffer<int32_t> pcm_convert_to_32(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { - assert(src_size % sample_format_size(src_format) == 0); - switch (src_format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: @@ -407,108 +306,66 @@ pcm_convert_to_32(PcmBuffer &buffer, case SampleFormat::S8: return pcm_allocate_8_to_32(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: return pcm_allocate_16_to_32(buffer, - (const int16_t *)src, src_size, - dest_size_r); + ConstBuffer<int16_t>::FromVoid(src)); case SampleFormat::S24_P32: return pcm_allocate_24p32_to_32(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::S32: - *dest_size_r = src_size; - return (const int32_t *)src; + return ConstBuffer<int32_t>::FromVoid(src); case SampleFormat::FLOAT: return pcm_allocate_float_to_32(buffer, - (const float *)src, src_size, - dest_size_r); + ConstBuffer<float>::FromVoid(src)); } return nullptr; } -template<SampleFormat F, class Traits=SampleTraits<F>> -static void -ConvertToFloat(float *dest, - typename Traits::const_pointer_type src, - typename Traits::const_pointer_type end) -{ - constexpr float factor = 0.5 / (1 << (Traits::BITS - 2)); - while (src != end) - *dest++ = float(*src++) * factor; +struct Convert8ToFloat + : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S8>> {}; -} +struct Convert16ToFloat + : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S16>> {}; -template<SampleFormat F, class Traits=SampleTraits<F>> -static void -ConvertToFloat(float *dest, - typename Traits::const_pointer_type src, size_t size) -{ - ConvertToFloat<F, Traits>(dest, src, pcm_end_pointer(src, size)); -} +struct Convert24ToFloat + : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S24_P32>> {}; -template<SampleFormat F, class Traits=SampleTraits<F>> -static float * -AllocateToFloat(PcmBuffer &buffer, - typename Traits::const_pointer_type src, size_t src_size, - size_t *dest_size_r) -{ - constexpr size_t src_sample_size = Traits::SAMPLE_SIZE; - assert(src_size % src_sample_size == 0); - - const size_t num_samples = src_size / src_sample_size; - *dest_size_r = num_samples * sizeof(float); - float *dest = (float *)buffer.Get(*dest_size_r); - ConvertToFloat<F, Traits>(dest, src, src_size); - return dest; -} +struct Convert32ToFloat + : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S32>> {}; -static float * -pcm_allocate_8_to_float(PcmBuffer &buffer, - const int8_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_8_to_float(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - return AllocateToFloat<SampleFormat::S8>(buffer, src, src_size, - dest_size_r); + return AllocateConvert(buffer, Convert8ToFloat(), src); } -static float * -pcm_allocate_16_to_float(PcmBuffer &buffer, - const int16_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_16_to_float(PcmBuffer &buffer, ConstBuffer<int16_t> src) { - return AllocateToFloat<SampleFormat::S16>(buffer, src, src_size, - dest_size_r); + return AllocateConvert(buffer, Convert16ToFloat(), src); } -static float * -pcm_allocate_24p32_to_float(PcmBuffer &buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_24p32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - return AllocateToFloat<SampleFormat::S24_P32>(buffer, src, src_size, - dest_size_r); + return AllocateConvert(buffer, Convert24ToFloat(), src); } -static float * -pcm_allocate_32_to_float(PcmBuffer &buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - return AllocateToFloat<SampleFormat::S32>(buffer, src, src_size, - dest_size_r); + return AllocateConvert(buffer, Convert32ToFloat(), src); } -const float * +ConstBuffer<float> pcm_convert_to_float(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { switch (src_format) { case SampleFormat::UNDEFINED: @@ -517,27 +374,22 @@ pcm_convert_to_float(PcmBuffer &buffer, case SampleFormat::S8: return pcm_allocate_8_to_float(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: return pcm_allocate_16_to_float(buffer, - (const int16_t *)src, src_size, - dest_size_r); - - case SampleFormat::S24_P32: - return pcm_allocate_24p32_to_float(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int16_t>::FromVoid(src)); case SampleFormat::S32: return pcm_allocate_32_to_float(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); + + case SampleFormat::S24_P32: + return pcm_allocate_24p32_to_float(buffer, + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::FLOAT: - *dest_size_r = src_size; - return (const float *)src; + return ConstBuffer<float>::FromVoid(src); } return nullptr; diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx index cc44d6dd5..da182e771 100644 --- a/src/pcm/PcmFormat.hxx +++ b/src/pcm/PcmFormat.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 @@ -25,6 +25,7 @@ #include <stdint.h> #include <stddef.h> +template<typename T> struct ConstBuffer; class PcmBuffer; class PcmDither; @@ -36,14 +37,12 @@ class PcmDither; * @param dither a pcm_dither object for 24-to-16 conversion * @param bits the number of in the source buffer * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const int16_t * +gcc_pure +ConstBuffer<int16_t> pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); /** * Converts PCM samples to 24 bit (32 bit alignment). @@ -51,14 +50,12 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, * @param buffer a PcmBuffer object * @param bits the number of in the source buffer * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const int32_t * +gcc_pure +ConstBuffer<int32_t> pcm_convert_to_24(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); /** * Converts PCM samples to 32 bit. @@ -66,14 +63,12 @@ pcm_convert_to_24(PcmBuffer &buffer, * @param buffer a PcmBuffer object * @param bits the number of in the source buffer * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const int32_t * +gcc_pure +ConstBuffer<int32_t> pcm_convert_to_32(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); /** * Converts PCM samples to 32 bit floating point. @@ -85,9 +80,9 @@ pcm_convert_to_32(PcmBuffer &buffer, * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const float * +gcc_pure +ConstBuffer<float> pcm_convert_to_float(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); #endif diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx index 001794061..d21b5f04b 100644 --- a/src/pcm/PcmMix.cxx +++ b/src/pcm/PcmMix.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,42 +19,56 @@ #include "config.h" #include "PcmMix.hxx" -#include "PcmVolume.hxx" +#include "Volume.hxx" #include "PcmUtils.hxx" #include "AudioFormat.hxx" +#include "Traits.hxx" +#include "util/Clamp.hxx" +#include "PcmDither.cxx" // including the .cxx file to get inlined templates + +#include <assert.h> #include <math.h> -template<typename T, typename U, unsigned bits> -static T -PcmAddVolume(T _a, T _b, int volume1, int volume2) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::value_type +PcmAddVolume(PcmDither &dither, + typename Traits::value_type _a, typename Traits::value_type _b, + int volume1, int volume2) { - U a(_a), b(_b); - - U c = ((a * volume1 + b * volume2) + - pcm_volume_dither() + PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; + typename Traits::long_type a(_a), b(_b); + typename Traits::long_type c(a * volume1 + b * volume2); - return PcmClamp<T, U, bits>(c); + return dither.DitherShift<typename Traits::long_type, + Traits::BITS + PCM_VOLUME_BITS, + Traits::BITS>(c); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void -PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2) +PcmAddVolume(PcmDither &dither, + typename Traits::pointer_type a, + typename Traits::const_pointer_type b, + size_t n, int volume1, int volume2) { for (size_t i = 0; i != n; ++i) - a[i] = PcmAddVolume<T, U, bits>(a[i], b[i], volume1, volume2); + a[i] = PcmAddVolume<F, Traits>(dither, a[i], b[i], + volume1, volume2); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void -PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2) +PcmAddVolumeVoid(PcmDither &dither, + void *a, const void *b, size_t size, int volume1, int volume2) { - constexpr size_t sample_size = sizeof(T); + constexpr size_t sample_size = Traits::SAMPLE_SIZE; assert(size % sample_size == 0); - PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size, - volume1, volume2); + PcmAddVolume<F, Traits>(dither, + typename Traits::pointer_type(a), + typename Traits::const_pointer_type(b), + size / sample_size, + volume1, volume2); } static void @@ -72,7 +86,7 @@ pcm_add_vol_float(float *buffer1, const float *buffer2, } static bool -pcm_add_vol(void *buffer1, const void *buffer2, size_t size, +pcm_add_vol(PcmDither &dither, void *buffer1, const void *buffer2, size_t size, int vol1, int vol2, SampleFormat format) { @@ -83,23 +97,27 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size, return false; case SampleFormat::S8: - PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S8>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::S16: - PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S16>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::S24_P32: - PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S24_P32>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::S32: - PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S32>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::FLOAT: @@ -114,30 +132,35 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size, gcc_unreachable(); } -template<typename T, typename U, unsigned bits> -static T -PcmAdd(T _a, T _b) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::value_type +PcmAdd(typename Traits::value_type _a, typename Traits::value_type _b) { - U a(_a), b(_b); - return PcmClamp<T, U, bits>(a + b); + typename Traits::sum_type a(_a), b(_b); + + return PcmClamp<F, Traits>(a + b); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void -PcmAdd(T *a, const T *b, unsigned n) +PcmAdd(typename Traits::pointer_type a, + typename Traits::const_pointer_type b, + size_t n) { for (size_t i = 0; i != n; ++i) - a[i] = PcmAdd<T, U, bits>(a[i], b[i]); + a[i] = PcmAdd<F, Traits>(a[i], b[i]); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void PcmAddVoid(void *a, const void *b, size_t size) { - constexpr size_t sample_size = sizeof(T); + constexpr size_t sample_size = Traits::SAMPLE_SIZE; assert(size % sample_size == 0); - PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size); + PcmAdd<F, Traits>(typename Traits::pointer_type(a), + typename Traits::const_pointer_type(b), + size / sample_size); } static void @@ -162,19 +185,19 @@ pcm_add(void *buffer1, const void *buffer2, size_t size, return false; case SampleFormat::S8: - PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S8>(buffer1, buffer2, size); return true; case SampleFormat::S16: - PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S16>(buffer1, buffer2, size); return true; case SampleFormat::S24_P32: - PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S24_P32>(buffer1, buffer2, size); return true; case SampleFormat::S32: - PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S32>(buffer1, buffer2, size); return true; case SampleFormat::FLOAT: @@ -188,10 +211,9 @@ pcm_add(void *buffer1, const void *buffer2, size_t size, } bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, +pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size, SampleFormat format, float portion1) { - int vol1; float s; /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses -1 @@ -202,8 +224,9 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size, s = sin(M_PI_2 * portion1); s *= s; - vol1 = s * PCM_VOLUME_1 + 0.5; - vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); + int vol1 = s * PCM_VOLUME_1S + 0.5; + vol1 = Clamp<int>(vol1, 0, PCM_VOLUME_1S); - return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); + return pcm_add_vol(dither, buffer1, buffer2, size, + vol1, PCM_VOLUME_1S - vol1, format); } diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx index 637c88f8a..4e22a33f1 100644 --- a/src/pcm/PcmMix.hxx +++ b/src/pcm/PcmMix.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 @@ -25,6 +25,8 @@ #include <stddef.h> +class PcmDither; + /* * Linearly mixes two PCM buffers. Both must have the same length and * the same audio format. The formula is: @@ -44,7 +46,7 @@ */ gcc_warn_unused_result bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, +pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size, SampleFormat format, float portion1); #endif diff --git a/src/pcm/PcmPack.cxx b/src/pcm/PcmPack.cxx index 8920eb288..7a3379ad0 100644 --- a/src/pcm/PcmPack.cxx +++ b/src/pcm/PcmPack.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 diff --git a/src/pcm/PcmPack.hxx b/src/pcm/PcmPack.hxx index aed011767..271a3cd25 100644 --- a/src/pcm/PcmPack.hxx +++ b/src/pcm/PcmPack.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/pcm/PcmPrng.hxx b/src/pcm/PcmPrng.hxx index 0c823250d..5233caba6 100644 --- a/src/pcm/PcmPrng.hxx +++ b/src/pcm/PcmPrng.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 @@ -24,7 +24,7 @@ * A very simple linear congruential PRNG. It's good enough for PCM * dithering. */ -static unsigned long +constexpr static inline unsigned long pcm_prng(unsigned long state) { return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx deleted file mode 100644 index 01f269ea9..000000000 --- a/src/pcm/PcmResample.cxx +++ /dev/null @@ -1,173 +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 "PcmResampleInternal.hxx" - -#ifdef HAVE_LIBSAMPLERATE -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#endif - -#include <string.h> - -#ifdef HAVE_LIBSAMPLERATE -static bool lsr_enabled; -#endif - -#ifdef HAVE_LIBSAMPLERATE -static bool -pcm_resample_lsr_enabled(void) -{ - return lsr_enabled; -} -#endif - -bool -pcm_resample_global_init(Error &error) -{ -#ifdef HAVE_LIBSAMPLERATE - const char *converter = - config_get_string(CONF_SAMPLERATE_CONVERTER, ""); - - lsr_enabled = strcmp(converter, "internal") != 0; - if (lsr_enabled) - return pcm_resample_lsr_global_init(converter, error); - else - return true; -#else - (void)error; - return true; -#endif -} - -PcmResampler::PcmResampler() -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - pcm_resample_lsr_init(this); -#endif -} - -PcmResampler::~PcmResampler() -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - pcm_resample_lsr_deinit(this); -#endif -} - -void -PcmResampler::Reset() -{ -#ifdef HAVE_LIBSAMPLERATE - pcm_resample_lsr_reset(this); -#endif -} - -const float * -PcmResampler::ResampleFloat(unsigned channels, unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - return pcm_resample_lsr_float(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error_r); -#else - (void)error_r; -#endif - - /* sizeof(float)==sizeof(int32_t); the fallback resampler does - not do any math on the sample values, so this hack is - possible: */ - return (const float *) - pcm_resample_fallback_32(this, channels, - src_rate, (const int32_t *)src_buffer, - src_size, - dest_rate, dest_size_r); -} - -const int16_t * -PcmResampler::Resample16(unsigned channels, - unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - return pcm_resample_lsr_16(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error_r); -#else - (void)error_r; -#endif - - return pcm_resample_fallback_16(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r); -} - -const int32_t * -PcmResampler::Resample32(unsigned channels, unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - return pcm_resample_lsr_32(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error_r); -#else - (void)error_r; -#endif - - return pcm_resample_fallback_32(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r); -} - -const int32_t * -PcmResampler::Resample24(unsigned channels, unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - return pcm_resample_lsr_24(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error_r); -#else - (void)error_r; -#endif - - /* reuse the 32 bit code - the resampler code doesn't care if - the upper 8 bits are actually used */ - return pcm_resample_fallback_32(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r); -} diff --git a/src/pcm/PcmResample.hxx b/src/pcm/PcmResample.hxx deleted file mode 100644 index e839d6ecd..000000000 --- a/src/pcm/PcmResample.hxx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_RESAMPLE_HXX -#define MPD_PCM_RESAMPLE_HXX - -#include "check.h" -#include "PcmBuffer.hxx" - -#include <stdint.h> -#include <stddef.h> - -#ifdef HAVE_LIBSAMPLERATE -#include <samplerate.h> -#endif - -class Error; - -/** - * This object is statically allocated (within another struct), and - * holds buffer allocations and the state for the resampler. - */ -struct PcmResampler { -#ifdef HAVE_LIBSAMPLERATE - SRC_STATE *state; - SRC_DATA data; - - PcmBuffer in, out; - - struct { - unsigned src_rate; - unsigned dest_rate; - unsigned channels; - } prev; - - int error; -#endif - - PcmBuffer buffer; - - PcmResampler(); - ~PcmResampler(); - - /** - * @see pcm_convert_reset() - */ - void Reset(); - - /** - * Resamples 32 bit float data. - * - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ - const float *ResampleFloat(unsigned channels, unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r); - - /** - * Resamples 16 bit PCM data. - * - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ - const int16_t *Resample16(unsigned channels, unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r); - - /** - * Resamples 32 bit PCM data. - * - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ - const int32_t *Resample32(unsigned channels, unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r); - - /** - * Resamples 24 bit PCM data. - * - * @param channels the number of channels - * @param src_rate the source sample rate - * @param src the source PCM buffer - * @param src_size the size of #src in bytes - * @param dest_rate the requested destination sample rate - * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer - */ - const int32_t *Resample24(unsigned channels, unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error_r); -}; - -bool -pcm_resample_global_init(Error &error); - -#endif diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx deleted file mode 100644 index a62cd64f7..000000000 --- a/src/pcm/PcmResampleFallback.cxx +++ /dev/null @@ -1,106 +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 "PcmResampleInternal.hxx" - -#include <assert.h> - -/* resampling code blatantly ripped from ESD */ -const int16_t * -pcm_resample_fallback_16(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) -{ - unsigned dest_pos = 0; - unsigned src_frames = src_size / channels / sizeof(*src_buffer); - unsigned dest_frames = - (src_frames * dest_rate + src_rate - 1) / src_rate; - unsigned dest_samples = dest_frames * channels; - size_t dest_size = dest_samples * sizeof(*src_buffer); - int16_t *dest_buffer = (int16_t *)state->buffer.Get(dest_size); - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - switch (channels) { - case 1: - while (dest_pos < dest_samples) { - unsigned src_pos = dest_pos * src_rate / dest_rate; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - } - break; - case 2: - while (dest_pos < dest_samples) { - unsigned src_pos = dest_pos * src_rate / dest_rate; - src_pos &= ~1; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - dest_buffer[dest_pos++] = src_buffer[src_pos + 1]; - } - break; - } - - *dest_size_r = dest_size; - return dest_buffer; -} - -const int32_t * -pcm_resample_fallback_32(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) -{ - unsigned dest_pos = 0; - unsigned src_frames = src_size / channels / sizeof(*src_buffer); - unsigned dest_frames = - (src_frames * dest_rate + src_rate - 1) / src_rate; - unsigned dest_samples = dest_frames * channels; - size_t dest_size = dest_samples * sizeof(*src_buffer); - int32_t *dest_buffer = (int32_t *)state->buffer.Get(dest_size); - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - switch (channels) { - case 1: - while (dest_pos < dest_samples) { - unsigned src_pos = dest_pos * src_rate / dest_rate; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - } - break; - case 2: - while (dest_pos < dest_samples) { - unsigned src_pos = dest_pos * src_rate / dest_rate; - src_pos &= ~1; - - dest_buffer[dest_pos++] = src_buffer[src_pos]; - dest_buffer[dest_pos++] = src_buffer[src_pos + 1]; - } - break; - } - - *dest_size_r = dest_size; - return dest_buffer; -} diff --git a/src/pcm/PcmResampleInternal.hxx b/src/pcm/PcmResampleInternal.hxx deleted file mode 100644 index 5090c13d1..000000000 --- a/src/pcm/PcmResampleInternal.hxx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Internal declarations for the pcm_resample library. The "internal" - * resampler is called "fallback" in the MPD source, so the file name - * of this header is somewhat unrelated to it. - */ - -#ifndef MPD_PCM_RESAMPLE_INTERNAL_HXX -#define MPD_PCM_RESAMPLE_INTERNAL_HXX - -#include "check.h" -#include "PcmResample.hxx" - -#ifdef HAVE_LIBSAMPLERATE - -bool -pcm_resample_lsr_global_init(const char *converter, Error &error); - -void -pcm_resample_lsr_init(PcmResampler *state); - -void -pcm_resample_lsr_deinit(PcmResampler *state); - -void -pcm_resample_lsr_reset(PcmResampler *state); - -const float * -pcm_resample_lsr_float(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error); - -const int16_t * -pcm_resample_lsr_16(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error); - -const int32_t * -pcm_resample_lsr_32(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, - size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error); - -const int32_t * -pcm_resample_lsr_24(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, - size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error); - -#endif - -const int16_t * -pcm_resample_fallback_16(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); - -const int32_t * -pcm_resample_fallback_32(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, - size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); - -#endif diff --git a/src/pcm/PcmResampleLibsamplerate.cxx b/src/pcm/PcmResampleLibsamplerate.cxx deleted file mode 100644 index 9eac2d545..000000000 --- a/src/pcm/PcmResampleLibsamplerate.cxx +++ /dev/null @@ -1,310 +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 "PcmResampleInternal.hxx" -#include "PcmUtils.hxx" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -static int lsr_converter = SRC_SINC_FASTEST; - -static constexpr Domain libsamplerate_domain("libsamplerate"); - -static bool -lsr_parse_converter(const char *s) -{ - assert(s != nullptr); - - if (*s == 0) - return true; - - char *endptr; - long l = strtol(s, &endptr, 10); - if (*endptr == 0 && src_get_name(l) != nullptr) { - lsr_converter = l; - return true; - } - - size_t length = strlen(s); - for (int i = 0;; ++i) { - const char *name = src_get_name(i); - if (name == nullptr) - break; - - if (StringEqualsCaseASCII(s, name, length)) { - lsr_converter = i; - return true; - } - } - - return false; -} - -bool -pcm_resample_lsr_global_init(const char *converter, Error &error) -{ - if (!lsr_parse_converter(converter)) { - error.Format(libsamplerate_domain, - "unknown samplerate converter '%s'", converter); - return false; - } - - FormatDebug(libsamplerate_domain, - "libsamplerate converter '%s'", - src_get_name(lsr_converter)); - - return true; -} - -void -pcm_resample_lsr_init(PcmResampler *state) -{ - state->state = nullptr; - memset(&state->data, 0, sizeof(state->data)); - memset(&state->prev, 0, sizeof(state->prev)); - state->error = 0; -} - -void -pcm_resample_lsr_deinit(PcmResampler *state) -{ - if (state->state != nullptr) - state->state = src_delete(state->state); -} - -void -pcm_resample_lsr_reset(PcmResampler *state) -{ - if (state->state != nullptr) - src_reset(state->state); -} - -static bool -pcm_resample_set(PcmResampler *state, - unsigned channels, unsigned src_rate, unsigned dest_rate, - Error &error_r) -{ - /* (re)set the state/ratio if the in or out format changed */ - if (channels == state->prev.channels && - src_rate == state->prev.src_rate && - dest_rate == state->prev.dest_rate) - return true; - - state->error = 0; - state->prev.channels = channels; - state->prev.src_rate = src_rate; - state->prev.dest_rate = dest_rate; - - if (state->state) - state->state = src_delete(state->state); - - int error; - state->state = src_new(lsr_converter, channels, &error); - if (!state->state) { - error_r.Format(libsamplerate_domain, error, - "libsamplerate initialization has failed: %s", - src_strerror(error)); - return false; - } - - SRC_DATA *data = &state->data; - data->src_ratio = (double)dest_rate / (double)src_rate; - FormatDebug(libsamplerate_domain, - "setting samplerate conversion ratio to %.2lf", - data->src_ratio); - src_set_ratio(state->state, data->src_ratio); - - return true; -} - -static bool -lsr_process(PcmResampler *state, Error &error) -{ - if (state->error == 0) - state->error = src_process(state->state, &state->data); - if (state->error) { - error.Format(libsamplerate_domain, state->error, - "libsamplerate has failed: %s", - src_strerror(state->error)); - return false; - } - - return true; -} - -const float * -pcm_resample_lsr_float(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const float *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error) -{ - SRC_DATA *data = &state->data; - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - if (!pcm_resample_set(state, channels, src_rate, dest_rate, error)) - return nullptr; - - data->input_frames = src_size / sizeof(*src_buffer) / channels; - data->data_in = const_cast<float *>(src_buffer); - - data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; - size_t data_out_size = data->output_frames * sizeof(float) * channels; - data->data_out = (float *)state->out.Get(data_out_size); - - if (!lsr_process(state, error)) - return nullptr; - - *dest_size_r = data->output_frames_gen * - sizeof(*data->data_out) * channels; - return data->data_out; -} - -const int16_t * -pcm_resample_lsr_16(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error) -{ - SRC_DATA *data = &state->data; - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - if (!pcm_resample_set(state, channels, src_rate, dest_rate, - error)) - return nullptr; - - data->input_frames = src_size / sizeof(*src_buffer) / channels; - size_t data_in_size = data->input_frames * sizeof(float) * channels; - data->data_in = (float *)state->in.Get(data_in_size); - - data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; - size_t data_out_size = data->output_frames * sizeof(float) * channels; - data->data_out = (float *)state->out.Get(data_out_size); - - src_short_to_float_array(src_buffer, data->data_in, - data->input_frames * channels); - - if (!lsr_process(state, error)) - return nullptr; - - int16_t *dest_buffer; - *dest_size_r = data->output_frames_gen * - sizeof(*dest_buffer) * channels; - dest_buffer = (int16_t *)state->buffer.Get(*dest_size_r); - src_float_to_short_array(data->data_out, dest_buffer, - data->output_frames_gen * channels); - - return dest_buffer; -} - -#ifdef HAVE_LIBSAMPLERATE_NOINT - -/* libsamplerate introduced these functions in v0.1.3 */ - -static void -src_int_to_float_array(const int *in, float *out, int len) -{ - while (len-- > 0) - *out++ = *in++ / (float)(1 << (24 - 1)); -} - -static void -src_float_to_int_array (const float *in, int *out, int len) -{ - while (len-- > 0) - *out++ = *in++ * (float)(1 << (24 - 1)); -} - -#endif - -const int32_t * -pcm_resample_lsr_32(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error) -{ - SRC_DATA *data = &state->data; - - assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - - if (!pcm_resample_set(state, channels, src_rate, dest_rate, - error)) - return nullptr; - - data->input_frames = src_size / sizeof(*src_buffer) / channels; - size_t data_in_size = data->input_frames * sizeof(float) * channels; - data->data_in = (float *)state->in.Get(data_in_size); - - data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; - size_t data_out_size = data->output_frames * sizeof(float) * channels; - data->data_out = (float *)state->out.Get(data_out_size); - - src_int_to_float_array(src_buffer, data->data_in, - data->input_frames * channels); - - if (!lsr_process(state, error)) - return nullptr; - - int32_t *dest_buffer; - *dest_size_r = data->output_frames_gen * - sizeof(*dest_buffer) * channels; - dest_buffer = (int32_t *)state->buffer.Get(*dest_size_r); - src_float_to_int_array(data->data_out, dest_buffer, - data->output_frames_gen * channels); - - return dest_buffer; -} - -const int32_t * -pcm_resample_lsr_24(PcmResampler *state, - unsigned channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, size_t *dest_size_r, - Error &error) -{ - const auto result = pcm_resample_lsr_32(state, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error); - if (result != nullptr) - /* src_float_to_int_array() clamps for 32 bit - integers; now make sure everything's fine for 24 - bit */ - /* TODO: eliminate the 32 bit clamp to reduce overhead */ - PcmClampN<int32_t, int32_t, 24>(const_cast<int32_t *>(result), - result, - *dest_size_r / sizeof(*result)); - - return result; -} diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx index febe12d7b..23870a729 100644 --- a/src/pcm/PcmUtils.hxx +++ b/src/pcm/PcmUtils.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 @@ -26,53 +26,31 @@ #include <stdint.h> -/** - * Add a byte count to the specified pointer. This is a utility - * function to convert a source pointer and a byte count to an "end" - * pointer for use in loops. - */ -template<typename T> -static inline const T * -pcm_end_pointer(const T *p, size_t size) -{ - return (const T *)((const uint8_t *)p + size); -} +enum class SampleFormat : uint8_t; +template<SampleFormat F> struct SampleTraits; /** * Check if the value is within the range of the provided bit size, * and caps it if necessary. */ -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> gcc_const -static inline T -PcmClamp(U x) +static inline typename Traits::value_type +PcmClamp(typename Traits::long_type x) { - constexpr U MIN_VALUE = -(U(1) << (bits - 1)); - constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1; + typedef typename Traits::value_type T; typedef std::numeric_limits<T> limits; - static_assert(MIN_VALUE >= limits::min(), "out of range"); - static_assert(MAX_VALUE <= limits::max(), "out of range"); + static_assert(Traits::MIN >= limits::min(), "out of range"); + static_assert(Traits::MAX <= limits::max(), "out of range"); - if (gcc_unlikely(x < MIN_VALUE)) - return T(MIN_VALUE); + if (gcc_unlikely(x < Traits::MIN)) + return T(Traits::MIN); - if (gcc_unlikely(x > MAX_VALUE)) - return T(MAX_VALUE); + if (gcc_unlikely(x > Traits::MAX)) + return T(Traits::MAX); return T(x); } -/** - * Check if the values in this buffer are within the range of the - * provided bit size, and clamps them whenever necessary. - */ -template<typename T, typename U, unsigned bits> -static inline void -PcmClampN(T *dest, const U *src, unsigned n) -{ - while (n-- > 0) - *dest++ = PcmClamp<T, U, bits>(*src++); -} - #endif diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx deleted file mode 100644 index 564880633..000000000 --- a/src/pcm/PcmVolume.cxx +++ /dev/null @@ -1,188 +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 "PcmVolume.hxx" -#include "PcmUtils.hxx" -#include "AudioFormat.hxx" - -#include <stdint.h> -#include <string.h> - -static void -pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) -{ - while (buffer < end) { - int32_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample); - } -} - -static void -pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) -{ - while (buffer < end) { - int32_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - - *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample); - } -} - -#ifdef __i386__ -/** - * Optimized volume function for i386. Use the EDX:EAX 2*32 bit - * multiplication result instead of emulating 64 bit multiplication. - */ -static inline int32_t -pcm_volume_sample_24(int32_t sample, int32_t volume, gcc_unused int32_t dither) -{ - int32_t result; - - asm(/* edx:eax = sample * volume */ - "imul %2\n" - - /* "add %3, %1\n" dithering disabled for now, because we - have no overflow check - is dithering really important - here? */ - - /* eax = edx:eax / PCM_VOLUME_1 */ - "sal $22, %%edx\n" - "shr $10, %1\n" - "or %%edx, %1\n" - - : "=a"(result) - : "0"(sample), "r"(volume) /* , "r"(dither) */ - : "edx" - ); - - return result; -} -#endif - -static void -pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) -{ - while (buffer < end) { -#ifdef __i386__ - /* assembly version for i386 */ - int32_t sample = *buffer; - - sample = pcm_volume_sample_24(sample, volume, - pcm_volume_dither()); -#else - /* portable version */ - int64_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; -#endif - *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample); - } -} - -static void -pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) -{ - while (buffer < end) { -#ifdef __i386__ - /* assembly version for i386 */ - int32_t sample = *buffer; - - *buffer++ = pcm_volume_sample_24(sample, volume, 0); -#else - /* portable version */ - int64_t sample = *buffer; - - sample = (sample * volume + pcm_volume_dither() + - PCM_VOLUME_1 / 2) - / PCM_VOLUME_1; - *buffer++ = PcmClamp<int32_t, int64_t, 32>(sample); -#endif - } -} - -static void -pcm_volume_change_float(float *buffer, const float *end, float volume) -{ - while (buffer < end) { - float sample = *buffer; - sample *= volume; - *buffer++ = sample; - } -} - -bool -pcm_volume(void *buffer, size_t length, - SampleFormat format, - int volume) -{ - if (volume == PCM_VOLUME_1) - return true; - - if (volume <= 0) { - memset(buffer, 0, length); - return true; - } - - const void *end = pcm_end_pointer(buffer, length); - switch (format) { - case SampleFormat::UNDEFINED: - case SampleFormat::DSD: - /* not implemented */ - return false; - - case SampleFormat::S8: - pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end, - volume); - return true; - - case SampleFormat::S16: - pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end, - volume); - return true; - - case SampleFormat::S24_P32: - pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end, - volume); - return true; - - case SampleFormat::S32: - pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end, - volume); - return true; - - case SampleFormat::FLOAT: - pcm_volume_change_float((float *)buffer, (const float *)end, - pcm_volume_to_float(volume)); - return true; - } - - assert(false); - gcc_unreachable(); -} diff --git a/src/pcm/PcmVolume.hxx b/src/pcm/PcmVolume.hxx deleted file mode 100644 index 8cd82acf7..000000000 --- a/src/pcm/PcmVolume.hxx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PCM_VOLUME_HXX -#define MPD_PCM_VOLUME_HXX - -#include "PcmPrng.hxx" -#include "AudioFormat.hxx" - -#include <stdint.h> -#include <stddef.h> - -enum { - /** this value means "100% volume" */ - PCM_VOLUME_1 = 1024, -}; - -struct AudioFormat; - -/** - * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an - * integer volume value (1000 = 100%). - */ -static inline int -pcm_float_to_volume(float volume) -{ - return volume * PCM_VOLUME_1 + 0.5; -} - -static inline float -pcm_volume_to_float(int volume) -{ - return (float)volume / (float)PCM_VOLUME_1; -} - -/** - * Returns the next volume dithering number, between -511 and +511. - * This number is taken from a global PRNG, see pcm_prng(). - */ -static inline int -pcm_volume_dither(void) -{ - static unsigned long state; - uint32_t r; - - r = state = pcm_prng(state); - - return (r & 511) - ((r >> 9) & 511); -} - -/** - * Adjust the volume of the specified PCM buffer. - * - * @param buffer the PCM buffer - * @param length the length of the PCM buffer - * @param format the sample format of the PCM buffer - * @param volume the volume between 0 and #PCM_VOLUME_1 - * @return true on success, false if the audio format is not supported - */ -bool -pcm_volume(void *buffer, size_t length, - SampleFormat format, - int volume); - -#endif diff --git a/src/pcm/Resampler.hxx b/src/pcm/Resampler.hxx new file mode 100644 index 000000000..9b6ccbbc7 --- /dev/null +++ b/src/pcm/Resampler.hxx @@ -0,0 +1,74 @@ +/* + * 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_PCM_RESAMPLER_HXX +#define MPD_PCM_RESAMPLER_HXX + +#include "util/ConstBuffer.hxx" +#include "Compiler.h" + +struct AudioFormat; +class Error; + +/** + * This is an interface for plugins that convert PCM data to a + * specific sample rate. + */ +class PcmResampler { +public: + virtual ~PcmResampler() {} + + /** + * Opens the resampler, preparing it for Resample(). + * + * @param af the audio format of incoming data; the plugin may + * modify the object to enforce another input format (however, + * it may not request a different input sample rate) + * @param new_sample_rate the requested output sample rate + * @param error location to store the error + * @return the format of outgoing data or + * AudioFormat::Undefined() on error + */ + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) = 0; + + /** + * Closes the resampler. After that, you may call Open() + * again. + */ + virtual void Close() = 0; + + /** + * Resamples a block of PCM data. + * + * @param src the input buffer + * @param src_size the size of #src_buffer in bytes + * @param dest_size_r the size of the returned buffer + * @param error location to store the error occurring, or nullptr + * to ignore errors. + * @return the destination buffer on success (will be + * invalidated by filter_close() or filter_filter()), nullptr on + * error + */ + gcc_pure + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) = 0; +}; + +#endif diff --git a/src/pcm/ShiftConvert.hxx b/src/pcm/ShiftConvert.hxx new file mode 100644 index 000000000..92f96b7ba --- /dev/null +++ b/src/pcm/ShiftConvert.hxx @@ -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. + */ + +#ifndef MPD_PCM_SHIFT_CONVERT_HXX +#define MPD_PCM_SHIFT_CONVERT_HXX + +#include "Traits.hxx" + +/** + * Convert from one integer sample format to another by shifting bits + * to the left. + */ +template<SampleFormat SF, SampleFormat DF, + class ST=SampleTraits<SF>, + class DT=SampleTraits<DF>> +struct LeftShiftSampleConvert { + typedef ST SrcTraits; + typedef DT DstTraits; + + typedef typename SrcTraits::value_type SV; + typedef typename DstTraits::value_type DV; + + static_assert(SrcTraits::BITS < DstTraits::BITS, + "Source format must be smaller than destination format"); + + constexpr static DV Convert(SV src) { + return DV(src) << (DstTraits::BITS - SrcTraits::BITS); + } +}; + +/** + * Convert from one integer sample format to another by shifting bits + * to the right. + */ +template<SampleFormat SF, SampleFormat DF, + class ST=SampleTraits<SF>, + class DT=SampleTraits<DF>> +struct RightShiftSampleConvert { + typedef ST SrcTraits; + typedef DT DstTraits; + + typedef typename SrcTraits::value_type SV; + typedef typename DstTraits::value_type DV; + + static_assert(SrcTraits::BITS > DstTraits::BITS, + "Source format must be smaller than destination format"); + + constexpr static DV Convert(SV src) { + return src >> (SrcTraits::BITS - DstTraits::BITS); + } +}; + +#endif diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx new file mode 100644 index 000000000..56b9760d5 --- /dev/null +++ b/src/pcm/SoxrResampler.cxx @@ -0,0 +1,163 @@ +/* + * 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 "SoxrResampler.hxx" +#include "AudioFormat.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <soxr.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain soxr_domain("soxr"); + +static unsigned long soxr_quality_recipe = SOXR_HQ; + +static const char * +soxr_quality_name(unsigned long recipe) +{ + switch (recipe) { + case SOXR_VHQ: + return "Very High Quality"; + case SOXR_HQ: + return "High Quality"; + case SOXR_MQ: + return "Medium Quality"; + case SOXR_LQ: + return "Low Quality"; + case SOXR_QQ: + return "Quick"; + } + + gcc_unreachable(); +} + +static bool +soxr_parse_converter(const char *converter) +{ + assert(converter != nullptr); + + assert(memcmp(converter, "soxr", 4) == 0); + if (converter[4] == '\0') + return true; + if (converter[4] != ' ') + return false; + + // converter example is "soxr very high", we want the "very high" part + const char *quality = converter + 5; + if (strcmp(quality, "very high") == 0) + soxr_quality_recipe = SOXR_VHQ; + else if (strcmp(quality, "high") == 0) + soxr_quality_recipe = SOXR_HQ; + else if (strcmp(quality, "medium") == 0) + soxr_quality_recipe = SOXR_MQ; + else if (strcmp(quality, "low") == 0) + soxr_quality_recipe = SOXR_LQ; + else if (strcmp(quality, "quick") == 0) + soxr_quality_recipe = SOXR_QQ; + else + return false; + + return true; +} + +bool +pcm_resample_soxr_global_init(const char *converter, Error &error) +{ + if (!soxr_parse_converter(converter)) { + error.Format(soxr_domain, + "unknown samplerate converter '%s'", converter); + return false; + } + + FormatDebug(soxr_domain, + "soxr converter '%s'", + soxr_quality_name(soxr_quality_recipe)); + + return true; +} + +AudioFormat +SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) +{ + assert(af.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + soxr_error_t e; + soxr_quality_spec_t quality = soxr_quality_spec(soxr_quality_recipe, 0); + soxr = soxr_create(af.sample_rate, new_sample_rate, + af.channels, &e, + nullptr, &quality, nullptr); + if (soxr == nullptr) { + error.Format(soxr_domain, + "soxr initialization has failed: %s", e); + return AudioFormat::Undefined(); + } + + FormatDebug(soxr_domain, "soxr engine '%s'", soxr_engine(soxr)); + + channels = af.channels; + + ratio = float(new_sample_rate) / float(af.sample_rate); + FormatDebug(soxr_domain, + "samplerate conversion ratio to %.2lf", + ratio); + + /* libsoxr works with floating point samples */ + af.format = SampleFormat::FLOAT; + + AudioFormat result = af; + result.sample_rate = new_sample_rate; + return result; +} + +void +SoxrPcmResampler::Close() +{ + soxr_delete(soxr); +} + +ConstBuffer<void> +SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error) +{ + const size_t frame_size = channels * sizeof(float); + assert(src.size % frame_size == 0); + + const size_t n_frames = src.size / frame_size; + + const size_t o_frames = size_t(n_frames * ratio + 0.5); + + float *output_buffer = (float *)buffer.Get(o_frames * frame_size); + + size_t i_done, o_done; + soxr_error_t e = soxr_process(soxr, src.data, n_frames, &i_done, + output_buffer, o_frames, &o_done); + if (e != nullptr) { + error.Format(soxr_domain, "soxr error: %s", e); + return nullptr; + } + + return { output_buffer, o_done * frame_size }; +} diff --git a/src/pcm/SoxrResampler.hxx b/src/pcm/SoxrResampler.hxx new file mode 100644 index 000000000..efe9bd5cb --- /dev/null +++ b/src/pcm/SoxrResampler.hxx @@ -0,0 +1,50 @@ +/* + * 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_PCM_SOXR_RESAMPLER_HXX +#define MPD_PCM_SOXR_RESAMPLER_HXX + +#include "Resampler.hxx" +#include "PcmBuffer.hxx" + +struct AudioFormat; + +/** + * A resampler using soxr. + */ +class SoxrPcmResampler final : public PcmResampler { + struct soxr *soxr; + + unsigned channels; + float ratio; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) override; +}; + +bool +pcm_resample_soxr_global_init(const char *converter, Error &error); + +#endif diff --git a/src/pcm/Traits.hxx b/src/pcm/Traits.hxx new file mode 100644 index 000000000..97259ac73 --- /dev/null +++ b/src/pcm/Traits.hxx @@ -0,0 +1,153 @@ +/* + * 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_PCM_TRAITS_HXX +#define MPD_PCM_TRAITS_HXX + +#include "check.h" +#include "AudioFormat.hxx" + +#include <stdint.h> +#include <stddef.h> + +/** + * This template describes the specified #SampleFormat. This is an + * empty prototype; the specializations contain the real definitions. + * See SampleTraits<uint8_t> for more documentation. + */ +template<SampleFormat F> +struct SampleTraits {}; + +template<> +struct SampleTraits<SampleFormat::S8> { + /** + * The type used for one sample value. + */ + typedef int8_t value_type; + + /** + * A writable pointer. + */ + typedef value_type *pointer_type; + + /** + * A read-only pointer. + */ + typedef const value_type *const_pointer_type; + + /** + * A "long" type that is large and accurate enough for adding + * two values without risking an (integer) overflow or + * (floating point) precision loss. + */ + typedef int sum_type; + + /** + * A "long" type that is large and accurate enough for + * arithmetic without risking an (integer) overflow or + * (floating point) precision loss. + */ + typedef int_least32_t long_type; + + /** + * The size of one sample in bytes. + */ + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + + /** + * The integer bit depth of one sample. This attribute may + * not exist if this is not an integer sample format. + */ + static constexpr unsigned BITS = sizeof(value_type) * 8; + + /** + * The minimum sample value. + */ + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + + /** + * The maximum sample value. + */ + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::S16> { + typedef int16_t value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef int_least32_t sum_type; + typedef int_least32_t long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + static constexpr unsigned BITS = sizeof(value_type) * 8; + + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::S32> { + typedef int32_t value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef int_least64_t sum_type; + typedef int_least64_t long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + static constexpr unsigned BITS = sizeof(value_type) * 8; + + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::S24_P32> { + typedef int32_t value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef int_least32_t sum_type; + typedef int_least64_t long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + static constexpr unsigned BITS = 24; + + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::FLOAT> { + typedef float value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef float sum_type; + typedef float long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + + static constexpr value_type MIN = -1; + static constexpr value_type MAX = 1; +}; + +#endif diff --git a/src/pcm/Volume.cxx b/src/pcm/Volume.cxx new file mode 100644 index 000000000..b12d8fd41 --- /dev/null +++ b/src/pcm/Volume.cxx @@ -0,0 +1,189 @@ +/* + * 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 "Volume.hxx" +#include "Domain.hxx" +#include "PcmUtils.hxx" +#include "Traits.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" + +#include "PcmDither.cxx" // including the .cxx file to get inlined templates + +#include <stdint.h> +#include <string.h> + +template<SampleFormat F, class Traits=SampleTraits<F>> +static inline typename Traits::value_type +pcm_volume_sample(PcmDither &dither, + typename Traits::value_type _sample, + int volume) +{ + typename Traits::long_type sample(_sample); + + return dither.DitherShift<typename Traits::long_type, + Traits::BITS + PCM_VOLUME_BITS, + Traits::BITS>(sample * volume); +} + +template<SampleFormat F, class Traits=SampleTraits<F>> +static void +pcm_volume_change(PcmDither &dither, + typename Traits::pointer_type dest, + typename Traits::const_pointer_type src, + size_t n, + int volume) +{ + for (size_t i = 0; i != n; ++i) + dest[i] = pcm_volume_sample<F, Traits>(dither, src[i], volume); +} + +static void +pcm_volume_change_8(PcmDither &dither, + int8_t *dest, const int8_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S8>(dither, dest, src, n, volume); +} + +static void +pcm_volume_change_16(PcmDither &dither, + int16_t *dest, const int16_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S16>(dither, dest, src, n, volume); +} + +static void +pcm_volume_change_24(PcmDither &dither, + int32_t *dest, const int32_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S24_P32>(dither, dest, src, n, + volume); +} + +static void +pcm_volume_change_32(PcmDither &dither, + int32_t *dest, const int32_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S32>(dither, dest, src, n, volume); +} + +static void +pcm_volume_change_float(float *dest, const float *src, size_t n, + float volume) +{ + for (size_t i = 0; i != n; ++i) + dest[i] = src[i] * volume; +} + +bool +PcmVolume::Open(SampleFormat _format, Error &error) +{ + assert(format == SampleFormat::UNDEFINED); + + switch (_format) { + case SampleFormat::UNDEFINED: + error.Format(pcm_domain, + "Software volume for %s is not implemented", + sample_format_to_string(_format)); + return false; + + case SampleFormat::S8: + case SampleFormat::S16: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + break; + + case SampleFormat::DSD: + // TODO: implement this; currently, it's a no-op + break; + } + + format = _format; + return true; +} + +ConstBuffer<void> +PcmVolume::Apply(ConstBuffer<void> src) +{ + if (volume == PCM_VOLUME_1) + return src; + + void *data = buffer.Get(src.size); + + if (volume == 0) { + /* optimized special case: 0% volume = memset(0) */ + /* TODO: is this valid for all sample formats? What + about floating point? */ + memset(data, 0, src.size); + return { data, src.size }; + } + + switch (format) { + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + + case SampleFormat::S8: + pcm_volume_change_8(dither, (int8_t *)data, + (const int8_t *)src.data, + src.size / sizeof(int8_t), + volume); + break; + + case SampleFormat::S16: + pcm_volume_change_16(dither, (int16_t *)data, + (const int16_t *)src.data, + src.size / sizeof(int16_t), + volume); + break; + + case SampleFormat::S24_P32: + pcm_volume_change_24(dither, (int32_t *)data, + (const int32_t *)src.data, + src.size / sizeof(int32_t), + volume); + break; + + case SampleFormat::S32: + pcm_volume_change_32(dither, (int32_t *)data, + (const int32_t *)src.data, + src.size / sizeof(int32_t), + volume); + break; + + case SampleFormat::FLOAT: + pcm_volume_change_float((float *)data, + (const float *)src.data, + src.size / sizeof(float), + pcm_volume_to_float(volume)); + break; + + case SampleFormat::DSD: + // TODO: implement this; currently, it's a no-op + return src; + } + + return { data, src.size }; +} diff --git a/src/pcm/Volume.hxx b/src/pcm/Volume.hxx new file mode 100644 index 000000000..a156fc72e --- /dev/null +++ b/src/pcm/Volume.hxx @@ -0,0 +1,130 @@ +/* + * 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_PCM_VOLUME_HXX +#define MPD_PCM_VOLUME_HXX + +#include "AudioFormat.hxx" +#include "PcmBuffer.hxx" +#include "PcmDither.hxx" + +#include <stdint.h> +#include <stddef.h> + +#ifndef NDEBUG +#include <assert.h> +#endif + +class Error; +template<typename T> struct ConstBuffer; + +/** + * Number of fractional bits for a fixed-point volume value. + */ +static constexpr unsigned PCM_VOLUME_BITS = 10; + +/** + * This value means "100% volume". + */ +static constexpr unsigned PCM_VOLUME_1 = 1024; +static constexpr int PCM_VOLUME_1S = PCM_VOLUME_1; + +struct AudioFormat; + +/** + * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an + * integer volume value (1000 = 100%). + */ +static inline int +pcm_float_to_volume(float volume) +{ + return volume * PCM_VOLUME_1 + 0.5; +} + +static inline float +pcm_volume_to_float(int volume) +{ + return (float)volume / (float)PCM_VOLUME_1; +} + +/** + * A class that converts samples from one format to another. + */ +class PcmVolume { + SampleFormat format; + + unsigned volume; + + PcmBuffer buffer; + PcmDither dither; + +public: + PcmVolume() + :volume(PCM_VOLUME_1) { +#ifndef NDEBUG + format = SampleFormat::UNDEFINED; +#endif + } + +#ifndef NDEBUG + ~PcmVolume() { + assert(format == SampleFormat::UNDEFINED); + } +#endif + + unsigned GetVolume() const { + return volume; + } + + /** + * @param _volume the volume level in the range + * [0..#PCM_VOLUME_1]; may be bigger than #PCM_VOLUME_1, but + * then it will most likely clip a lot + */ + void SetVolume(unsigned _volume) { + volume = _volume; + } + + /** + * Opens the object, prepare for Apply(). + * + * @param format the sample format + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat format, Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close() { +#ifndef NDEBUG + assert(format != SampleFormat::UNDEFINED); + format = SampleFormat::UNDEFINED; +#endif + } + + /** + * Apply the volume level. + */ + gcc_pure + ConstBuffer<void> Apply(ConstBuffer<void> src); +}; + +#endif diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx deleted file mode 100644 index 94198b8c3..000000000 --- a/src/playlist/AsxPlaylistPlugin.cxx +++ /dev/null @@ -1,286 +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 "AsxPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static constexpr Domain asx_domain("asx"); - -/** - * This is the state object for the GLib XML parser. - */ -struct AsxParser { - /** - * The list of songs (in reverse order because that's faster - * while adding). - */ - std::forward_list<SongPointer> songs; - - /** - * The current position in the XML file. - */ - enum { - ROOT, ENTRY, - } state; - - /** - * The current tag within the "entry" element. This is only - * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there - * is no (known) tag. - */ - TagType tag; - - /** - * The current song. It is allocated after the "location" - * element. - */ - Song *song; - - AsxParser() - :state(ROOT) {} - -}; - -static const gchar * -get_attribute(const gchar **attribute_names, const gchar **attribute_values, - const gchar *name) -{ - for (unsigned i = 0; attribute_names[i] != nullptr; ++i) - if (StringEqualsCaseASCII(attribute_names[i], name)) - return attribute_values[i]; - - return nullptr; -} - -static void -asx_start_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, gcc_unused GError **error) -{ - AsxParser *parser = (AsxParser *)user_data; - - switch (parser->state) { - case AsxParser::ROOT: - if (StringEqualsCaseASCII(element_name, "entry")) { - parser->state = AsxParser::ENTRY; - parser->song = Song::NewRemote("asx:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case AsxParser::ENTRY: - if (StringEqualsCaseASCII(element_name, "ref")) { - const gchar *href = get_attribute(attribute_names, - attribute_values, - "href"); - if (href != nullptr) { - /* create new song object, and copy - the existing tag over; we cannot - replace the existing song's URI, - because that attribute is - immutable */ - Song *song = Song::NewRemote(href); - - if (parser->song != nullptr) { - song->tag = parser->song->tag; - parser->song->tag = nullptr; - parser->song->Free(); - } - - parser->song = song; - } - } else if (StringEqualsCaseASCII(element_name, "author")) - /* is that correct? or should it be COMPOSER - or PERFORMER? */ - parser->tag = TAG_ARTIST; - else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; - - break; - } -} - -static void -asx_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) -{ - AsxParser *parser = (AsxParser *)user_data; - - switch (parser->state) { - case AsxParser::ROOT: - break; - - case AsxParser::ENTRY: - if (StringEqualsCaseASCII(element_name, "entry")) { - if (strcmp(parser->song->uri, "asx:") != 0) - parser->songs.emplace_front(parser->song); - else - parser->song->Free(); - - parser->state = AsxParser::ROOT; - } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; - - break; - } -} - -static void -asx_text(gcc_unused GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, gcc_unused GError **error) -{ - AsxParser *parser = (AsxParser *)user_data; - - switch (parser->state) { - case AsxParser::ROOT: - break; - - case AsxParser::ENTRY: - if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == nullptr) - parser->song->tag = new Tag(); - parser->song->tag->AddItem(parser->tag, - text, text_len); - } - - break; - } -} - -static const GMarkupParser asx_parser = { - asx_start_element, - asx_end_element, - asx_text, - nullptr, - nullptr, -}; - -static void -asx_parser_destroy(gpointer data) -{ - AsxParser *parser = (AsxParser *)data; - - if (parser->state >= AsxParser::ENTRY) - parser->song->Free(); -} - -/* - * The playlist object - * - */ - -static SongEnumerator * -asx_open_stream(InputStream &is) -{ - AsxParser parser; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - Error error2; - GError *error = nullptr; - - /* parse the ASX XML file */ - - context = g_markup_parse_context_new(&asx_parser, - G_MARKUP_TREAT_CDATA_AS_TEXT, - &parser, asx_parser_destroy); - - while (true) { - nbytes = is.LockRead(buffer, sizeof(buffer), error2); - if (nbytes == 0) { - if (error2.IsDefined()) { - g_markup_parse_context_free(context); - LogError(error2); - return nullptr; - } - - break; - } - - success = g_markup_parse_context_parse(context, buffer, nbytes, - &error); - if (!success) { - FormatErrno(asx_domain, - "XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return nullptr; - } - } - - success = g_markup_parse_context_end_parse(context, &error); - if (!success) { - FormatErrno(asx_domain, - "XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return nullptr; - } - - parser.songs.reverse(); - MemorySongEnumerator *playlist = - new MemorySongEnumerator(std::move(parser.songs)); - - g_markup_parse_context_free(context); - - return playlist; -} - -static const char *const asx_suffixes[] = { - "asx", - nullptr -}; - -static const char *const asx_mime_types[] = { - "video/x-ms-asf", - nullptr -}; - -const struct playlist_plugin asx_playlist_plugin = { - "asx", - - nullptr, - nullptr, - nullptr, - asx_open_stream, - - nullptr, - asx_suffixes, - asx_mime_types, -}; diff --git a/src/playlist/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx deleted file mode 100644 index 240c1824a..000000000 --- a/src/playlist/AsxPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX -#define MPD_ASX_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin asx_playlist_plugin; - -#endif diff --git a/src/playlist/CloseSongEnumerator.cxx b/src/playlist/CloseSongEnumerator.cxx new file mode 100644 index 000000000..2dddef823 --- /dev/null +++ b/src/playlist/CloseSongEnumerator.cxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CloseSongEnumerator.hxx" +#include "input/InputStream.hxx" + +CloseSongEnumerator::~CloseSongEnumerator() +{ + delete other; + delete is; +} + +DetachedSong * +CloseSongEnumerator::NextSong() +{ + return other->NextSong(); +} diff --git a/src/playlist/CloseSongEnumerator.hxx b/src/playlist/CloseSongEnumerator.hxx new file mode 100644 index 000000000..17f015394 --- /dev/null +++ b/src/playlist/CloseSongEnumerator.hxx @@ -0,0 +1,47 @@ +/* + * 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_CLOSE_SONG_ENUMERATOR_HXX +#define MPD_CLOSE_SONG_ENUMERATOR_HXX + +#include "SongEnumerator.hxx" +#include "Compiler.h" + +class InputStream; + +/** + * A #SongEnumerator wrapper that closes an #InputStream automatically + * after deleting the #SongEnumerator + */ +class CloseSongEnumerator final : public SongEnumerator { + SongEnumerator *const other; + + InputStream *const is; + +public: + gcc_nonnull_all + CloseSongEnumerator(SongEnumerator *_other, InputStream *const _is) + :other(_other), is(_is) {} + + virtual ~CloseSongEnumerator(); + + virtual DetachedSong *NextSong() override; +}; + +#endif diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx deleted file mode 100644 index 42a43bbad..000000000 --- a/src/playlist/CuePlaylistPlugin.cxx +++ /dev/null @@ -1,91 +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 "CuePlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "tag/Tag.hxx" -#include "Song.hxx" -#include "cue/CueParser.hxx" -#include "TextInputStream.hxx" - -#include <assert.h> -#include <string.h> - -class CuePlaylist final : public SongEnumerator { - InputStream &is; - TextInputStream tis; - CueParser parser; - - public: - CuePlaylist(InputStream &_is) - :is(_is), tis(is) { - } - - virtual Song *NextSong() override; -}; - -static SongEnumerator * -cue_playlist_open_stream(InputStream &is) -{ - return new CuePlaylist(is); -} - -Song * -CuePlaylist::NextSong() -{ - Song *song = parser.Get(); - if (song != nullptr) - return song; - - std::string line; - while (tis.ReadLine(line)) { - parser.Feed(line.c_str()); - song = parser.Get(); - if (song != nullptr) - return song; - } - - parser.Finish(); - return parser.Get(); -} - -static const char *const cue_playlist_suffixes[] = { - "cue", - nullptr -}; - -static const char *const cue_playlist_mime_types[] = { - "application/x-cue", - nullptr -}; - -const struct playlist_plugin cue_playlist_plugin = { - "cue", - - nullptr, - nullptr, - nullptr, - cue_playlist_open_stream, - - nullptr, - cue_playlist_suffixes, - cue_playlist_mime_types, -}; diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx deleted file mode 100644 index cf5e3a8f0..000000000 --- a/src/playlist/CuePlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX -#define MPD_CUE_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin cue_playlist_plugin; - -#endif diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx deleted file mode 100644 index a1a865c08..000000000 --- a/src/playlist/DespotifyPlaylistPlugin.cxx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2011-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "DespotifyPlaylistPlugin.hxx" -#include "DespotifyUtils.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "tag/Tag.hxx" -#include "Song.hxx" -#include "Log.hxx" - -extern "C" { -#include <despotify.h> -} - -#include <string.h> -#include <stdlib.h> - -static void -add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) -{ - const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; - Song *song; - char uri[128]; - char *ds_uri; - - /* Create a spt://... URI for MPD */ - snprintf(uri, sizeof(uri), "%s://", dsp_scheme); - ds_uri = uri + strlen(dsp_scheme) + 3; - - if (despotify_track_to_uri(track, ds_uri) != ds_uri) { - /* Should never really fail, but let's be sure */ - FormatDebug(despotify_domain, - "Can't add track %s", track->title); - return; - } - - song = Song::NewRemote(uri); - song->tag = mpd_despotify_tag_from_track(track); - - songs.emplace_front(song); -} - -static bool -parse_track(struct despotify_session *session, - std::forward_list<SongPointer> &songs, - struct ds_link *link) -{ - struct ds_track *track = despotify_link_get_track(session, link); - if (track == nullptr) - return false; - - add_song(songs, track); - return true; -} - -static bool -parse_playlist(struct despotify_session *session, - std::forward_list<SongPointer> &songs, - struct ds_link *link) -{ - ds_playlist *playlist = despotify_link_get_playlist(session, link); - if (playlist == nullptr) - return false; - - for (ds_track *track = playlist->tracks; track != nullptr; - track = track->next) - add_song(songs, track); - - return true; -} - -static SongEnumerator * -despotify_playlist_open_uri(const char *url, - gcc_unused Mutex &mutex, gcc_unused Cond &cond) -{ - despotify_session *session = mpd_despotify_get_session(); - if (session == nullptr) - return nullptr; - - /* Get link without spt:// */ - ds_link *link = - despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); - if (link == nullptr) { - FormatDebug(despotify_domain, "Can't find %s\n", url); - return nullptr; - } - - std::forward_list<SongPointer> songs; - - bool parse_result; - switch (link->type) { - case LINK_TYPE_TRACK: - parse_result = parse_track(session, songs, link); - break; - case LINK_TYPE_PLAYLIST: - parse_result = parse_playlist(session, songs, link); - break; - default: - parse_result = false; - break; - } - - despotify_free_link(link); - if (!parse_result) - return nullptr; - - songs.reverse(); - return new MemorySongEnumerator(std::move(songs)); -} - -static const char *const despotify_schemes[] = { - "spt", - nullptr -}; - -const struct playlist_plugin despotify_playlist_plugin = { - "despotify", - - nullptr, - nullptr, - despotify_playlist_open_uri, - nullptr, - - despotify_schemes, - nullptr, - nullptr, -}; diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/DespotifyPlaylistPlugin.hxx deleted file mode 100644 index c1e5b7f39..000000000 --- a/src/playlist/DespotifyPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2011-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX -#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin despotify_playlist_plugin; - -#endif diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx deleted file mode 100644 index d758650eb..000000000 --- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** \file - * - * Playlist plugin that reads embedded cue sheets from the "CUESHEET" - * tag of a music file. - */ - -#include "config.h" -#include "EmbeddedCuePlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "tag/Tag.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagId3.hxx" -#include "tag/ApeTag.hxx" -#include "Song.hxx" -#include "TagFile.hxx" -#include "cue/CueParser.hxx" -#include "fs/Traits.hxx" -#include "fs/AllocatedPath.hxx" -#include "util/ASCII.hxx" - -#include <assert.h> -#include <string.h> - -class EmbeddedCuePlaylist final : public SongEnumerator { -public: - /** - * This is an override for the CUE's "FILE". An embedded CUE - * sheet must always point to the song file it is contained - * in. - */ - std::string filename; - - /** - * The value of the file's "CUESHEET" tag. - */ - std::string cuesheet; - - /** - * The offset of the next line within "cuesheet". - */ - char *next; - - CueParser *parser; - -public: - EmbeddedCuePlaylist() - :parser(nullptr) { - } - - virtual ~EmbeddedCuePlaylist() { - delete parser; - } - - virtual Song *NextSong() override; -}; - -static void -embcue_tag_pair(const char *name, const char *value, void *ctx) -{ - EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx; - - if (playlist->cuesheet.empty() && - StringEqualsCaseASCII(name, "cuesheet")) - playlist->cuesheet = value; -} - -static const struct tag_handler embcue_tag_handler = { - nullptr, - nullptr, - embcue_tag_pair, -}; - -static SongEnumerator * -embcue_playlist_open_uri(const char *uri, - gcc_unused Mutex &mutex, - gcc_unused Cond &cond) -{ - if (!PathTraits::IsAbsoluteUTF8(uri)) - /* only local files supported */ - return nullptr; - - const auto path_fs = AllocatedPath::FromUTF8(uri); - if (path_fs.IsNull()) - return nullptr; - - const auto playlist = new EmbeddedCuePlaylist(); - - tag_file_scan(path_fs, &embcue_tag_handler, playlist); - if (playlist->cuesheet.empty()) { - tag_ape_scan2(path_fs, &embcue_tag_handler, playlist); - if (playlist->cuesheet.empty()) - tag_id3_scan(path_fs, &embcue_tag_handler, playlist); - } - - if (playlist->cuesheet.empty()) { - /* no "CUESHEET" tag found */ - delete playlist; - return nullptr; - } - - playlist->filename = PathTraits::GetBaseUTF8(uri); - - playlist->next = &playlist->cuesheet[0]; - playlist->parser = new CueParser(); - - return playlist; -} - -Song * -EmbeddedCuePlaylist::NextSong() -{ - Song *song = parser->Get(); - if (song != nullptr) - return song; - - while (*next != 0) { - const char *line = next; - char *eol = strpbrk(next, "\r\n"); - if (eol != nullptr) { - /* null-terminate the line */ - *eol = 0; - next = eol + 1; - } else - /* last line; put the "next" pointer to the - end of the buffer */ - next += strlen(line); - - parser->Feed(line); - song = parser->Get(); - if (song != nullptr) - return song->ReplaceURI(filename.c_str()); - } - - parser->Finish(); - song = parser->Get(); - if (song != nullptr) - song = song->ReplaceURI(filename.c_str()); - return song; -} - -static const char *const embcue_playlist_suffixes[] = { - /* a few codecs that are known to be supported; there are - probably many more */ - "flac", - "mp3", "mp2", - "mp4", "mp4a", "m4b", - "ape", - "wv", - "ogg", "oga", - nullptr -}; - -const struct playlist_plugin embcue_playlist_plugin = { - "cue", - - nullptr, - nullptr, - embcue_playlist_open_uri, - nullptr, - - embcue_playlist_suffixes, - nullptr, - nullptr, -}; diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx deleted file mode 100644 index e306730f4..000000000 --- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX -#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin embcue_playlist_plugin; - -#endif diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx deleted file mode 100644 index 8d260fec7..000000000 --- a/src/playlist/ExtM3uPlaylistPlugin.cxx +++ /dev/null @@ -1,157 +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 "ExtM3uPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "util/StringUtil.hxx" -#include "TextInputStream.hxx" - -#include <glib.h> - -#include <string.h> -#include <stdlib.h> - -class ExtM3uPlaylist final : public SongEnumerator { - TextInputStream tis; - -public: - ExtM3uPlaylist(InputStream &is) - :tis(is) { - } - - bool CheckFirstLine() { - std::string line; - return tis.ReadLine(line) && - strcmp(line.c_str(), "#EXTM3U") == 0; - } - - virtual Song *NextSong() override; -}; - -static SongEnumerator * -extm3u_open_stream(InputStream &is) -{ - ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is); - - if (!playlist->CheckFirstLine()) { - /* no EXTM3U header: fall back to the plain m3u - plugin */ - delete playlist; - return NULL; - } - - return playlist; -} - -/** - * Parse a EXTINF line. - * - * @param line the rest of the input line after the colon - */ -static Tag * -extm3u_parse_tag(const char *line) -{ - long duration; - char *endptr; - const char *name; - Tag *tag; - - duration = strtol(line, &endptr, 10); - if (endptr[0] != ',') - /* malformed line */ - return NULL; - - if (duration < 0) - /* 0 means unknown duration */ - duration = 0; - - name = strchug_fast(endptr + 1); - if (*name == 0 && duration == 0) - /* no information available; don't allocate a tag - object */ - return NULL; - - tag = new Tag(); - tag->time = duration; - - /* unfortunately, there is no real specification for the - EXTM3U format, so we must assume that the string after the - comma is opaque, and is just the song name*/ - if (*name != 0) - tag->AddItem(TAG_NAME, name); - - return tag; -} - -Song * -ExtM3uPlaylist::NextSong() -{ - Tag *tag = NULL; - std::string line; - const char *line_s; - Song *song; - - do { - if (!tis.ReadLine(line)) { - delete tag; - return NULL; - } - - line_s = line.c_str(); - - if (g_str_has_prefix(line_s, "#EXTINF:")) { - delete tag; - tag = extm3u_parse_tag(line_s + 8); - continue; - } - - line_s = strchug_fast(line_s); - } while (line_s[0] == '#' || *line_s == 0); - - song = Song::NewRemote(line_s); - song->tag = tag; - return song; -} - -static const char *const extm3u_suffixes[] = { - "m3u", - NULL -}; - -static const char *const extm3u_mime_types[] = { - "audio/x-mpegurl", - NULL -}; - -const struct playlist_plugin extm3u_playlist_plugin = { - "extm3u", - - nullptr, - nullptr, - nullptr, - extm3u_open_stream, - - nullptr, - extm3u_suffixes, - extm3u_mime_types, -}; diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx deleted file mode 100644 index 844fba15c..000000000 --- a/src/playlist/ExtM3uPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX -#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin extm3u_playlist_plugin; - -#endif diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx deleted file mode 100644 index 3f99bdfdf..000000000 --- a/src/playlist/M3uPlaylistPlugin.cxx +++ /dev/null @@ -1,83 +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 "M3uPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "Song.hxx" -#include "util/StringUtil.hxx" -#include "TextInputStream.hxx" - -class M3uPlaylist final : public SongEnumerator { - TextInputStream tis; - -public: - M3uPlaylist(InputStream &is) - :tis(is) { - } - - virtual Song *NextSong() override; -}; - -static SongEnumerator * -m3u_open_stream(InputStream &is) -{ - return new M3uPlaylist(is); -} - -Song * -M3uPlaylist::NextSong() -{ - std::string line; - const char *line_s; - - do { - if (!tis.ReadLine(line)) - return nullptr; - - line_s = line.c_str(); - line_s = strchug_fast(line_s); - } while (line_s[0] == '#' || *line_s == 0); - - return Song::NewRemote(line_s); -} - -static const char *const m3u_suffixes[] = { - "m3u", - nullptr -}; - -static const char *const m3u_mime_types[] = { - "audio/x-mpegurl", - nullptr -}; - -const struct playlist_plugin m3u_playlist_plugin = { - "m3u", - - nullptr, - nullptr, - nullptr, - m3u_open_stream, - - nullptr, - m3u_suffixes, - m3u_mime_types, -}; diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/M3uPlaylistPlugin.hxx deleted file mode 100644 index a2058bb29..000000000 --- a/src/playlist/M3uPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX -#define MPD_M3U_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin m3u_playlist_plugin; - -#endif diff --git a/src/playlist/MemorySongEnumerator.cxx b/src/playlist/MemorySongEnumerator.cxx new file mode 100644 index 000000000..c3127c2bf --- /dev/null +++ b/src/playlist/MemorySongEnumerator.cxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MemorySongEnumerator.hxx" + +DetachedSong * +MemorySongEnumerator::NextSong() +{ + if (songs.empty()) + return nullptr; + + auto result = new DetachedSong(std::move(songs.front())); + songs.pop_front(); + return result; +} diff --git a/src/playlist/MemorySongEnumerator.hxx b/src/playlist/MemorySongEnumerator.hxx new file mode 100644 index 000000000..e87a4f6dd --- /dev/null +++ b/src/playlist/MemorySongEnumerator.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX +#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX + +#include "SongEnumerator.hxx" +#include "DetachedSong.hxx" + +#include <forward_list> + +class MemorySongEnumerator final : public SongEnumerator { + std::forward_list<DetachedSong> songs; + +public: + MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs) + :songs(std::move(_songs)) {} + + virtual DetachedSong *NextSong() override; +}; + +#endif diff --git a/src/playlist/PlaylistAny.cxx b/src/playlist/PlaylistAny.cxx new file mode 100644 index 000000000..7093fb99a --- /dev/null +++ b/src/playlist/PlaylistAny.cxx @@ -0,0 +1,40 @@ +/* + * 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 "PlaylistAny.hxx" +#include "PlaylistStream.hxx" +#include "PlaylistMapper.hxx" +#include "util/UriUtil.hxx" + +SongEnumerator * +playlist_open_any(const char *uri, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Mutex &mutex, Cond &cond) +{ + return uri_has_scheme(uri) + ? playlist_open_remote(uri, mutex, cond) + : playlist_mapper_open(uri, +#ifdef ENABLE_DATABASE + storage, +#endif + mutex, cond); +} diff --git a/src/playlist/PlaylistAny.hxx b/src/playlist/PlaylistAny.hxx new file mode 100644 index 000000000..23b0075b6 --- /dev/null +++ b/src/playlist/PlaylistAny.hxx @@ -0,0 +1,40 @@ +/* + * 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_PLAYLIST_ANY_HXX +#define MPD_PLAYLIST_ANY_HXX + +class Mutex; +class Cond; +class SongEnumerator; +class Storage; + +/** + * Opens a playlist from the specified URI, which can be either an + * absolute remote URI (with a scheme) or a relative path to the + * music orplaylist directory. + */ +SongEnumerator * +playlist_open_any(const char *uri, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Mutex &mutex, Cond &cond); + +#endif diff --git a/src/playlist/PlaylistMapper.cxx b/src/playlist/PlaylistMapper.cxx new file mode 100644 index 000000000..c50254309 --- /dev/null +++ b/src/playlist/PlaylistMapper.cxx @@ -0,0 +1,97 @@ +/* + * 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 "PlaylistMapper.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistStream.hxx" +#include "PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "fs/AllocatedPath.hxx" +#include "storage/StorageInterface.hxx" +#include "util/UriUtil.hxx" + +#include <assert.h> + +/** + * Load a playlist from the configured playlist directory. + */ +static SongEnumerator * +playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond) +{ + assert(spl_valid_name(uri)); + + const auto path_fs = map_spl_utf8_to_fs(uri); + if (path_fs.IsNull()) + return nullptr; + + return playlist_open_path(path_fs.c_str(), mutex, cond); +} + +#ifdef ENABLE_DATABASE + +/** + * Load a playlist from the configured music directory. + */ +static SongEnumerator * +playlist_open_in_storage(const char *uri, const Storage *storage, + Mutex &mutex, Cond &cond) +{ + assert(uri_safe_local(uri)); + + if (storage == nullptr) + return nullptr; + + { + const auto path = storage->MapFS(uri); + if (!path.IsNull()) + return playlist_open_path(path.c_str(), mutex, cond); + } + + const auto uri2 = storage->MapUTF8(uri); + return playlist_open_remote(uri, mutex, cond); +} + +#endif + +SongEnumerator * +playlist_mapper_open(const char *uri, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Mutex &mutex, Cond &cond) +{ + if (spl_valid_name(uri)) { + auto playlist = playlist_open_in_playlist_dir(uri, + mutex, cond); + if (playlist != nullptr) + return playlist; + } + +#ifdef ENABLE_DATABASE + if (uri_safe_local(uri)) { + auto playlist = playlist_open_in_storage(uri, storage, + mutex, cond); + if (playlist != nullptr) + return playlist; + } +#endif + + return nullptr; +} diff --git a/src/playlist/PlaylistMapper.hxx b/src/playlist/PlaylistMapper.hxx new file mode 100644 index 000000000..29ce45083 --- /dev/null +++ b/src/playlist/PlaylistMapper.hxx @@ -0,0 +1,41 @@ +/* + * 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_PLAYLIST_MAPPER_HXX +#define MPD_PLAYLIST_MAPPER_HXX + +#include "check.h" + +class Mutex; +class Cond; +class SongEnumerator; +class Storage; + +/** + * Opens a playlist from an URI relative to the playlist or music + * directory. + */ +SongEnumerator * +playlist_mapper_open(const char *uri, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Mutex &mutex, Cond &cond); + +#endif diff --git a/src/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx new file mode 100644 index 000000000..fd779ad8d --- /dev/null +++ b/src/playlist/PlaylistPlugin.hxx @@ -0,0 +1,109 @@ +/* + * 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_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_PLUGIN_HXX + +struct config_param; +class InputStream; +struct Tag; +class Mutex; +class Cond; +class SongEnumerator; + +struct playlist_plugin { + const char *name; + + /** + * Initialize the plugin. Optional method. + * + * @param param a configuration block for this plugin, or nullptr + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const config_param ¶m); + + /** + * Deinitialize a plugin which was initialized successfully. + * Optional method. + */ + void (*finish)(void); + + /** + * Opens the playlist on the specified URI. This URI has + * either matched one of the schemes or one of the suffixes. + */ + SongEnumerator *(*open_uri)(const char *uri, + Mutex &mutex, Cond &cond); + + /** + * Opens the playlist in the specified input stream. It has + * either matched one of the suffixes or one of the MIME + * types. + */ + SongEnumerator *(*open_stream)(InputStream &is); + + const char *const*schemes; + const char *const*suffixes; + const char *const*mime_types; +}; + +/** + * Initialize a plugin. + * + * @param param a configuration block for this plugin, or nullptr if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ +static inline bool +playlist_plugin_init(const struct playlist_plugin *plugin, + const config_param ¶m) +{ + return plugin->init != nullptr + ? plugin->init(param) + : true; +} + +/** + * Deinitialize a plugin which was initialized successfully. + */ +static inline void +playlist_plugin_finish(const struct playlist_plugin *plugin) +{ + if (plugin->finish != nullptr) + plugin->finish(); +} + +static inline SongEnumerator * +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, + Mutex &mutex, Cond &cond) +{ + return plugin->open_uri(uri, mutex, cond); +} + +static inline SongEnumerator * +playlist_plugin_open_stream(const struct playlist_plugin *plugin, + InputStream &is) +{ + return plugin->open_stream(is); +} + +#endif diff --git a/src/playlist/PlaylistQueue.cxx b/src/playlist/PlaylistQueue.cxx new file mode 100644 index 000000000..b10a26172 --- /dev/null +++ b/src/playlist/PlaylistQueue.cxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistQueue.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "queue/Playlist.hxx" +#include "SongEnumerator.hxx" +#include "DetachedSong.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "fs/Traits.hxx" +#include "util/Error.hxx" + +#ifdef ENABLE_DATABASE +#include "SongLoader.hxx" +#endif + +bool +playlist_load_into_queue(const char *uri, SongEnumerator &e, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + const SongLoader &loader, + Error &error) +{ + const std::string base_uri = uri != nullptr + ? PathTraitsUTF8::GetParent(uri) + : std::string("."); + + DetachedSong *song; + for (unsigned i = 0; + i < end_index && (song = e.NextSong()) != nullptr; + ++i) { + if (i < start_index) { + /* skip songs before the start index */ + delete song; + continue; + } + + if (!playlist_check_translate_song(*song, base_uri.c_str(), + loader)) { + delete song; + continue; + } + + unsigned id = dest.AppendSong(pc, std::move(*song), error); + delete song; + if (id == 0) + return false; + } + + return true; +} + +bool +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + const SongLoader &loader, + Error &error) +{ + Mutex mutex; + Cond cond; + + auto playlist = playlist_open_any(uri, +#ifdef ENABLE_DATABASE + loader.GetStorage(), +#endif + mutex, cond); + if (playlist == nullptr) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_LIST), + "No such playlist"); + return false; + } + + bool result = + playlist_load_into_queue(uri, *playlist, + start_index, end_index, + dest, pc, loader, error); + delete playlist; + return result; +} diff --git a/src/playlist/PlaylistQueue.hxx b/src/playlist/PlaylistQueue.hxx new file mode 100644 index 000000000..28eb86fcc --- /dev/null +++ b/src/playlist/PlaylistQueue.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief Glue between playlist plugin and the play queue + */ + +#ifndef MPD_PLAYLIST_QUEUE_HXX +#define MPD_PLAYLIST_QUEUE_HXX + +#include "PlaylistError.hxx" + +class Error; +class SongLoader; +class SongEnumerator; +struct playlist; +struct PlayerControl; + +/** + * Loads the contents of a playlist and append it to the specified + * play queue. + * + * @param uri the URI of the playlist, used to resolve relative song + * URIs + * @param start_index the index of the first song + * @param end_index the index of the last song (excluding) + */ +bool +playlist_load_into_queue(const char *uri, SongEnumerator &e, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + const SongLoader &loader, + Error &error); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +bool +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + const SongLoader &loader, Error &error); + +#endif + diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx new file mode 100644 index 000000000..bc5932de3 --- /dev/null +++ b/src/playlist/PlaylistRegistry.cxx @@ -0,0 +1,282 @@ +/* + * 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 "PlaylistRegistry.hxx" +#include "PlaylistPlugin.hxx" +#include "plugins/ExtM3uPlaylistPlugin.hxx" +#include "plugins/M3uPlaylistPlugin.hxx" +#include "plugins/XspfPlaylistPlugin.hxx" +#include "plugins/DespotifyPlaylistPlugin.hxx" +#include "plugins/SoundCloudPlaylistPlugin.hxx" +#include "plugins/PlsPlaylistPlugin.hxx" +#include "plugins/AsxPlaylistPlugin.hxx" +#include "plugins/RssPlaylistPlugin.hxx" +#include "plugins/CuePlaylistPlugin.hxx" +#include "plugins/EmbeddedCuePlaylistPlugin.hxx" +#include "input/InputStream.hxx" +#include "util/UriUtil.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Macros.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +const struct playlist_plugin *const playlist_plugins[] = { + &extm3u_playlist_plugin, + &m3u_playlist_plugin, +#ifdef HAVE_GLIB + // TODO: enable without GLib + &pls_playlist_plugin, +#endif +#ifdef HAVE_EXPAT + &xspf_playlist_plugin, + &asx_playlist_plugin, + &rss_playlist_plugin, +#endif +#ifdef ENABLE_DESPOTIFY + &despotify_playlist_plugin, +#endif +#ifdef ENABLE_SOUNDCLOUD + &soundcloud_playlist_plugin, +#endif + &cue_playlist_plugin, + &embcue_playlist_plugin, + nullptr +}; + +static constexpr unsigned n_playlist_plugins = + ARRAY_SIZE(playlist_plugins) - 1; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[n_playlist_plugins]; + +#define playlist_plugins_for_each_enabled(plugin) \ + playlist_plugins_for_each(plugin) \ + if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins]) + +void +playlist_list_global_init(void) +{ + const config_param empty; + + for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + const struct config_param *param = + config_find_block(CONF_PLAYLIST_PLUGIN, "name", + plugin->name); + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + playlist_plugins_enabled[i] = + playlist_plugin_init(playlist_plugins[i], *param); + } +} + +void +playlist_list_global_finish(void) +{ + playlist_plugins_for_each_enabled(plugin) + playlist_plugin_finish(plugin); +} + +static SongEnumerator * +playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, + bool *tried) +{ + SongEnumerator *playlist = nullptr; + + assert(uri != nullptr); + + const auto scheme = uri_get_scheme(uri); + if (scheme.empty()) + return nullptr; + + for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + assert(!tried[i]); + + if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr && + plugin->schemes != nullptr && + string_array_contains(plugin->schemes, scheme.c_str())) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != nullptr) + break; + + tried[i] = true; + } + } + + return playlist; +} + +static SongEnumerator * +playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond, + const bool *tried) +{ + const char *suffix; + SongEnumerator *playlist = nullptr; + + assert(uri != nullptr); + + suffix = uri_get_suffix(uri); + if (suffix == nullptr) + return nullptr; + + for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && !tried[i] && + plugin->open_uri != nullptr && plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != nullptr) + break; + } + } + + return playlist; +} + +SongEnumerator * +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + /** this array tracks which plugins have already been tried by + playlist_list_open_uri_scheme() */ + bool tried[n_playlist_plugins]; + + assert(uri != nullptr); + + memset(tried, false, sizeof(tried)); + + auto playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried); + if (playlist == nullptr) + playlist = playlist_list_open_uri_suffix(uri, mutex, cond, + tried); + + return playlist; +} + +static SongEnumerator * +playlist_list_open_stream_mime2(InputStream &is, const char *mime) +{ + assert(mime != nullptr); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->open_stream != nullptr && + plugin->mime_types != nullptr && + string_array_contains(plugin->mime_types, mime)) { + /* rewind the stream, so each plugin gets a + fresh start */ + is.Rewind(IgnoreError()); + + auto playlist = playlist_plugin_open_stream(plugin, + is); + if (playlist != nullptr) + return playlist; + } + } + + return nullptr; +} + +static SongEnumerator * +playlist_list_open_stream_mime(InputStream &is, const char *full_mime) +{ + assert(full_mime != nullptr); + + const char *semicolon = strchr(full_mime, ';'); + if (semicolon == nullptr) + return playlist_list_open_stream_mime2(is, full_mime); + + if (semicolon == full_mime) + return nullptr; + + /* probe only the portion before the semicolon*/ + const std::string mime(full_mime, semicolon); + return playlist_list_open_stream_mime2(is, mime.c_str()); +} + +SongEnumerator * +playlist_list_open_stream_suffix(InputStream &is, const char *suffix) +{ + assert(suffix != nullptr); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->open_stream != nullptr && + plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) { + /* rewind the stream, so each plugin gets a + fresh start */ + is.Rewind(IgnoreError()); + + auto playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != nullptr) + return playlist; + } + } + + return nullptr; +} + +SongEnumerator * +playlist_list_open_stream(InputStream &is, const char *uri) +{ + assert(is.IsReady()); + + const char *const mime = is.GetMimeType(); + if (mime != nullptr) { + auto playlist = playlist_list_open_stream_mime(is, mime); + if (playlist != nullptr) + return playlist; + } + + const char *suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr; + if (suffix != nullptr) { + auto playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != nullptr) + return playlist; + } + + return nullptr; +} + +bool +playlist_suffix_supported(const char *suffix) +{ + assert(suffix != nullptr); + + playlist_plugins_for_each_enabled(plugin) { + if (plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) + return true; + } + + return false; +} diff --git a/src/playlist/PlaylistRegistry.hxx b/src/playlist/PlaylistRegistry.hxx new file mode 100644 index 000000000..7ce559baa --- /dev/null +++ b/src/playlist/PlaylistRegistry.hxx @@ -0,0 +1,74 @@ +/* + * 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_PLAYLIST_REGISTRY_HXX +#define MPD_PLAYLIST_REGISTRY_HXX + +class Mutex; +class Cond; +class SongEnumerator; +class InputStream; + +extern const struct playlist_plugin *const playlist_plugins[]; + +#define playlist_plugins_for_each(plugin) \ + for (const struct playlist_plugin *plugin, \ + *const*playlist_plugin_iterator = &playlist_plugins[0]; \ + (plugin = *playlist_plugin_iterator) != nullptr; \ + ++playlist_plugin_iterator) + +/** + * Initializes all playlist plugins. + */ +void +playlist_list_global_init(void); + +/** + * Deinitializes all playlist plugins. + */ +void +playlist_list_global_finish(void); + +/** + * Opens a playlist by its URI. + */ +SongEnumerator * +playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond); + +SongEnumerator * +playlist_list_open_stream_suffix(InputStream &is, const char *suffix); + +/** + * Opens a playlist from an input stream. + * + * @param is an #input_stream object which is open and ready + * @param uri optional URI which was used to open the stream; may be + * used to select the appropriate playlist plugin + */ +SongEnumerator * +playlist_list_open_stream(InputStream &is, const char *uri); + +/** + * Determines if there is a playlist plugin which can handle the + * specified file name suffix. + */ +bool +playlist_suffix_supported(const char *suffix); + +#endif diff --git a/src/playlist/PlaylistSong.cxx b/src/playlist/PlaylistSong.cxx new file mode 100644 index 000000000..3603c1add --- /dev/null +++ b/src/playlist/PlaylistSong.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 "PlaylistSong.hxx" +#include "SongLoader.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "fs/Traits.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" + +#include <assert.h> +#include <string.h> + +static void +merge_song_metadata(DetachedSong &add, const DetachedSong &base) +{ + if (base.GetTag().IsDefined()) { + TagBuilder builder(add.GetTag()); + builder.Complement(base.GetTag()); + add.SetTag(builder.Commit()); + } + + add.SetLastModified(base.GetLastModified()); +} + +static bool +playlist_check_load_song(DetachedSong &song, const SongLoader &loader) +{ + DetachedSong *tmp = loader.LoadSong(song.GetURI(), IgnoreError()); + if (tmp == nullptr) + return false; + + song.SetURI(tmp->GetURI()); + if (!song.HasRealURI() && tmp->HasRealURI()) + song.SetRealURI(tmp->GetRealURI()); + + merge_song_metadata(song, *tmp); + delete tmp; + return true; +} + +bool +playlist_check_translate_song(DetachedSong &song, const char *base_uri, + const SongLoader &loader) +{ + if (base_uri != nullptr && strcmp(base_uri, ".") == 0) + /* PathTraitsUTF8::GetParent() returns "." when there + is no directory name in the given path; clear that + now, because it would break the database lookup + functions */ + base_uri = nullptr; + + const char *uri = song.GetURI(); + if (base_uri != nullptr && !uri_has_scheme(uri) && + !PathTraitsUTF8::IsAbsolute(uri)) + song.SetURI(PathTraitsUTF8::Build(base_uri, uri)); + + return playlist_check_load_song(song, loader); +} diff --git a/src/playlist/PlaylistSong.hxx b/src/playlist/PlaylistSong.hxx new file mode 100644 index 000000000..278df46a8 --- /dev/null +++ b/src/playlist/PlaylistSong.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_PLAYLIST_SONG_HXX +#define MPD_PLAYLIST_SONG_HXX + +class SongLoader; +class DetachedSong; + +/** + * Verifies the song, returns false if it is unsafe. Translate the + * song to a song within the database, if it is a local file. + * + * @return true on success, false if the song should not be used + */ +bool +playlist_check_translate_song(DetachedSong &song, const char *base_uri, + const SongLoader &loader); + +#endif diff --git a/src/playlist/PlaylistStream.cxx b/src/playlist/PlaylistStream.cxx new file mode 100644 index 000000000..5855c598b --- /dev/null +++ b/src/playlist/PlaylistStream.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 "PlaylistStream.hxx" +#include "PlaylistRegistry.hxx" +#include "CloseSongEnumerator.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "input/InputStream.hxx" +#include "Log.hxx" + +#include <assert.h> + +static SongEnumerator * +playlist_open_path_suffix(const char *path_fs, Mutex &mutex, Cond &cond) +{ + assert(path_fs != nullptr); + + const char *suffix = uri_get_suffix(path_fs); + if (suffix == nullptr || !playlist_suffix_supported(suffix)) + return nullptr; + + Error error; + InputStream *is = InputStream::OpenReady(path_fs, mutex, cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + auto playlist = playlist_list_open_stream_suffix(*is, suffix); + if (playlist != nullptr) + playlist = new CloseSongEnumerator(playlist, is); + else + delete is; + + return playlist; +} + +SongEnumerator * +playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond) +{ + auto playlist = playlist_list_open_uri(path_fs, mutex, cond); + if (playlist == nullptr) + playlist = playlist_open_path_suffix(path_fs, mutex, cond); + + return playlist; +} + +SongEnumerator * +playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond) +{ + assert(uri_has_scheme(uri)); + + SongEnumerator *playlist = playlist_list_open_uri(uri, mutex, cond); + if (playlist != nullptr) + return playlist; + + Error error; + InputStream *is = InputStream::OpenReady(uri, mutex, cond, error); + if (is == nullptr) { + if (error.IsDefined()) + FormatError(error, "Failed to open %s", uri); + + return nullptr; + } + + playlist = playlist_list_open_stream(*is, uri); + if (playlist == nullptr) { + delete is; + return nullptr; + } + + return new CloseSongEnumerator(playlist, is); +} diff --git a/src/playlist/PlaylistStream.hxx b/src/playlist/PlaylistStream.hxx new file mode 100644 index 000000000..02f5485ef --- /dev/null +++ b/src/playlist/PlaylistStream.hxx @@ -0,0 +1,45 @@ +/* + * 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_PLAYLIST_STREAM_HXX +#define MPD_PLAYLIST_STREAM_HXX + +#include "Compiler.h" + +class Mutex; +class Cond; +class SongEnumerator; + +/** + * Opens a playlist from a local file. + * + * @param path_fs the path of the playlist file + * @param is_r on success, an input_stream object is returned here, + * which must be closed after the playlist_provider object is freed + * @return a playlist, or nullptr on error + */ +gcc_nonnull_all +SongEnumerator * +playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond); + +gcc_nonnull_all +SongEnumerator * +playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond); + +#endif diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx deleted file mode 100644 index 7b5c8824c..000000000 --- a/src/playlist/PlsPlaylistPlugin.cxx +++ /dev/null @@ -1,182 +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 "PlsPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <string> -#include <stdio.h> - -static constexpr Domain pls_domain("pls"); - -static void -pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) -{ - gchar *value; - int length; - GError *error = nullptr; - int num_entries = g_key_file_get_integer(keyfile, "playlist", - "NumberOfEntries", &error); - if (error) { - FormatError(pls_domain, - "Invalid PLS file: '%s'", error->message); - g_error_free(error); - error = nullptr; - - /* Hack to work around shoutcast failure to comform to spec */ - num_entries = g_key_file_get_integer(keyfile, "playlist", - "numberofentries", &error); - if (error) { - g_error_free(error); - error = nullptr; - } - } - - while (num_entries > 0) { - Song *song; - - char key[64]; - sprintf(key, "File%u", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); - if(error) { - FormatError(pls_domain, "Invalid PLS entry %s: '%s'", - key, error->message); - g_error_free(error); - return; - } - - song = Song::NewRemote(value); - g_free(value); - - sprintf(key, "Title%u", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); - if(error == nullptr && value){ - if (song->tag == nullptr) - song->tag = new Tag(); - song->tag->AddItem(TAG_TITLE, value); - } - /* Ignore errors? Most likely value not present */ - if(error) g_error_free(error); - error = nullptr; - g_free(value); - - sprintf(key, "Length%u", num_entries); - length = g_key_file_get_integer(keyfile, "playlist", key, - &error); - if(error == nullptr && length > 0){ - if (song->tag == nullptr) - song->tag = new Tag(); - song->tag->time = length; - } - /* Ignore errors? Most likely value not present */ - if(error) g_error_free(error); - error = nullptr; - - songs.emplace_front(song); - num_entries--; - } - -} - -static SongEnumerator * -pls_open_stream(InputStream &is) -{ - GError *error = nullptr; - Error error2; - size_t nbytes; - char buffer[1024]; - bool success; - GKeyFile *keyfile; - - std::string kf_data; - - do { - nbytes = is.LockRead(buffer, sizeof(buffer), error2); - if (nbytes == 0) { - if (error2.IsDefined()) { - LogError(error2); - return nullptr; - } - - break; - } - - kf_data.append(buffer, nbytes); - /* Limit to 64k */ - } while (kf_data.length() < 65536); - - if (kf_data.empty()) { - LogWarning(pls_domain, "KeyFile parser failed: No Data"); - return nullptr; - } - - keyfile = g_key_file_new(); - success = g_key_file_load_from_data(keyfile, - kf_data.data(), kf_data.length(), - G_KEY_FILE_NONE, &error); - - if (!success) { - FormatError(pls_domain, - "KeyFile parser failed: %s", error->message); - g_error_free(error); - g_key_file_free(keyfile); - return nullptr; - } - - std::forward_list<SongPointer> songs; - pls_parser(keyfile, songs); - g_key_file_free(keyfile); - - return new MemorySongEnumerator(std::move(songs)); -} - -static const char *const pls_suffixes[] = { - "pls", - nullptr -}; - -static const char *const pls_mime_types[] = { - "audio/x-scpls", - nullptr -}; - -const struct playlist_plugin pls_playlist_plugin = { - "pls", - - nullptr, - nullptr, - nullptr, - pls_open_stream, - - nullptr, - pls_suffixes, - pls_mime_types, -}; diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/PlsPlaylistPlugin.hxx deleted file mode 100644 index 3fafd36d0..000000000 --- a/src/playlist/PlsPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX -#define MPD_PLS_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin pls_playlist_plugin; - -#endif diff --git a/src/playlist/Print.cxx b/src/playlist/Print.cxx new file mode 100644 index 000000000..0db2a4ab0 --- /dev/null +++ b/src/playlist/Print.cxx @@ -0,0 +1,74 @@ +/* + * 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 "Print.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "SongEnumerator.hxx" +#include "SongPrint.hxx" +#include "DetachedSong.hxx" +#include "SongLoader.hxx" +#include "fs/Traits.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "client/Client.hxx" + +static void +playlist_provider_print(Client &client, const char *uri, + SongEnumerator &e, bool detail) +{ + const std::string base_uri = uri != nullptr + ? PathTraitsUTF8::GetParent(uri) + : std::string("."); + + const SongLoader loader(client); + + DetachedSong *song; + while ((song = e.NextSong()) != nullptr) { + if (playlist_check_translate_song(*song, base_uri.c_str(), + loader)) { + if (detail) + song_print_info(client, *song); + else + song_print_uri(client, *song); + } + + delete song; + } +} + +bool +playlist_file_print(Client &client, const char *uri, bool detail) +{ + Mutex mutex; + Cond cond; + + SongEnumerator *playlist = playlist_open_any(uri, +#ifdef ENABLE_DATABASE + client.GetStorage(), +#endif + mutex, cond); + if (playlist == nullptr) + return false; + + playlist_provider_print(client, uri, *playlist, detail); + delete playlist; + return true; +} diff --git a/src/playlist/Print.hxx b/src/playlist/Print.hxx new file mode 100644 index 000000000..c2fff5475 --- /dev/null +++ b/src/playlist/Print.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_PLAYLIST__PRINT_HXX +#define MPD_PLAYLIST__PRINT_HXX + +class Client; + +/** + * Send the playlist file to the client. + * + * @param client the client which requested the playlist + * @param uri the URI of the playlist file in UTF-8 encoding + * @param detail true if all details should be printed + * @return true on success, false if the playlist does not exist + */ +bool +playlist_file_print(Client &client, const char *uri, bool detail); + +#endif diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx deleted file mode 100644 index e2a44bfd3..000000000 --- a/src/playlist/RssPlaylistPlugin.cxx +++ /dev/null @@ -1,284 +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 "RssPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static constexpr Domain rss_domain("rss"); - -/** - * This is the state object for the GLib XML parser. - */ -struct RssParser { - /** - * The list of songs (in reverse order because that's faster - * while adding). - */ - std::forward_list<SongPointer> songs; - - /** - * The current position in the XML file. - */ - enum { - ROOT, ITEM, - } state; - - /** - * The current tag within the "entry" element. This is only - * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there - * is no (known) tag. - */ - TagType tag; - - /** - * The current song. It is allocated after the "location" - * element. - */ - Song *song; - - RssParser() - :state(ROOT) {} -}; - -static const gchar * -get_attribute(const gchar **attribute_names, const gchar **attribute_values, - const gchar *name) -{ - for (unsigned i = 0; attribute_names[i] != nullptr; ++i) - if (StringEqualsCaseASCII(attribute_names[i], name)) - return attribute_values[i]; - - return nullptr; -} - -static void -rss_start_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, gcc_unused GError **error) -{ - RssParser *parser = (RssParser *)user_data; - - switch (parser->state) { - case RssParser::ROOT: - if (StringEqualsCaseASCII(element_name, "item")) { - parser->state = RssParser::ITEM; - parser->song = Song::NewRemote("rss:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case RssParser::ITEM: - if (StringEqualsCaseASCII(element_name, "enclosure")) { - const gchar *href = get_attribute(attribute_names, - attribute_values, - "url"); - if (href != nullptr) { - /* create new song object, and copy - the existing tag over; we cannot - replace the existing song's URI, - because that attribute is - immutable */ - Song *song = Song::NewRemote(href); - - if (parser->song != nullptr) { - song->tag = parser->song->tag; - parser->song->tag = nullptr; - parser->song->Free(); - } - - parser->song = song; - } - } else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; - else if (StringEqualsCaseASCII(element_name, "itunes:author")) - parser->tag = TAG_ARTIST; - - break; - } -} - -static void -rss_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) -{ - RssParser *parser = (RssParser *)user_data; - - switch (parser->state) { - case RssParser::ROOT: - break; - - case RssParser::ITEM: - if (StringEqualsCaseASCII(element_name, "item")) { - if (strcmp(parser->song->uri, "rss:") != 0) - parser->songs.emplace_front(parser->song); - else - parser->song->Free(); - - parser->state = RssParser::ROOT; - } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; - - break; - } -} - -static void -rss_text(gcc_unused GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, gcc_unused GError **error) -{ - RssParser *parser = (RssParser *)user_data; - - switch (parser->state) { - case RssParser::ROOT: - break; - - case RssParser::ITEM: - if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == nullptr) - parser->song->tag = new Tag(); - parser->song->tag->AddItem(parser->tag, - text, text_len); - } - - break; - } -} - -static const GMarkupParser rss_parser = { - rss_start_element, - rss_end_element, - rss_text, - nullptr, - nullptr, -}; - -static void -rss_parser_destroy(gpointer data) -{ - RssParser *parser = (RssParser *)data; - - if (parser->state >= RssParser::ITEM) - parser->song->Free(); -} - -/* - * The playlist object - * - */ - -static SongEnumerator * -rss_open_stream(InputStream &is) -{ - RssParser parser; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - Error error2; - GError *error = nullptr; - - /* parse the RSS XML file */ - - context = g_markup_parse_context_new(&rss_parser, - G_MARKUP_TREAT_CDATA_AS_TEXT, - &parser, rss_parser_destroy); - - while (true) { - nbytes = is.LockRead(buffer, sizeof(buffer), error2); - if (nbytes == 0) { - if (error2.IsDefined()) { - g_markup_parse_context_free(context); - LogError(error2); - return nullptr; - } - - break; - } - - success = g_markup_parse_context_parse(context, buffer, nbytes, - &error); - if (!success) { - FormatError(rss_domain, - "XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return nullptr; - } - } - - success = g_markup_parse_context_end_parse(context, &error); - if (!success) { - FormatError(rss_domain, - "XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return nullptr; - } - - parser.songs.reverse(); - MemorySongEnumerator *playlist = - new MemorySongEnumerator(std::move(parser.songs)); - - g_markup_parse_context_free(context); - - return playlist; -} - -static const char *const rss_suffixes[] = { - "rss", - nullptr -}; - -static const char *const rss_mime_types[] = { - "application/rss+xml", - "text/xml", - nullptr -}; - -const struct playlist_plugin rss_playlist_plugin = { - "rss", - - nullptr, - nullptr, - nullptr, - rss_open_stream, - - nullptr, - rss_suffixes, - rss_mime_types, -}; diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx deleted file mode 100644 index f49f7e9cf..000000000 --- a/src/playlist/RssPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX -#define MPD_RSS_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin rss_playlist_plugin; - -#endif diff --git a/src/playlist/SongEnumerator.hxx b/src/playlist/SongEnumerator.hxx new file mode 100644 index 000000000..75295add1 --- /dev/null +++ b/src/playlist/SongEnumerator.hxx @@ -0,0 +1,41 @@ +/* + * 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_SONG_ENUMERATOR_HXX +#define MPD_SONG_ENUMERATOR_HXX + +class DetachedSong; + +/** + * An object which provides serial access to a number of #Song + * objects. It is used to enumerate the contents of a playlist file. + */ +class SongEnumerator { +public: + virtual ~SongEnumerator() {} + + /** + * Obtain the next song. The caller is responsible for + * freeing the returned #Song object. Returns nullptr if + * there are no more songs. + */ + virtual DetachedSong *NextSong() = 0; +}; + +#endif diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx deleted file mode 100644 index f6797b14d..000000000 --- a/src/playlist/SoundCloudPlaylistPlugin.cxx +++ /dev/null @@ -1,421 +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 "SoundCloudPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "ConfigData.hxx" -#include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <yajl/yajl_parse.h> - -#include <string> - -#include <string.h> - -static struct { - std::string apikey; -} soundcloud_config; - -static constexpr Domain soundcloud_domain("soundcloud"); - -static bool -soundcloud_init(const config_param ¶m) -{ - soundcloud_config.apikey = param.GetBlockValue("apikey", ""); - if (soundcloud_config.apikey.empty()) { - LogDebug(soundcloud_domain, - "disabling the soundcloud playlist plugin " - "because API key is not set"); - return false; - } - - return true; -} - -/** - * Construct a full soundcloud resolver URL from the given fragment. - * @param uri uri of a soundcloud page (or just the path) - * @return Constructed URL. Must be freed with g_free. - */ -static char * -soundcloud_resolve(const char* uri) { - char *u, *ru; - - if (g_str_has_prefix(uri, "http://")) { - u = g_strdup(uri); - } else if (g_str_has_prefix(uri, "soundcloud.com")) { - u = g_strconcat("http://", uri, nullptr); - } else { - /* assume it's just a path on soundcloud.com */ - u = g_strconcat("http://soundcloud.com/", uri, nullptr); - } - - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", - u, "&client_id=", - soundcloud_config.apikey.c_str(), nullptr); - g_free(u); - - return ru; -} - -/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ - -enum key { - Duration, - Title, - Stream_URL, - Other, -}; - -const char* key_str[] = { - "duration", - "title", - "stream_url", - nullptr, -}; - -struct parse_data { - int key; - char* stream_url; - long duration; - char* title; - int got_url; /* nesting level of last stream_url */ - - std::forward_list<SongPointer> songs; -}; - -static int handle_integer(void *ctx, - long -#ifndef HAVE_YAJL1 - long -#endif - intval) -{ - struct parse_data *data = (struct parse_data *) ctx; - - switch (data->key) { - case Duration: - data->duration = intval; - break; - default: - break; - } - - return 1; -} - -static int handle_string(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - const char *s = (const char *) stringval; - - switch (data->key) { - case Title: - if (data->title != nullptr) - g_free(data->title); - data->title = g_strndup(s, stringlen); - break; - case Stream_URL: - if (data->stream_url != nullptr) - g_free(data->stream_url); - data->stream_url = g_strndup(s, stringlen); - data->got_url = 1; - break; - default: - break; - } - - return 1; -} - -static int handle_mapkey(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) -{ - struct parse_data *data = (struct parse_data *) ctx; - - int i; - data->key = Other; - - for (i = 0; i < Other; ++i) { - if (memcmp((const char *)stringval, key_str[i], stringlen) == 0) { - data->key = i; - break; - } - } - - return 1; -} - -static int handle_start_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 0) - data->got_url++; - - return 1; -} - -static int handle_end_map(void *ctx) -{ - struct parse_data *data = (struct parse_data *) ctx; - - if (data->got_url > 1) { - data->got_url--; - return 1; - } - - if (data->got_url == 0) - return 1; - - /* got_url == 1, track finished, make it into a song */ - data->got_url = 0; - - Song *s; - char *u; - - u = g_strconcat(data->stream_url, "?client_id=", - soundcloud_config.apikey.c_str(), nullptr); - s = Song::NewRemote(u); - g_free(u); - - Tag *t = new Tag(); - t->time = data->duration / 1000; - if (data->title != nullptr) - t->AddItem(TAG_NAME, data->title); - s->tag = t; - - data->songs.emplace_front(s); - - return 1; -} - -static yajl_callbacks parse_callbacks = { - nullptr, - nullptr, - handle_integer, - nullptr, - nullptr, - handle_string, - handle_start_map, - handle_mapkey, - handle_end_map, - nullptr, - nullptr, -}; - -/** - * Read JSON data and parse it using the given YAJL parser. - * @param url URL of the JSON data. - * @param hand YAJL parser handle. - * @return -1 on error, 0 on success. - */ -static int -soundcloud_parse_json(const char *url, yajl_handle hand, - Mutex &mutex, Cond &cond) -{ - char buffer[4096]; - unsigned char *ubuffer = (unsigned char *)buffer; - - Error error; - InputStream *input_stream = InputStream::Open(url, mutex, cond, - error); - if (input_stream == nullptr) { - if (error.IsDefined()) - LogError(error); - return -1; - } - - mutex.lock(); - input_stream->WaitReady(); - - yajl_status stat; - int done = 0; - - while (!done) { - const size_t nbytes = - input_stream->Read(buffer, sizeof(buffer), error); - if (nbytes == 0) { - if (error.IsDefined()) - LogError(error); - - if (input_stream->IsEOF()) { - done = true; - } else { - mutex.unlock(); - input_stream->Close(); - return -1; - } - } - - if (done) { -#ifdef HAVE_YAJL1 - stat = yajl_parse_complete(hand); -#else - stat = yajl_complete_parse(hand); -#endif - } else - stat = yajl_parse(hand, ubuffer, nbytes); - - if (stat != yajl_status_ok -#ifdef HAVE_YAJL1 - && stat != yajl_status_insufficient_data -#endif - ) - { - unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); - LogError(soundcloud_domain, (const char *)str); - yajl_free_error(hand, str); - break; - } - } - - mutex.unlock(); - input_stream->Close(); - - return 0; -} - -/** - * Parse a soundcloud:// URL and create a playlist. - * @param uri A soundcloud URL. Accepted forms: - * soundcloud://track/<track-id> - * soundcloud://playlist/<playlist-id> - * soundcloud://url/<url or path of soundcloud page> - */ - -static SongEnumerator * -soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) -{ - char *s, *p; - char *scheme, *arg, *rest; - s = g_strdup(uri); - scheme = s; - for (p = s; *p; p++) { - if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { - *p = 0; - p += 3; - break; - } - } - arg = p; - for (; *p; p++) { - if (*p == '/') { - *p = 0; - p++; - break; - } - } - rest = p; - - if (strcmp(scheme, "soundcloud") != 0) { - FormatWarning(soundcloud_domain, - "incompatible scheme for soundcloud plugin: %s", - scheme); - g_free(s); - return nullptr; - } - - char *u = nullptr; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", - rest, ".json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); - } else if (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", - rest, ".json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); - } else if (strcmp(arg, "url") == 0) { - /* Translate to soundcloud resolver call. libcurl will automatically - follow the redirect to the right resource. */ - u = soundcloud_resolve(rest); - } - g_free(s); - - if (u == nullptr) { - LogWarning(soundcloud_domain, "unknown soundcloud URI"); - return nullptr; - } - - yajl_handle hand; - struct parse_data data; - - data.got_url = 0; - data.title = nullptr; - data.stream_url = nullptr; -#ifdef HAVE_YAJL1 - hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, (void *) &data); -#else - hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data); -#endif - - int ret = soundcloud_parse_json(u, hand, mutex, cond); - - g_free(u); - yajl_free(hand); - if (data.title != nullptr) - g_free(data.title); - if (data.stream_url != nullptr) - g_free(data.stream_url); - - if (ret == -1) - return nullptr; - - data.songs.reverse(); - return new MemorySongEnumerator(std::move(data.songs)); -} - -static const char *const soundcloud_schemes[] = { - "soundcloud", - nullptr -}; - -const struct playlist_plugin soundcloud_playlist_plugin = { - "soundcloud", - - soundcloud_init, - nullptr, - soundcloud_open_uri, - nullptr, - - soundcloud_schemes, - nullptr, - nullptr, -}; - - diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx deleted file mode 100644 index 7c121328c..000000000 --- a/src/playlist/SoundCloudPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX -#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin soundcloud_playlist_plugin; - -#endif diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx deleted file mode 100644 index dcfab5a80..000000000 --- a/src/playlist/XspfPlaylistPlugin.cxx +++ /dev/null @@ -1,301 +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 "XspfPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" -#include "tag/Tag.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -static constexpr Domain xspf_domain("xspf"); - -/** - * This is the state object for the GLib XML parser. - */ -struct XspfParser { - /** - * The list of songs (in reverse order because that's faster - * while adding). - */ - std::forward_list<SongPointer> songs; - - /** - * The current position in the XML file. - */ - enum { - ROOT, PLAYLIST, TRACKLIST, TRACK, - LOCATION, - } state; - - /** - * The current tag within the "track" element. This is only - * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there - * is no (known) tag. - */ - TagType tag; - - /** - * The current song. It is allocated after the "location" - * element. - */ - Song *song; - - XspfParser() - :state(ROOT) {} -}; - -static void -xspf_start_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gcc_unused const gchar **attribute_names, - gcc_unused const gchar **attribute_values, - gpointer user_data, gcc_unused GError **error) -{ - XspfParser *parser = (XspfParser *)user_data; - - switch (parser->state) { - case XspfParser::ROOT: - if (strcmp(element_name, "playlist") == 0) - parser->state = XspfParser::PLAYLIST; - - break; - - case XspfParser::PLAYLIST: - if (strcmp(element_name, "trackList") == 0) - parser->state = XspfParser::TRACKLIST; - - break; - - case XspfParser::TRACKLIST: - if (strcmp(element_name, "track") == 0) { - parser->state = XspfParser::TRACK; - parser->song = nullptr; - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case XspfParser::TRACK: - if (strcmp(element_name, "location") == 0) - parser->state = XspfParser::LOCATION; - else if (strcmp(element_name, "title") == 0) - parser->tag = TAG_TITLE; - else if (strcmp(element_name, "creator") == 0) - /* TAG_COMPOSER would be more correct - according to the XSPF spec */ - parser->tag = TAG_ARTIST; - else if (strcmp(element_name, "annotation") == 0) - parser->tag = TAG_COMMENT; - else if (strcmp(element_name, "album") == 0) - parser->tag = TAG_ALBUM; - else if (strcmp(element_name, "trackNum") == 0) - parser->tag = TAG_TRACK; - - break; - - case XspfParser::LOCATION: - break; - } -} - -static void -xspf_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) -{ - XspfParser *parser = (XspfParser *)user_data; - - switch (parser->state) { - case XspfParser::ROOT: - break; - - case XspfParser::PLAYLIST: - if (strcmp(element_name, "playlist") == 0) - parser->state = XspfParser::ROOT; - - break; - - case XspfParser::TRACKLIST: - if (strcmp(element_name, "tracklist") == 0) - parser->state = XspfParser::PLAYLIST; - - break; - - case XspfParser::TRACK: - if (strcmp(element_name, "track") == 0) { - if (parser->song != nullptr) - parser->songs.emplace_front(parser->song); - - parser->state = XspfParser::TRACKLIST; - } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; - - break; - - case XspfParser::LOCATION: - parser->state = XspfParser::TRACK; - break; - } -} - -static void -xspf_text(gcc_unused GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, gcc_unused GError **error) -{ - XspfParser *parser = (XspfParser *)user_data; - - switch (parser->state) { - case XspfParser::ROOT: - case XspfParser::PLAYLIST: - case XspfParser::TRACKLIST: - break; - - case XspfParser::TRACK: - if (parser->song != nullptr && - parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == nullptr) - parser->song->tag = new Tag(); - parser->song->tag->AddItem(parser->tag, text, text_len); - } - - break; - - case XspfParser::LOCATION: - if (parser->song == nullptr) { - char *uri = g_strndup(text, text_len); - parser->song = Song::NewRemote(uri); - g_free(uri); - } - - break; - } -} - -static const GMarkupParser xspf_parser = { - xspf_start_element, - xspf_end_element, - xspf_text, - nullptr, - nullptr, -}; - -static void -xspf_parser_destroy(gpointer data) -{ - XspfParser *parser = (XspfParser *)data; - - if (parser->state >= XspfParser::TRACK && parser->song != nullptr) - parser->song->Free(); -} - -/* - * The playlist object - * - */ - -static SongEnumerator * -xspf_open_stream(InputStream &is) -{ - XspfParser parser; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - Error error2; - GError *error = nullptr; - - /* parse the XSPF XML file */ - - context = g_markup_parse_context_new(&xspf_parser, - G_MARKUP_TREAT_CDATA_AS_TEXT, - &parser, xspf_parser_destroy); - - while (true) { - nbytes = is.LockRead(buffer, sizeof(buffer), error2); - if (nbytes == 0) { - if (error2.IsDefined()) { - g_markup_parse_context_free(context); - LogError(error2); - return nullptr; - } - - break; - } - - success = g_markup_parse_context_parse(context, buffer, nbytes, - &error); - if (!success) { - FormatError(xspf_domain, - "XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return nullptr; - } - } - - success = g_markup_parse_context_end_parse(context, &error); - if (!success) { - FormatError(xspf_domain, - "XML parser failed: %s", error->message); - g_error_free(error); - g_markup_parse_context_free(context); - return nullptr; - } - - parser.songs.reverse(); - MemorySongEnumerator *playlist = - new MemorySongEnumerator(std::move(parser.songs)); - - g_markup_parse_context_free(context); - - return playlist; -} - -static const char *const xspf_suffixes[] = { - "xspf", - nullptr -}; - -static const char *const xspf_mime_types[] = { - "application/xspf+xml", - nullptr -}; - -const struct playlist_plugin xspf_playlist_plugin = { - "xspf", - - nullptr, - nullptr, - nullptr, - xspf_open_stream, - - nullptr, - xspf_suffixes, - xspf_mime_types, -}; diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/XspfPlaylistPlugin.hxx deleted file mode 100644 index fc9bbd2c6..000000000 --- a/src/playlist/XspfPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX -#define MPD_XSPF_PLAYLIST_PLUGIN_HXX - -extern const struct playlist_plugin xspf_playlist_plugin; - -#endif diff --git a/src/playlist/cue/CueParser.cxx b/src/playlist/cue/CueParser.cxx new file mode 100644 index 000000000..10f28b5a1 --- /dev/null +++ b/src/playlist/cue/CueParser.cxx @@ -0,0 +1,316 @@ +/* + * 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 "CueParser.hxx" +#include "util/Alloc.hxx" +#include "util/StringUtil.hxx" +#include "util/CharUtil.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +CueParser::CueParser() + :state(HEADER), + current(nullptr), + previous(nullptr), + finished(nullptr), + end(false) {} + +CueParser::~CueParser() +{ + delete current; + delete previous; + delete finished; +} + +static const char * +cue_next_word(char *p, char **pp) +{ + assert(p >= *pp); + assert(!IsWhitespaceNotNull(*p)); + + const char *word = p; + while (!IsWhitespaceOrNull(*p)) + ++p; + + *p = 0; + *pp = p + 1; + return word; +} + +static const char * +cue_next_quoted(char *p, char **pp) +{ + assert(p >= *pp); + assert(p[-1] == '"'); + + char *end = strchr(p, '"'); + if (end == nullptr) { + /* syntax error - ignore it silently */ + *pp = p + strlen(p); + return p; + } + + *end = 0; + *pp = end + 1; + + return p; +} + +static const char * +cue_next_token(char **pp) +{ + char *p = StripLeft(*pp); + if (*p == 0) + return nullptr; + + return cue_next_word(p, pp); +} + +static const char * +cue_next_value(char **pp) +{ + char *p = StripLeft(*pp); + if (*p == 0) + return nullptr; + + if (*p == '"') + return cue_next_quoted(p + 1, pp); + else + return cue_next_word(p, pp); +} + +static void +cue_add_tag(TagBuilder &tag, TagType type, char *p) +{ + const char *value = cue_next_value(&p); + if (value != nullptr) + tag.AddItem(type, value); + +} + +static void +cue_parse_rem(char *p, TagBuilder &tag) +{ + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + TagType type2 = tag_name_parse_i(type); + if (type2 != TAG_NUM_OF_ITEM_TYPES) + cue_add_tag(tag, type2, p); +} + +TagBuilder * +CueParser::GetCurrentTag() +{ + if (state == HEADER) + return &header_tag; + else if (state == TRACK) + return &song_tag; + else + return nullptr; +} + +static int +cue_parse_position(const char *p) +{ + char *endptr; + unsigned long minutes = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long seconds = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long frames = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != 0) + return -1; + + return minutes * 60000 + seconds * 1000 + frames * 1000 / 75; +} + +void +CueParser::Commit() +{ + /* the caller of this library must call cue_parser_get() often + enough */ + assert(finished == nullptr); + assert(!end); + + if (current == nullptr) + return; + + assert(!current->GetTag().IsDefined()); + current->SetTag(song_tag.Commit()); + + finished = previous; + previous = current; + current = nullptr; +} + +void +CueParser::Feed2(char *p) +{ + assert(!end); + assert(p != nullptr); + + const char *command = cue_next_token(&p); + if (command == nullptr) + return; + + if (strcmp(command, "REM") == 0) { + TagBuilder *tag = GetCurrentTag(); + if (tag != nullptr) + cue_parse_rem(p, *tag); + } else if (strcmp(command, "PERFORMER") == 0) { + /* MPD knows a "performer" tag, but it is not a good + match for this CUE tag; from the Hydrogenaudio + Knowledgebase: "At top-level this will specify the + CD artist, while at track-level it specifies the + track artist." */ + + TagType type = state == TRACK + ? TAG_ARTIST + : TAG_ALBUM_ARTIST; + + TagBuilder *tag = GetCurrentTag(); + if (tag != nullptr) + cue_add_tag(*tag, type, p); + } else if (strcmp(command, "TITLE") == 0) { + if (state == HEADER) + cue_add_tag(header_tag, TAG_ALBUM, p); + else if (state == TRACK) + cue_add_tag(song_tag, TAG_TITLE, p); + } else if (strcmp(command, "FILE") == 0) { + Commit(); + + const char *new_filename = cue_next_value(&p); + if (new_filename == nullptr) + return; + + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + if (strcmp(type, "WAVE") != 0 && + strcmp(type, "MP3") != 0 && + strcmp(type, "AIFF") != 0) { + state = IGNORE_FILE; + return; + } + + state = WAVE; + filename = new_filename; + } else if (state == IGNORE_FILE) { + return; + } else if (strcmp(command, "TRACK") == 0) { + Commit(); + + const char *nr = cue_next_token(&p); + if (nr == nullptr) + return; + + const char *type = cue_next_token(&p); + if (type == nullptr) + return; + + if (strcmp(type, "AUDIO") != 0) { + state = IGNORE_TRACK; + return; + } + + state = TRACK; + current = new DetachedSong(filename); + assert(!current->GetTag().IsDefined()); + + song_tag = header_tag; + song_tag.AddItem(TAG_TRACK, nr); + + last_updated = false; + } else if (state == IGNORE_TRACK) { + return; + } else if (state == TRACK && strcmp(command, "INDEX") == 0) { + const char *nr = cue_next_token(&p); + if (nr == nullptr) + return; + + const char *position = cue_next_token(&p); + if (position == nullptr) + return; + + int position_ms = cue_parse_position(position); + if (position_ms < 0) + return; + + if (!last_updated && previous != nullptr && + previous->GetStartMS() < (unsigned)position_ms) { + last_updated = true; + previous->SetEndMS(position_ms); + } + + current->SetStartMS(position_ms); + } +} + +void +CueParser::Feed(const char *line) +{ + assert(!end); + assert(line != nullptr); + + char *allocated = xstrdup(line); + Feed2(allocated); + free(allocated); +} + +void +CueParser::Finish() +{ + if (end) + /* has already been called, ignore */ + return; + + Commit(); + end = true; +} + +DetachedSong * +CueParser::Get() +{ + if (finished == nullptr && end) { + /* cue_parser_finish() has been called already: + deliver all remaining (partial) results */ + assert(current == nullptr); + + finished = previous; + previous = nullptr; + } + + DetachedSong *song = finished; + finished = nullptr; + return song; +} diff --git a/src/playlist/cue/CueParser.hxx b/src/playlist/cue/CueParser.hxx new file mode 100644 index 000000000..7e040169b --- /dev/null +++ b/src/playlist/cue/CueParser.hxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CUE_PARSER_HXX +#define MPD_CUE_PARSER_HXX + +#include "check.h" +#include "tag/TagBuilder.hxx" +#include "Compiler.h" + +#include <string> + +class DetachedSong; +struct Tag; + +class CueParser { + enum { + /** + * Parsing the CUE header. + */ + HEADER, + + /** + * Parsing a "FILE ... WAVE". + */ + WAVE, + + /** + * Ignore everything until the next "FILE". + */ + IGNORE_FILE, + + /** + * Parsing a "TRACK ... AUDIO". + */ + TRACK, + + /** + * Ignore everything until the next "TRACK". + */ + IGNORE_TRACK, + } state; + + /** + * Tags read from the CUE header. + */ + TagBuilder header_tag; + + /** + * Tags read for the current song (attribute #current). When + * #current gets moved to #previous, TagBuilder::Commit() will + * be called. + */ + TagBuilder song_tag; + + std::string filename; + + /** + * The song currently being edited. + */ + DetachedSong *current; + + /** + * The previous song. It is remembered because its end_time + * will be set to the current song's start time. + */ + DetachedSong *previous; + + /** + * A song that is completely finished and can be returned to + * the caller via cue_parser_get(). + */ + DetachedSong *finished; + + /** + * Set to true after previous.end_time has been updated to the + * start time of the current song. + */ + bool last_updated; + + /** + * Tracks whether cue_parser_finish() has been called. If + * true, then all remaining (partial) results will be + * delivered by cue_parser_get(). + */ + bool end; + +public: + CueParser(); + ~CueParser(); + + /** + * Feed a text line from the CUE file into the parser. Call + * cue_parser_get() after this to see if a song has been finished. + */ + void Feed(const char *line); + + /** + * Tell the parser that the end of the file has been reached. Call + * cue_parser_get() after this to see if a song has been finished. + * This procedure must be done twice! + */ + void Finish(); + + /** + * Check if a song was finished by the last cue_parser_feed() or + * cue_parser_finish() call. + * + * @return a song object that must be freed by the caller, or NULL if + * no song was finished at this time + */ + DetachedSong *Get(); + +private: + gcc_pure + TagBuilder *GetCurrentTag(); + + /** + * Commit the current song. It will be moved to "previous", + * so the next song may soon edit its end time (using the next + * song's start time). + */ + void Commit(); + + void Feed2(char *p); +}; + +#endif diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx new file mode 100644 index 000000000..3185a8144 --- /dev/null +++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx @@ -0,0 +1,186 @@ +/* + * 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 "AsxPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "tag/TagBuilder.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Log.hxx" + +/** + * This is the state object for the GLib XML parser. + */ +struct AsxParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<DetachedSong> songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ENTRY, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + TagType tag_type; + + /** + * The current song URI. It is set by the "ref" element. + */ + std::string location; + + TagBuilder tag_builder; + + AsxParser() + :state(ROOT) {} + +}; + +static void XMLCALL +asx_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + if (StringEqualsCaseASCII(element_name, "entry")) { + parser->state = AsxParser::ENTRY; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case AsxParser::ENTRY: + if (StringEqualsCaseASCII(element_name, "ref")) { + const char *href = + ExpatParser::GetAttributeCase(atts, "href"); + if (href != nullptr) + parser->location = href; + } else if (StringEqualsCaseASCII(element_name, "author")) + /* is that correct? or should it be COMPOSER + or PERFORMER? */ + parser->tag_type = TAG_ARTIST; + else if (StringEqualsCaseASCII(element_name, "title")) + parser->tag_type = TAG_TITLE; + + break; + } +} + +static void XMLCALL +asx_end_element(void *user_data, const XML_Char *element_name) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (StringEqualsCaseASCII(element_name, "entry")) { + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); + + parser->state = AsxParser::ROOT; + } else + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void XMLCALL +asx_char_data(void *user_data, const XML_Char *s, int len) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, len); + + break; + } +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +asx_open_stream(InputStream &is) +{ + AsxParser parser; + + { + ExpatParser expat(&parser); + expat.SetElementHandler(asx_start_element, asx_end_element); + expat.SetCharacterDataHandler(asx_char_data); + + Error error; + if (!expat.Parse(is, error)) { + LogError(error); + return nullptr; + } + } + + parser.songs.reverse(); + return new MemorySongEnumerator(std::move(parser.songs)); +} + +static const char *const asx_suffixes[] = { + "asx", + nullptr +}; + +static const char *const asx_mime_types[] = { + "video/x-ms-asf", + nullptr +}; + +const struct playlist_plugin asx_playlist_plugin = { + "asx", + + nullptr, + nullptr, + nullptr, + asx_open_stream, + + nullptr, + asx_suffixes, + asx_mime_types, +}; diff --git a/src/playlist/plugins/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx new file mode 100644 index 000000000..63371be0f --- /dev/null +++ b/src/playlist/plugins/AsxPlaylistPlugin.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_ASX_PLAYLIST_PLUGIN_HXX +#define MPD_ASX_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin asx_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx new file mode 100644 index 000000000..b907d34d0 --- /dev/null +++ b/src/playlist/plugins/CuePlaylistPlugin.cxx @@ -0,0 +1,88 @@ +/* + * 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 "CuePlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" +#include "../cue/CueParser.hxx" +#include "input/TextInputStream.hxx" + +#include <string> + +class CuePlaylist final : public SongEnumerator { + InputStream &is; + TextInputStream tis; + CueParser parser; + + public: + CuePlaylist(InputStream &_is) + :is(_is), tis(is) { + } + + virtual DetachedSong *NextSong() override; +}; + +static SongEnumerator * +cue_playlist_open_stream(InputStream &is) +{ + return new CuePlaylist(is); +} + +DetachedSong * +CuePlaylist::NextSong() +{ + DetachedSong *song = parser.Get(); + if (song != nullptr) + return song; + + const char *line; + while ((line = tis.ReadLine()) != nullptr) { + parser.Feed(line); + song = parser.Get(); + if (song != nullptr) + return song; + } + + parser.Finish(); + return parser.Get(); +} + +static const char *const cue_playlist_suffixes[] = { + "cue", + nullptr +}; + +static const char *const cue_playlist_mime_types[] = { + "application/x-cue", + nullptr +}; + +const struct playlist_plugin cue_playlist_plugin = { + "cue", + + nullptr, + nullptr, + nullptr, + cue_playlist_open_stream, + + nullptr, + cue_playlist_suffixes, + cue_playlist_mime_types, +}; diff --git a/src/playlist/plugins/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx new file mode 100644 index 000000000..4d833bfc2 --- /dev/null +++ b/src/playlist/plugins/CuePlaylistPlugin.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_CUE_PLAYLIST_PLUGIN_HXX +#define MPD_CUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin cue_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/DespotifyPlaylistPlugin.cxx b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx new file mode 100644 index 000000000..636f64bc6 --- /dev/null +++ b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx @@ -0,0 +1,142 @@ +/* + * 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 "DespotifyPlaylistPlugin.hxx" +#include "lib/despotify/DespotifyUtils.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "tag/Tag.hxx" +#include "DetachedSong.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <string.h> +#include <stdlib.h> +#include <string.h> + +static void +add_song(std::forward_list<DetachedSong> &songs, ds_track &track) +{ + const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; + char uri[128]; + char *ds_uri; + + /* Create a spt://... URI for MPD */ + snprintf(uri, sizeof(uri), "%s://", dsp_scheme); + ds_uri = uri + strlen(dsp_scheme) + 3; + + if (despotify_track_to_uri(&track, ds_uri) != ds_uri) { + /* Should never really fail, but let's be sure */ + FormatDebug(despotify_domain, + "Can't add track %s", track.title); + return; + } + + songs.emplace_front(uri, mpd_despotify_tag_from_track(track)); +} + +static bool +parse_track(struct despotify_session *session, + std::forward_list<DetachedSong> &songs, + struct ds_link *link) +{ + struct ds_track *track = despotify_link_get_track(session, link); + if (track == nullptr) + return false; + + add_song(songs, *track); + return true; +} + +static bool +parse_playlist(struct despotify_session *session, + std::forward_list<DetachedSong> &songs, + struct ds_link *link) +{ + ds_playlist *playlist = despotify_link_get_playlist(session, link); + if (playlist == nullptr) + return false; + + for (ds_track *track = playlist->tracks; track != nullptr; + track = track->next) + add_song(songs, *track); + + return true; +} + +static SongEnumerator * +despotify_playlist_open_uri(const char *url, + gcc_unused Mutex &mutex, gcc_unused Cond &cond) +{ + despotify_session *session = mpd_despotify_get_session(); + if (session == nullptr) + return nullptr; + + /* Get link without spt:// */ + ds_link *link = + despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); + if (link == nullptr) { + FormatDebug(despotify_domain, "Can't find %s\n", url); + return nullptr; + } + + std::forward_list<DetachedSong> songs; + + bool parse_result; + switch (link->type) { + case LINK_TYPE_TRACK: + parse_result = parse_track(session, songs, link); + break; + case LINK_TYPE_PLAYLIST: + parse_result = parse_playlist(session, songs, link); + break; + default: + parse_result = false; + break; + } + + despotify_free_link(link); + if (!parse_result) + return nullptr; + + songs.reverse(); + return new MemorySongEnumerator(std::move(songs)); +} + +static const char *const despotify_schemes[] = { + "spt", + nullptr +}; + +const struct playlist_plugin despotify_playlist_plugin = { + "despotify", + + nullptr, + nullptr, + despotify_playlist_open_uri, + nullptr, + + despotify_schemes, + nullptr, + nullptr, +}; diff --git a/src/playlist/plugins/DespotifyPlaylistPlugin.hxx b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx new file mode 100644 index 000000000..6acfd40f4 --- /dev/null +++ b/src/playlist/plugins/DespotifyPlaylistPlugin.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_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX +#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin despotify_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx new file mode 100644 index 000000000..2e903ae03 --- /dev/null +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Playlist plugin that reads embedded cue sheets from the "CUESHEET" + * tag of a music file. + */ + +#include "config.h" +#include "EmbeddedCuePlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" +#include "../cue/CueParser.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagId3.hxx" +#include "tag/ApeTag.hxx" +#include "DetachedSong.hxx" +#include "TagFile.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "util/ASCII.hxx" + +#include <string.h> + +class EmbeddedCuePlaylist final : public SongEnumerator { +public: + /** + * This is an override for the CUE's "FILE". An embedded CUE + * sheet must always point to the song file it is contained + * in. + */ + std::string filename; + + /** + * The value of the file's "CUESHEET" tag. + */ + std::string cuesheet; + + /** + * The offset of the next line within "cuesheet". + */ + char *next; + + CueParser *parser; + +public: + EmbeddedCuePlaylist() + :parser(nullptr) { + } + + virtual ~EmbeddedCuePlaylist() { + delete parser; + } + + virtual DetachedSong *NextSong() override; +}; + +static void +embcue_tag_pair(const char *name, const char *value, void *ctx) +{ + EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx; + + if (playlist->cuesheet.empty() && + StringEqualsCaseASCII(name, "cuesheet")) + playlist->cuesheet = value; +} + +static const struct tag_handler embcue_tag_handler = { + nullptr, + nullptr, + embcue_tag_pair, +}; + +static SongEnumerator * +embcue_playlist_open_uri(const char *uri, + gcc_unused Mutex &mutex, + gcc_unused Cond &cond) +{ + if (!PathTraitsUTF8::IsAbsolute(uri)) + /* only local files supported */ + return nullptr; + + const auto path_fs = AllocatedPath::FromUTF8(uri); + if (path_fs.IsNull()) + return nullptr; + + const auto playlist = new EmbeddedCuePlaylist(); + + tag_file_scan(path_fs, embcue_tag_handler, playlist); + if (playlist->cuesheet.empty()) { + tag_ape_scan2(path_fs, &embcue_tag_handler, playlist); + if (playlist->cuesheet.empty()) + tag_id3_scan(path_fs, &embcue_tag_handler, playlist); + } + + if (playlist->cuesheet.empty()) { + /* no "CUESHEET" tag found */ + delete playlist; + return nullptr; + } + + playlist->filename = PathTraitsUTF8::GetBase(uri); + + playlist->next = &playlist->cuesheet[0]; + playlist->parser = new CueParser(); + + return playlist; +} + +DetachedSong * +EmbeddedCuePlaylist::NextSong() +{ + DetachedSong *song = parser->Get(); + if (song != nullptr) + return song; + + while (*next != 0) { + const char *line = next; + char *eol = strpbrk(next, "\r\n"); + if (eol != nullptr) { + /* null-terminate the line */ + *eol = 0; + next = eol + 1; + } else + /* last line; put the "next" pointer to the + end of the buffer */ + next += strlen(line); + + parser->Feed(line); + song = parser->Get(); + if (song != nullptr) { + song->SetURI(filename); + return song; + } + } + + parser->Finish(); + song = parser->Get(); + if (song != nullptr) + song->SetURI(filename); + return song; +} + +static const char *const embcue_playlist_suffixes[] = { + /* a few codecs that are known to be supported; there are + probably many more */ + "flac", + "mp3", "mp2", + "mp4", "mp4a", "m4b", + "ape", + "wv", + "ogg", "oga", + nullptr +}; + +const struct playlist_plugin embcue_playlist_plugin = { + "cue", + + nullptr, + nullptr, + embcue_playlist_open_uri, + nullptr, + + embcue_playlist_suffixes, + nullptr, + nullptr, +}; diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx new file mode 100644 index 000000000..5eedf3f13 --- /dev/null +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.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_EMBCUE_PLAYLIST_PLUGIN_HXX +#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin embcue_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx new file mode 100644 index 000000000..15e8125e3 --- /dev/null +++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx @@ -0,0 +1,148 @@ +/* + * 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 "ExtM3uPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" +#include "input/TextInputStream.hxx" + +#include <string.h> +#include <stdlib.h> + +class ExtM3uPlaylist final : public SongEnumerator { + TextInputStream tis; + +public: + ExtM3uPlaylist(InputStream &is) + :tis(is) { + } + + bool CheckFirstLine() { + const char *line = tis.ReadLine(); + return line != nullptr && strcmp(line, "#EXTM3U") == 0; + } + + virtual DetachedSong *NextSong() override; +}; + +static SongEnumerator * +extm3u_open_stream(InputStream &is) +{ + ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is); + + if (!playlist->CheckFirstLine()) { + /* no EXTM3U header: fall back to the plain m3u + plugin */ + delete playlist; + return nullptr; + } + + return playlist; +} + +/** + * Parse a EXTINF line. + * + * @param line the rest of the input line after the colon + */ +static Tag +extm3u_parse_tag(const char *line) +{ + long duration; + char *endptr; + const char *name; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return Tag(); + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = StripLeft(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return Tag(); + + TagBuilder tag; + tag.SetTime(duration); + + /* unfortunately, there is no real specification for the + EXTM3U format, so we must assume that the string after the + comma is opaque, and is just the song name*/ + if (*name != 0) + tag.AddItem(TAG_NAME, name); + + return tag.Commit(); +} + +DetachedSong * +ExtM3uPlaylist::NextSong() +{ + Tag tag; + char *line_s; + + do { + line_s = tis.ReadLine(); + if (line_s == nullptr) + return nullptr; + + StripRight(line_s); + + if (StringStartsWith(line_s, "#EXTINF:")) { + tag = extm3u_parse_tag(line_s + 8); + continue; + } + + line_s = StripLeft(line_s); + } while (line_s[0] == '#' || *line_s == 0); + + return new DetachedSong(line_s, std::move(tag)); +} + +static const char *const extm3u_suffixes[] = { + "m3u", + nullptr +}; + +static const char *const extm3u_mime_types[] = { + "audio/x-mpegurl", + nullptr +}; + +const struct playlist_plugin extm3u_playlist_plugin = { + "extm3u", + + nullptr, + nullptr, + nullptr, + extm3u_open_stream, + + nullptr, + extm3u_suffixes, + extm3u_mime_types, +}; diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx new file mode 100644 index 000000000..5743ded43 --- /dev/null +++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.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_EXTM3U_PLAYLIST_PLUGIN_HXX +#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin extm3u_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx new file mode 100644 index 000000000..a4125bc70 --- /dev/null +++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx @@ -0,0 +1,82 @@ +/* + * 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 "M3uPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" +#include "DetachedSong.hxx" +#include "util/StringUtil.hxx" +#include "input/TextInputStream.hxx" + +class M3uPlaylist final : public SongEnumerator { + TextInputStream tis; + +public: + M3uPlaylist(InputStream &is) + :tis(is) { + } + + virtual DetachedSong *NextSong() override; +}; + +static SongEnumerator * +m3u_open_stream(InputStream &is) +{ + return new M3uPlaylist(is); +} + +DetachedSong * +M3uPlaylist::NextSong() +{ + char *line_s; + + do { + line_s = tis.ReadLine(); + if (line_s == nullptr) + return nullptr; + + line_s = Strip(line_s); + } while (line_s[0] == '#' || *line_s == 0); + + return new DetachedSong(line_s); +} + +static const char *const m3u_suffixes[] = { + "m3u", + nullptr +}; + +static const char *const m3u_mime_types[] = { + "audio/x-mpegurl", + nullptr +}; + +const struct playlist_plugin m3u_playlist_plugin = { + "m3u", + + nullptr, + nullptr, + nullptr, + m3u_open_stream, + + nullptr, + m3u_suffixes, + m3u_mime_types, +}; diff --git a/src/playlist/plugins/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx new file mode 100644 index 000000000..f1ad14069 --- /dev/null +++ b/src/playlist/plugins/M3uPlaylistPlugin.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_M3U_PLAYLIST_PLUGIN_HXX +#define MPD_M3U_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin m3u_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx new file mode 100644 index 000000000..7df7d134b --- /dev/null +++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx @@ -0,0 +1,164 @@ +/* + * 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 "PlsPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "input/InputStream.hxx" +#include "DetachedSong.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string> +#include <stdio.h> + +#include <stdio.h> + +static constexpr Domain pls_domain("pls"); + +static void +pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs) +{ + gchar *value; + GError *error = nullptr; + int num_entries = g_key_file_get_integer(keyfile, "playlist", + "NumberOfEntries", &error); + if (error) { + FormatError(pls_domain, + "Invalid PLS file: '%s'", error->message); + g_error_free(error); + error = nullptr; + + /* Hack to work around shoutcast failure to comform to spec */ + num_entries = g_key_file_get_integer(keyfile, "playlist", + "numberofentries", &error); + if (error) { + g_error_free(error); + error = nullptr; + } + } + + for (; num_entries > 0; --num_entries) { + char key[64]; + sprintf(key, "File%u", num_entries); + char *uri = g_key_file_get_string(keyfile, "playlist", key, + &error); + if(error) { + FormatError(pls_domain, "Invalid PLS entry %s: '%s'", + key, error->message); + g_error_free(error); + return; + } + + TagBuilder tag; + + sprintf(key, "Title%u", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + nullptr); + if (value != nullptr) + tag.AddItem(TAG_TITLE, value); + + g_free(value); + + sprintf(key, "Length%u", num_entries); + int length = g_key_file_get_integer(keyfile, "playlist", key, + nullptr); + if (length > 0) + tag.SetTime(length); + + songs.emplace_front(uri, tag.Commit()); + g_free(uri); + } + +} + +static SongEnumerator * +pls_open_stream(InputStream &is) +{ + GError *error = nullptr; + Error error2; + + std::string kf_data; + + do { + char buffer[1024]; + size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2); + if (nbytes == 0) { + if (error2.IsDefined()) { + LogError(error2); + return nullptr; + } + + break; + } + + kf_data.append(buffer, nbytes); + /* Limit to 64k */ + } while (kf_data.length() < 65536); + + if (kf_data.empty()) { + LogWarning(pls_domain, "KeyFile parser failed: No Data"); + return nullptr; + } + + GKeyFile *keyfile = g_key_file_new(); + if (!g_key_file_load_from_data(keyfile, + kf_data.data(), kf_data.length(), + G_KEY_FILE_NONE, &error)) { + FormatError(pls_domain, + "KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return nullptr; + } + + std::forward_list<DetachedSong> songs; + pls_parser(keyfile, songs); + g_key_file_free(keyfile); + + return new MemorySongEnumerator(std::move(songs)); +} + +static const char *const pls_suffixes[] = { + "pls", + nullptr +}; + +static const char *const pls_mime_types[] = { + "audio/x-scpls", + nullptr +}; + +const struct playlist_plugin pls_playlist_plugin = { + "pls", + + nullptr, + nullptr, + nullptr, + pls_open_stream, + + nullptr, + pls_suffixes, + pls_mime_types, +}; diff --git a/src/playlist/plugins/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx new file mode 100644 index 000000000..1a3f33873 --- /dev/null +++ b/src/playlist/plugins/PlsPlaylistPlugin.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_PLS_PLAYLIST_PLUGIN_HXX +#define MPD_PLS_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin pls_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx new file mode 100644 index 000000000..6f9aad54b --- /dev/null +++ b/src/playlist/plugins/RssPlaylistPlugin.cxx @@ -0,0 +1,185 @@ +/* + * 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 "RssPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "tag/TagBuilder.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Log.hxx" + +/** + * This is the state object for the GLib XML parser. + */ +struct RssParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<DetachedSong> songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ITEM, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + TagType tag_type; + + /** + * The current song URI. It is set by the "enclosure" + * element. + */ + std::string location; + + TagBuilder tag_builder; + + RssParser() + :state(ROOT) {} +}; + +static void XMLCALL +rss_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + if (StringEqualsCaseASCII(element_name, "item")) { + parser->state = RssParser::ITEM; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case RssParser::ITEM: + if (StringEqualsCaseASCII(element_name, "enclosure")) { + const char *href = + ExpatParser::GetAttributeCase(atts, "url"); + if (href != nullptr) + parser->location = href; + } else if (StringEqualsCaseASCII(element_name, "title")) + parser->tag_type = TAG_TITLE; + else if (StringEqualsCaseASCII(element_name, "itunes:author")) + parser->tag_type = TAG_ARTIST; + + break; + } +} + +static void XMLCALL +rss_end_element(void *user_data, const XML_Char *element_name) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (StringEqualsCaseASCII(element_name, "item")) { + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); + + parser->state = RssParser::ROOT; + } else + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void XMLCALL +rss_char_data(void *user_data, const XML_Char *s, int len) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, len); + + break; + } +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +rss_open_stream(InputStream &is) +{ + RssParser parser; + + { + ExpatParser expat(&parser); + expat.SetElementHandler(rss_start_element, rss_end_element); + expat.SetCharacterDataHandler(rss_char_data); + + Error error; + if (!expat.Parse(is, error)) { + LogError(error); + return nullptr; + } + } + + parser.songs.reverse(); + return new MemorySongEnumerator(std::move(parser.songs)); +} + +static const char *const rss_suffixes[] = { + "rss", + nullptr +}; + +static const char *const rss_mime_types[] = { + "application/rss+xml", + "text/xml", + nullptr +}; + +const struct playlist_plugin rss_playlist_plugin = { + "rss", + + nullptr, + nullptr, + nullptr, + rss_open_stream, + + nullptr, + rss_suffixes, + rss_mime_types, +}; diff --git a/src/playlist/plugins/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx new file mode 100644 index 000000000..a00a5a898 --- /dev/null +++ b/src/playlist/plugins/RssPlaylistPlugin.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_RSS_PLAYLIST_PLUGIN_HXX +#define MPD_RSS_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin rss_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx new file mode 100644 index 000000000..d9f62300b --- /dev/null +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx @@ -0,0 +1,401 @@ +/* + * 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 "SoundCloudPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "config/ConfigData.hxx" +#include "input/InputStream.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <yajl/yajl_parse.h> + +#include <string> + +#include <string.h> + +static struct { + std::string apikey; +} soundcloud_config; + +static constexpr Domain soundcloud_domain("soundcloud"); + +static bool +soundcloud_init(const config_param ¶m) +{ + // APIKEY for MPD application, registered under DarkFox' account. + soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8"); + if (soundcloud_config.apikey.empty()) { + LogDebug(soundcloud_domain, + "disabling the soundcloud playlist plugin " + "because API key is not set"); + return false; + } + + return true; +} + +/** + * Construct a full soundcloud resolver URL from the given fragment. + * @param uri uri of a soundcloud page (or just the path) + * @return Constructed URL. Must be freed with g_free. + */ +static char * +soundcloud_resolve(const char* uri) +{ + char *u, *ru; + + if (StringStartsWith(uri, "https://")) { + u = g_strdup(uri); + } else if (StringStartsWith(uri, "soundcloud.com")) { + u = g_strconcat("https://", uri, nullptr); + } else { + /* assume it's just a path on soundcloud.com */ + u = g_strconcat("https://soundcloud.com/", uri, nullptr); + } + + ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", + soundcloud_config.apikey.c_str(), nullptr); + g_free(u); + + return ru; +} + +/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ + +enum key { + Duration, + Title, + Stream_URL, + Other, +}; + +const char* key_str[] = { + "duration", + "title", + "stream_url", + nullptr, +}; + +struct parse_data { + int key; + char* stream_url; + long duration; + char* title; + int got_url; /* nesting level of last stream_url */ + + std::forward_list<DetachedSong> songs; +}; + +static int +handle_integer(void *ctx, + long +#ifndef HAVE_YAJL1 + long +#endif + intval) +{ + struct parse_data *data = (struct parse_data *) ctx; + + switch (data->key) { + case Duration: + data->duration = intval; + break; + default: + break; + } + + return 1; +} + +static int +handle_string(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + const char *s = (const char *) stringval; + + switch (data->key) { + case Title: + g_free(data->title); + data->title = g_strndup(s, stringlen); + break; + case Stream_URL: + g_free(data->stream_url); + data->stream_url = g_strndup(s, stringlen); + data->got_url = 1; + break; + default: + break; + } + + return 1; +} + +static int +handle_mapkey(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + + int i; + data->key = Other; + + for (i = 0; i < Other; ++i) { + if (memcmp((const char *)stringval, key_str[i], stringlen) == 0) { + data->key = i; + break; + } + } + + return 1; +} + +static int +handle_start_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 0) + data->got_url++; + + return 1; +} + +static int +handle_end_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 1) { + data->got_url--; + return 1; + } + + if (data->got_url == 0) + return 1; + + /* got_url == 1, track finished, make it into a song */ + data->got_url = 0; + + char *u = g_strconcat(data->stream_url, "?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + + TagBuilder tag; + tag.SetTime(data->duration / 1000); + if (data->title != nullptr) + tag.AddItem(TAG_NAME, data->title); + + data->songs.emplace_front(u, tag.Commit()); + g_free(u); + + return 1; +} + +static yajl_callbacks parse_callbacks = { + nullptr, + nullptr, + handle_integer, + nullptr, + nullptr, + handle_string, + handle_start_map, + handle_mapkey, + handle_end_map, + nullptr, + nullptr, +}; + +/** + * Read JSON data and parse it using the given YAJL parser. + * @param url URL of the JSON data. + * @param hand YAJL parser handle. + * @return -1 on error, 0 on success. + */ +static int +soundcloud_parse_json(const char *url, yajl_handle hand, + Mutex &mutex, Cond &cond) +{ + Error error; + InputStream *input_stream = InputStream::OpenReady(url, mutex, cond, + error); + if (input_stream == nullptr) { + if (error.IsDefined()) + LogError(error); + return -1; + } + + mutex.lock(); + + yajl_status stat; + int done = 0; + + while (!done) { + char buffer[4096]; + unsigned char *ubuffer = (unsigned char *)buffer; + const size_t nbytes = + input_stream->Read(buffer, sizeof(buffer), error); + if (nbytes == 0) { + if (error.IsDefined()) + LogError(error); + + if (input_stream->IsEOF()) { + done = true; + } else { + mutex.unlock(); + delete input_stream; + return -1; + } + } + + if (done) { +#ifdef HAVE_YAJL1 + stat = yajl_parse_complete(hand); +#else + stat = yajl_complete_parse(hand); +#endif + } else + stat = yajl_parse(hand, ubuffer, nbytes); + + if (stat != yajl_status_ok +#ifdef HAVE_YAJL1 + && stat != yajl_status_insufficient_data +#endif + ) + { + unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); + LogError(soundcloud_domain, (const char *)str); + yajl_free_error(hand, str); + break; + } + } + + mutex.unlock(); + delete input_stream; + + return 0; +} + +/** + * Parse a soundcloud:// URL and create a playlist. + * @param uri A soundcloud URL. Accepted forms: + * soundcloud://track/<track-id> + * soundcloud://playlist/<playlist-id> + * soundcloud://url/<url or path of soundcloud page> + */ +static SongEnumerator * +soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) +{ + assert(memcmp(uri, "soundcloud://", 13) == 0); + uri += 13; + + char *u = nullptr; + if (memcmp(uri, "track/", 6) == 0) { + const char *rest = uri + 6; + u = g_strconcat("https://api.soundcloud.com/tracks/", + rest, ".json?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "playlist/", 9) == 0) { + const char *rest = uri + 9; + u = g_strconcat("https://api.soundcloud.com/playlists/", + rest, ".json?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "user/", 5) == 0) { + const char *rest = uri + 5; + u = g_strconcat("https://api.soundcloud.com/users/", + rest, "/tracks.json?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "search/", 7) == 0) { + const char *rest = uri + 7; + u = g_strconcat("https://api.soundcloud.com/tracks.json?q=", + rest, "&client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "url/", 4) == 0) { + const char *rest = uri + 4; + /* Translate to soundcloud resolver call. libcurl will automatically + follow the redirect to the right resource. */ + u = soundcloud_resolve(rest); + } + + if (u == nullptr) { + LogWarning(soundcloud_domain, "unknown soundcloud URI"); + return nullptr; + } + + struct parse_data data; + data.got_url = 0; + data.title = nullptr; + data.stream_url = nullptr; +#ifdef HAVE_YAJL1 + yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, + &data); +#else + yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data); +#endif + + int ret = soundcloud_parse_json(u, hand, mutex, cond); + + g_free(u); + yajl_free(hand); + g_free(data.title); + g_free(data.stream_url); + + if (ret == -1) + return nullptr; + + data.songs.reverse(); + return new MemorySongEnumerator(std::move(data.songs)); +} + +static const char *const soundcloud_schemes[] = { + "soundcloud", + nullptr +}; + +const struct playlist_plugin soundcloud_playlist_plugin = { + "soundcloud", + + soundcloud_init, + nullptr, + soundcloud_open_uri, + nullptr, + + soundcloud_schemes, + nullptr, + nullptr, +}; + + diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx new file mode 100644 index 000000000..b355b477a --- /dev/null +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.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_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX +#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin soundcloud_playlist_plugin; + +#endif diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx new file mode 100644 index 000000000..5b6010b53 --- /dev/null +++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx @@ -0,0 +1,234 @@ +/* + * 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 "XspfPlaylistPlugin.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Log.hxx" + +#include <string.h> + +static constexpr Domain xspf_domain("xspf"); + +/** + * This is the state object for the GLib XML parser. + */ +struct XspfParser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + std::forward_list<DetachedSong> songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, PLAYLIST, TRACKLIST, TRACK, + LOCATION, + } state; + + /** + * The current tag within the "track" element. This is only + * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + TagType tag_type; + + /** + * The current song URI. It is set by the "location" element. + */ + std::string location; + + TagBuilder tag_builder; + + XspfParser() + :state(ROOT) {} +}; + +static void XMLCALL +xspf_start_element(void *user_data, const XML_Char *element_name, + gcc_unused const XML_Char **atts) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + if (strcmp(element_name, "playlist") == 0) + parser->state = XspfParser::PLAYLIST; + + break; + + case XspfParser::PLAYLIST: + if (strcmp(element_name, "trackList") == 0) + parser->state = XspfParser::TRACKLIST; + + break; + + case XspfParser::TRACKLIST: + if (strcmp(element_name, "track") == 0) { + parser->state = XspfParser::TRACK; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case XspfParser::TRACK: + if (strcmp(element_name, "location") == 0) + parser->state = XspfParser::LOCATION; + else if (strcmp(element_name, "title") == 0) + parser->tag_type = TAG_TITLE; + else if (strcmp(element_name, "creator") == 0) + /* TAG_COMPOSER would be more correct + according to the XSPF spec */ + parser->tag_type = TAG_ARTIST; + else if (strcmp(element_name, "annotation") == 0) + parser->tag_type = TAG_COMMENT; + else if (strcmp(element_name, "album") == 0) + parser->tag_type = TAG_ALBUM; + else if (strcmp(element_name, "trackNum") == 0) + parser->tag_type = TAG_TRACK; + + break; + + case XspfParser::LOCATION: + break; + } +} + +static void XMLCALL +xspf_end_element(void *user_data, const XML_Char *element_name) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + break; + + case XspfParser::PLAYLIST: + if (strcmp(element_name, "playlist") == 0) + parser->state = XspfParser::ROOT; + + break; + + case XspfParser::TRACKLIST: + if (strcmp(element_name, "tracklist") == 0) + parser->state = XspfParser::PLAYLIST; + + break; + + case XspfParser::TRACK: + if (strcmp(element_name, "track") == 0) { + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); + + parser->state = XspfParser::TRACKLIST; + } else + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + + break; + + case XspfParser::LOCATION: + parser->state = XspfParser::TRACK; + break; + } +} + +static void XMLCALL +xspf_char_data(void *user_data, const XML_Char *s, int len) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + case XspfParser::PLAYLIST: + case XspfParser::TRACKLIST: + break; + + case XspfParser::TRACK: + if (!parser->location.empty() && + parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, len); + + break; + + case XspfParser::LOCATION: + parser->location.assign(s, len); + + break; + } +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +xspf_open_stream(InputStream &is) +{ + XspfParser parser; + + { + ExpatParser expat(&parser); + expat.SetElementHandler(xspf_start_element, xspf_end_element); + expat.SetCharacterDataHandler(xspf_char_data); + + Error error; + if (!expat.Parse(is, error)) { + LogError(error); + return nullptr; + } + } + + parser.songs.reverse(); + return new MemorySongEnumerator(std::move(parser.songs)); +} + +static const char *const xspf_suffixes[] = { + "xspf", + nullptr +}; + +static const char *const xspf_mime_types[] = { + "application/xspf+xml", + nullptr +}; + +const struct playlist_plugin xspf_playlist_plugin = { + "xspf", + + nullptr, + nullptr, + nullptr, + xspf_open_stream, + + nullptr, + xspf_suffixes, + xspf_mime_types, +}; diff --git a/src/playlist/plugins/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx new file mode 100644 index 000000000..6b08a6be6 --- /dev/null +++ b/src/playlist/plugins/XspfPlaylistPlugin.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_XSPF_PLAYLIST_PLUGIN_HXX +#define MPD_XSPF_PLAYLIST_PLUGIN_HXX + +extern const struct playlist_plugin xspf_playlist_plugin; + +#endif diff --git a/src/poison.h b/src/poison.h index c95b5d005..c112f6e19 100644 --- a/src/poison.h +++ b/src/poison.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 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/protocol/Ack.cxx b/src/protocol/Ack.cxx index 3b66764bf..56f0f0b5d 100644 --- a/src/protocol/Ack.cxx +++ b/src/protocol/Ack.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 diff --git a/src/protocol/Ack.hxx b/src/protocol/Ack.hxx index 7d3be2626..e2c4dd9d1 100644 --- a/src/protocol/Ack.hxx +++ b/src/protocol/Ack.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/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx index b13ea3f4e..d2c40bbd6 100644 --- a/src/protocol/ArgParser.cxx +++ b/src/protocol/ArgParser.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 diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx index ea28de79e..f7f9531d0 100644 --- a/src/protocol/ArgParser.hxx +++ b/src/protocol/ArgParser.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/protocol/Result.cxx b/src/protocol/Result.cxx index 17b553c34..3cc5fc33e 100644 --- a/src/protocol/Result.cxx +++ b/src/protocol/Result.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,7 +19,7 @@ #include "config.h" #include "Result.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include <assert.h> diff --git a/src/protocol/Result.hxx b/src/protocol/Result.hxx index 0f7339c1c..0ac9d1e6b 100644 --- a/src/protocol/Result.hxx +++ b/src/protocol/Result.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/queue/IdTable.hxx b/src/queue/IdTable.hxx new file mode 100644 index 000000000..8e445243d --- /dev/null +++ b/src/queue/IdTable.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_ID_TABLE_HXX +#define MPD_ID_TABLE_HXX + +#include "Compiler.h" + +#include <algorithm> + +#include <assert.h> + +/** + * A table that maps id numbers to position numbers. + */ +class IdTable { + unsigned size; + + unsigned next; + + int *data; + +public: + IdTable(unsigned _size):size(_size), next(1), data(new int[size]) { + std::fill_n(data, size, -1); + } + + ~IdTable() { + delete[] data; + } + + int IdToPosition(unsigned id) const { + return id < size + ? data[id] + : -1; + } + + unsigned GenerateId() { + assert(next > 0); + assert(next < size); + + while (true) { + unsigned id = next; + + ++next; + if (next == size) + next = 1; + + if (data[id] < 0) + return id; + } + } + + unsigned Insert(unsigned position) { + unsigned id = GenerateId(); + data[id] = position; + return id; + } + + void Move(unsigned id, unsigned position) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = position; + } + + void Erase(unsigned id) { + assert(id < size); + assert(data[id] >= 0); + + data[id] = -1; + } +}; + +#endif diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx new file mode 100644 index 000000000..b2fd673b4 --- /dev/null +++ b/src/queue/Playlist.cxx @@ -0,0 +1,341 @@ +/* + * 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 "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "DetachedSong.hxx" +#include "Idle.hxx" +#include "Log.hxx" + +#include <assert.h> + +void +playlist::TagModified(DetachedSong &&song) +{ + if (!playing) + return; + + assert(current >= 0); + + DetachedSong ¤t_song = queue.GetOrder(current); + if (song.IsSame(current_song)) + current_song.MoveTagFrom(std::move(song)); + + queue.ModifyAtOrder(current); + queue.IncrementVersion(); + idle_add(IDLE_PLAYLIST); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(playlist &playlist, PlayerControl &pc, + unsigned order) +{ + assert(playlist.queue.IsValidOrder(order)); + + playlist.queued = order; + + const DetachedSong &song = playlist.queue.GetOrder(order); + + FormatDebug(playlist_domain, "queue song %i:\"%s\"", + playlist.queued, song.GetURI()); + + pc.EnqueueSong(new DetachedSong(song)); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(playlist &playlist, PlayerControl &pc) +{ + assert(pc.next_song == nullptr); + assert(playlist.queued >= -1); + + /* queued song has started: copy queued to current, + and notify the clients */ + + int current = playlist.current; + playlist.current = playlist.queued; + playlist.queued = -1; + + if(playlist.queue.consume) + playlist.DeleteOrder(pc, current); + + idle_add(IDLE_PLAYER); +} + +const DetachedSong * +playlist::GetQueuedSong() const +{ + return playing && queued >= 0 + ? &queue.GetOrder(queued) + : nullptr; +} + +void +playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) +{ + if (!playing) + return; + + if (prev == nullptr && bulk_edit) + /* postponed until CommitBulk() to avoid always + queueing the first song that is being added (in + random mode) */ + return; + + assert(!queue.IsEmpty()); + assert((queued < 0) == (prev == nullptr)); + + const int next_order = current >= 0 + ? queue.GetNextOrder(current) + : 0; + + if (next_order == 0 && queue.random && !queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + const unsigned current_position = + queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that the current still points to + the current song, after the song order has been + shuffled */ + current = queue.PositionToOrder(current_position); + } + + const DetachedSong *const next_song = next_order >= 0 + ? &queue.GetOrder(next_order) + : nullptr; + + if (prev != nullptr && next_song != prev) { + /* clear the currently queued song */ + pc.Cancel(); + queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(*this, pc, next_order); + else + queued = next_order; + } +} + +void +playlist::PlayOrder(PlayerControl &pc, int order) +{ + playing = true; + queued = -1; + + const DetachedSong &song = queue.GetOrder(order); + + FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI()); + + pc.Play(new DetachedSong(song)); + current = order; +} + +static void +playlist_resume_playback(playlist &playlist, PlayerControl &pc); + +void +playlist::SyncWithPlayer(PlayerControl &pc) +{ + if (!playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + pc.Lock(); + const PlayerState pc_state = pc.GetState(); + const DetachedSong *pc_next_song = pc.next_song; + pc.Unlock(); + + if (pc_state == PlayerState::STOP) + /* the player thread has stopped: check if playback + should be restarted with the next song. That can + happen if the playlist isn't filling the queue fast + enough */ + playlist_resume_playback(*this, pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == nullptr && queued != -1) + playlist_song_started(*this, pc); + + pc.Lock(); + pc_next_song = pc.next_song; + pc.Unlock(); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == nullptr && queued < 0) + UpdateQueuedSong(pc, nullptr); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(playlist &playlist, PlayerControl &pc) +{ + assert(playlist.playing); + assert(pc.GetState() == PlayerState::STOP); + + const auto error = pc.GetErrorType(); + if (error == PlayerError::NONE) + playlist.error_count = 0; + else + ++playlist.error_count; + + if ((playlist.stop_on_error && error != PlayerError::NONE) || + error == PlayerError::OUTPUT || + playlist.error_count >= playlist.queue.GetLength()) + /* too many errors, or critical error: stop + playback */ + playlist.Stop(pc); + else + /* continue playback at the next song */ + playlist.PlayNext(pc); +} + +void +playlist::SetRepeat(PlayerControl &pc, bool status) +{ + if (status == queue.repeat) + return; + + queue.repeat = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(playlist &playlist) +{ + if (playlist.current >= 0) + /* update playlist.current, order==position now */ + playlist.current = playlist.queue.OrderToPosition(playlist.current); + + playlist.queue.RestoreOrder(); +} + +void +playlist::SetSingle(PlayerControl &pc, bool status) +{ + if (status == queue.single) + return; + + queue.single = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetConsume(bool status) +{ + if (status == queue.consume) + return; + + queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetRandom(PlayerControl &pc, bool status) +{ + if (status == queue.random) + return; + + const DetachedSong *const queued_song = GetQueuedSong(); + + queue.random = status; + + if (queue.random) { + /* shuffle the queue order, but preserve current */ + + const int current_position = playing + ? GetCurrentPosition() + : -1; + + queue.ShuffleOrder(); + + if (current_position >= 0) { + /* make sure the current song is the first in + the order list, so the whole rest of the + playlist is played after that */ + unsigned current_order = + queue.PositionToOrder(current_position); + queue.SwapOrders(0, current_order); + current = 0; + } else + current = -1; + } else + playlist_order(*this); + + UpdateQueuedSong(pc, queued_song); + + idle_add(IDLE_OPTIONS); +} + +int +playlist::GetCurrentPosition() const +{ + return current >= 0 + ? queue.OrderToPosition(current) + : -1; +} + +int +playlist::GetNextPosition() const +{ + if (current < 0) + return -1; + + if (queue.single && queue.repeat) + return queue.OrderToPosition(current); + else if (queue.IsValidOrder(current + 1)) + return queue.OrderToPosition(current + 1); + else if (queue.repeat) + return queue.OrderToPosition(0); + + return -1; +} diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx new file mode 100644 index 000000000..0f73a0513 --- /dev/null +++ b/src/queue/Playlist.hxx @@ -0,0 +1,295 @@ +/* + * 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_PLAYLIST_HXX +#define MPD_PLAYLIST_HXX + +#include "queue/Queue.hxx" +#include "PlaylistError.hxx" + +enum TagType : uint8_t; +struct PlayerControl; +class DetachedSong; +class Database; +class Error; +class SongLoader; + +struct playlist { + /** + * The song queue - it contains the "real" playlist. + */ + struct Queue queue; + + /** + * This value is true if the player is currently playing (or + * should be playing). + */ + bool playing; + + /** + * If true, then any error is fatal; if false, MPD will + * attempt to play the next song on non-fatal errors. During + * seeking, this flag is set. + */ + bool stop_on_error; + + /** + * If true, then a bulk edit has been initiated by + * BeginBulk(), and UpdateQueuedSong() and OnModified() will + * be postponed until CommitBulk() + */ + bool bulk_edit; + + /** + * Has the queue been modified during bulk edit mode? + */ + bool bulk_modified; + + /** + * Number of errors since playback was started. If this + * number exceeds the length of the playlist, MPD gives up, + * because all songs have been tried. + */ + unsigned error_count; + + /** + * The "current song pointer". This is the song which is + * played when we get the "play" command. It is also the song + * which is currently being played. + */ + int current; + + /** + * The "next" song to be played, when the current one + * finishes. The decoder thread may start decoding and + * buffering it, while the "current" song is still playing. + * + * This variable is only valid if #playing is true. + */ + int queued; + + playlist(unsigned max_length) + :queue(max_length), playing(false), + bulk_edit(false), + current(-1), queued(-1) { + } + + ~playlist() { + } + + uint32_t GetVersion() const { + return queue.version; + } + + unsigned GetLength() const { + return queue.GetLength(); + } + + unsigned PositionToId(unsigned position) const { + return queue.PositionToId(position); + } + + gcc_pure + int GetCurrentPosition() const; + + gcc_pure + int GetNextPosition() const; + + /** + * Returns the song object which is currently queued. Returns + * none if there is none (yet?) or if MPD isn't playing. + */ + gcc_pure + const DetachedSong *GetQueuedSong() const; + + /** + * This is the "PLAYLIST" event handler. It is invoked by the + * player thread whenever it requests a new queued song, or + * when it exits. + */ + void SyncWithPlayer(PlayerControl &pc); + +protected: + /** + * Called by all editing methods after a modification. + * Updates the queue version and emits #IDLE_PLAYLIST. + */ + void OnModified(); + + /** + * Updates the "queued song". Calculates the next song + * according to the current one (if MPD isn't playing, it + * takes the first song), and queues this song. Clears the + * old queued song if there was one. + * + * @param prev the song which was previously queued, as + * determined by playlist_get_queued_song() + */ + void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev); + +public: + void BeginBulk(); + void CommitBulk(PlayerControl &pc); + + void Clear(PlayerControl &pc); + + /** + * A tag in the play queue has been modified by the player + * thread. Apply the given song's tag to the current song if + * the song matches. + */ + void TagModified(DetachedSong &&song); + +#ifdef ENABLE_DATABASE + /** + * The database has been modified. Pull all updates. + */ + void DatabaseModified(const Database &db); +#endif + + /** + * @return the new song id or 0 on error + */ + unsigned AppendSong(PlayerControl &pc, + DetachedSong &&song, + Error &error); + + /** + * @return the new song id or 0 on error + */ + unsigned AppendURI(PlayerControl &pc, + const SongLoader &loader, + const char *uri_utf8, + Error &error); + +protected: + void DeleteInternal(PlayerControl &pc, + unsigned song, const DetachedSong **queued_p); + +public: + PlaylistResult DeletePosition(PlayerControl &pc, + unsigned position); + + PlaylistResult DeleteOrder(PlayerControl &pc, + unsigned order) { + return DeletePosition(pc, queue.OrderToPosition(order)); + } + + PlaylistResult DeleteId(PlayerControl &pc, unsigned id); + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + PlaylistResult DeleteRange(PlayerControl &pc, + unsigned start, unsigned end); + + void DeleteSong(PlayerControl &pc, const char *uri); + + void Shuffle(PlayerControl &pc, unsigned start, unsigned end); + + PlaylistResult MoveRange(PlayerControl &pc, + unsigned start, unsigned end, int to); + + PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to); + + PlaylistResult SwapPositions(PlayerControl &pc, + unsigned song1, unsigned song2); + + PlaylistResult SwapIds(PlayerControl &pc, + unsigned id1, unsigned id2); + + PlaylistResult SetPriorityRange(PlayerControl &pc, + unsigned start_position, + unsigned end_position, + uint8_t priority); + + PlaylistResult SetPriorityId(PlayerControl &pc, + unsigned song_id, uint8_t priority); + + /** + * Sets the start_ms and end_ms attributes on the song + * with the specified id. + */ + bool SetSongIdRange(PlayerControl &pc, unsigned id, + unsigned start_ms, unsigned end_ms, + Error &error); + + bool AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error); + bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error); + + void Stop(PlayerControl &pc); + + PlaylistResult PlayPosition(PlayerControl &pc, int position); + + void PlayOrder(PlayerControl &pc, int order); + + PlaylistResult PlayId(PlayerControl &pc, int id); + + void PlayNext(PlayerControl &pc); + + void PlayPrevious(PlayerControl &pc); + + PlaylistResult SeekSongPosition(PlayerControl &pc, + unsigned song_position, + float seek_time); + + PlaylistResult SeekSongId(PlayerControl &pc, + unsigned song_id, float seek_time); + + /** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ + PlaylistResult SeekCurrent(PlayerControl &pc, + float seek_time, bool relative); + + bool GetRepeat() const { + return queue.repeat; + } + + void SetRepeat(PlayerControl &pc, bool new_value); + + bool GetRandom() const { + return queue.random; + } + + void SetRandom(PlayerControl &pc, bool new_value); + + bool GetSingle() const { + return queue.single; + } + + void SetSingle(PlayerControl &pc, bool new_value); + + bool GetConsume() const { + return queue.consume; + } + + void SetConsume(bool new_value); +}; + +#endif diff --git a/src/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx new file mode 100644 index 000000000..db0b8a25d --- /dev/null +++ b/src/queue/PlaylistControl.cxx @@ -0,0 +1,260 @@ +/* + * 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. + */ + +/* + * Functions for controlling playback on the playlist level. + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "DetachedSong.hxx" +#include "Log.hxx" + +void +playlist::Stop(PlayerControl &pc) +{ + if (!playing) + return; + + assert(current >= 0); + + FormatDebug(playlist_domain, "stop"); + pc.Stop(); + queued = -1; + playing = false; + + if (queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + current = queue.PositionToOrder(current_position); + } +} + +PlaylistResult +playlist::PlayPosition(PlayerControl &pc, int song) +{ + pc.ClearError(); + + unsigned i = song; + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue.IsEmpty()) + return PlaylistResult::SUCCESS; + + if (playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc.SetPause(false); + return PlaylistResult::SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = current >= 0 + ? current + : 0; + } else if (!queue.IsValidPosition(song)) + return PlaylistResult::BAD_RANGE; + + if (queue.random) { + if (song >= 0) + /* "i" is currently the song position (which + would be equal to the order number in + no-random mode); convert it to a order + number, because random mode is enabled */ + i = queue.PositionToOrder(song); + + if (!playing) + current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue.SwapOrders(i, current); + i = current; + } + + stop_on_error = false; + error_count = 0; + + PlayOrder(pc, i); + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::PlayId(PlayerControl &pc, int id) +{ + if (id == -1) + return PlayPosition(pc, id); + + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return PlayPosition(pc, song); +} + +void +playlist::PlayNext(PlayerControl &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert(queue.IsValidOrder(current)); + + const int old_current = current; + stop_on_error = false; + + /* determine the next song from the queue's order list */ + + const int next_order = queue.GetNextOrder(current); + if (next_order < 0) { + /* no song after this one: stop playback */ + Stop(pc); + + /* reset "current song" */ + current = -1; + } + else + { + if (next_order == 0 && queue.random) { + /* The queue told us that the next song is the first + song. This means we are in repeat mode. Shuffle + the queue order, so this time, the user hears the + songs in a different than before */ + assert(queue.repeat); + + queue.ShuffleOrder(); + + /* note that current and queued are + now invalid, but PlayOrder() will + discard them anyway */ + } + + PlayOrder(pc, next_order); + } + + /* Consume mode removes each played songs. */ + if (queue.consume) + DeleteOrder(pc, old_current); +} + +void +playlist::PlayPrevious(PlayerControl &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + + int order; + if (current > 0) { + /* play the preceding song */ + order = current - 1; + } else if (queue.repeat) { + /* play the last song in "repeat" mode */ + order = queue.GetLength() - 1; + } else { + /* re-start playing the current song if it's + the first one */ + order = current; + } + + PlayOrder(pc, order); +} + +PlaylistResult +playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) +{ + if (!queue.IsValidPosition(song)) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *queued_song = GetQueuedSong(); + + unsigned i = queue.random + ? queue.PositionToOrder(song) + : song; + + pc.ClearError(); + stop_on_error = true; + error_count = 0; + + if (!playing || (unsigned)current != i) { + /* seeking is not within the current song - prepare + song change */ + + playing = true; + current = i; + + queued_song = nullptr; + } + + if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) { + UpdateQueuedSong(pc, queued_song); + + return PlaylistResult::NOT_PLAYING; + } + + queued = -1; + UpdateQueuedSong(pc, nullptr); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SeekSongPosition(pc, song, seek_time); +} + +PlaylistResult +playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative) +{ + if (!playing) + return PlaylistResult::NOT_PLAYING; + + if (relative) { + const auto status = pc.GetStatus(); + + if (status.state != PlayerState::PLAY && + status.state != PlayerState::PAUSE) + return PlaylistResult::NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return SeekSongPosition(pc, current, seek_time); +} diff --git a/src/queue/PlaylistEdit.cxx b/src/queue/PlaylistEdit.cxx new file mode 100644 index 000000000..2ac015f6f --- /dev/null +++ b/src/queue/PlaylistEdit.cxx @@ -0,0 +1,490 @@ +/* + * 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. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" +#include "SongLoader.hxx" +#include "Idle.hxx" + +#include <stdlib.h> + +void +playlist::OnModified() +{ + if (bulk_edit) { + /* postponed to CommitBulk() */ + bulk_modified = true; + return; + } + + queue.IncrementVersion(); + + idle_add(IDLE_PLAYLIST); +} + +void +playlist::Clear(PlayerControl &pc) +{ + Stop(pc); + + queue.Clear(); + current = -1; + + OnModified(); +} + +void +playlist::BeginBulk() +{ + assert(!bulk_edit); + + bulk_edit = true; + bulk_modified = false; +} + +void +playlist::CommitBulk(PlayerControl &pc) +{ + assert(bulk_edit); + + bulk_edit = false; + if (!bulk_modified) + return; + + if (queued < 0) + /* if no song was queued, UpdateQueuedSong() is being + ignored in "bulk" edit mode; now that we have + shuffled all new songs, we can pick a random one + (instead of always picking the first one that was + added) */ + UpdateQueuedSong(pc, nullptr); + + OnModified(); +} + +unsigned +playlist::AppendSong(PlayerControl &pc, DetachedSong &&song, Error &error) +{ + unsigned id; + + if (queue.IsFull()) { + error.Set(playlist_domain, int(PlaylistResult::TOO_LARGE), + "Playlist is too large"); + return 0; + } + + const DetachedSong *const queued_song = GetQueuedSong(); + + id = queue.Append(std::move(song), 0); + + if (queue.random) { + /* shuffle the new song into the list of remaining + songs to play */ + + unsigned start; + if (queued >= 0) + start = queued + 1; + else + start = current + 1; + if (start < queue.GetLength()) + queue.ShuffleOrderLast(start, queue.GetLength()); + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return id; +} + +unsigned +playlist::AppendURI(PlayerControl &pc, const SongLoader &loader, + const char *uri, + Error &error) +{ + DetachedSong *song = loader.LoadSong(uri, error); + if (song == nullptr) + return 0; + + unsigned result = AppendSong(pc, std::move(*song), error); + delete song; + + return result; +} + +PlaylistResult +playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2) +{ + if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *const queued_song = GetQueuedSong(); + + queue.SwapPositions(song1, song2); + + if (queue.random) { + /* update the queue order, so that current + still points to the current song order */ + + queue.SwapOrders(queue.PositionToOrder(song1), + queue.PositionToOrder(song2)); + } else { + /* correct the "current" song order */ + + if (current == (int)song1) + current = song2; + else if (current == (int)song2) + current = song1; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2) +{ + int song1 = queue.IdToPosition(id1); + int song2 = queue.IdToPosition(id2); + + if (song1 < 0 || song2 < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SwapPositions(pc, song1, song2); +} + +PlaylistResult +playlist::SetPriorityRange(PlayerControl &pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= GetLength()) + return PlaylistResult::BAD_RANGE; + + if (end > GetLength()) + end = GetLength(); + + if (start >= end) + return PlaylistResult::SUCCESS; + + /* remember "current" and "queued" */ + + const int current_position = GetCurrentPosition(); + const DetachedSong *const queued_song = GetQueuedSong(); + + /* apply the priority changes */ + + queue.SetPriorityRange(start, end, priority, current); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + current = queue.PositionToOrder(current_position); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SetPriorityId(PlayerControl &pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue.IdToPosition(song_id); + if (song_position < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SetPriorityRange(pc, song_position, song_position + 1, + priority); + +} + +void +playlist::DeleteInternal(PlayerControl &pc, + unsigned song, const DetachedSong **queued_p) +{ + assert(song < GetLength()); + + unsigned songOrder = queue.PositionToOrder(song); + + if (playing && current == (int)songOrder) { + const bool paused = pc.GetState() == PlayerState::PAUSE; + + /* the current song is going to be deleted: see which + song is going to be played instead */ + + current = queue.GetNextOrder(current); + if (current == (int)songOrder) + current = -1; + + if (current >= 0 && !paused) + /* play the song after the deleted one */ + PlayOrder(pc, current); + else { + /* stop the player */ + + pc.Stop(); + playing = false; + } + + *queued_p = nullptr; + } else if (current == (int)songOrder) + /* there's a "current song" but we're not playing + currently - clear "current" */ + current = -1; + + /* now do it: remove the song */ + + queue.DeletePosition(song); + + /* update the "current" and "queued" variables */ + + if (current > (int)songOrder) + current--; +} + +PlaylistResult +playlist::DeletePosition(PlayerControl &pc, unsigned song) +{ + if (song >= queue.GetLength()) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *queued_song = GetQueuedSong(); + + DeleteInternal(pc, song, &queued_song); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end) +{ + if (start >= queue.GetLength()) + return PlaylistResult::BAD_RANGE; + + if (end > queue.GetLength()) + end = queue.GetLength(); + + if (start >= end) + return PlaylistResult::SUCCESS; + + const DetachedSong *queued_song = GetQueuedSong(); + + do { + DeleteInternal(pc, --end, &queued_song); + } while (end != start); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::DeleteId(PlayerControl &pc, unsigned id) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return DeletePosition(pc, song); +} + +void +playlist::DeleteSong(PlayerControl &pc, const char *uri) +{ + for (int i = queue.GetLength() - 1; i >= 0; --i) + if (queue.Get(i).IsURI(uri)) + DeletePosition(pc, i); +} + +PlaylistResult +playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to) +{ + if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) + return PlaylistResult::BAD_RANGE; + + if ((to >= 0 && to + end - start - 1 >= GetLength()) || + (to < 0 && unsigned(abs(to)) > GetLength())) + return PlaylistResult::BAD_RANGE; + + if ((int)start == to) + /* nothing happens */ + return PlaylistResult::SUCCESS; + + const DetachedSong *const queued_song = GetQueuedSong(); + + /* + * (to < 0) => move to offset from current song + * (-playlist.length == to) => move to position BEFORE current song + */ + const int currentSong = GetCurrentPosition(); + if (to < 0) { + if (currentSong < 0) + /* can't move relative to current song, + because there is no current song */ + return PlaylistResult::BAD_RANGE; + + if (start <= (unsigned)currentSong && (unsigned)currentSong < end) + /* no-op, can't be moved to offset of itself */ + return PlaylistResult::SUCCESS; + to = (currentSong + abs(to)) % GetLength(); + if (start < (unsigned)to) + to--; + } + + queue.MoveRange(start, end, to); + + if (!queue.random) { + /* update current/queued */ + if ((int)start <= current && (unsigned)current < end) + current += to - start; + else if (current >= (int)end && current <= to) + current -= end - start; + else if (current >= to && current < (int)start) + current += end - start; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::MoveId(PlayerControl &pc, unsigned id1, int to) +{ + int song = queue.IdToPosition(id1); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return MoveRange(pc, song, song + 1, to); +} + +void +playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) +{ + if (end > GetLength()) + /* correct the "end" offset */ + end = GetLength(); + + if (start + 1 >= end) + /* needs at least two entries. */ + return; + + const DetachedSong *const queued_song = GetQueuedSong(); + if (playing && current >= 0) { + unsigned current_position = queue.OrderToPosition(current); + + if (current_position >= start && current_position < end) { + /* put current playing song first */ + queue.SwapPositions(start, current_position); + + if (queue.random) { + current = queue.PositionToOrder(start); + } else + current = start; + + /* start shuffle after the current song */ + start++; + } + } else { + /* no playback currently: reset current */ + + current = -1; + } + + queue.ShuffleRange(start, end); + + UpdateQueuedSong(pc, queued_song); + OnModified(); +} + +bool +playlist::SetSongIdRange(PlayerControl &pc, unsigned id, + unsigned start_ms, unsigned end_ms, + Error &error) +{ + assert(end_ms == 0 || start_ms < end_ms); + + int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + if (playing) { + if (position == current) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit the current song"); + return false; + } + + if (position == queued) { + /* if we're manipulating the "queued" song, + the decoder thread may be decoding it + already; cancel that */ + pc.Cancel(); + queued = -1; + } + } + + DetachedSong &song = queue.Get(position); + if (song.GetTag().time > 0) { + /* validate the offsets */ + + const unsigned duration = song.GetTag().time; + if (start_ms / 1000u > duration) { + error.Set(playlist_domain, + int(PlaylistResult::BAD_RANGE), + "Invalid start offset"); + return false; + } + + if (end_ms / 1000u > duration) + end_ms = 0; + } + + /* edit it */ + song.SetStartMS(start_ms); + song.SetEndMS(end_ms); + + /* announce the change to all interested subsystems */ + UpdateQueuedSong(pc, nullptr); + queue.ModifyAtPosition(position); + OnModified(); + return true; +} diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx new file mode 100644 index 000000000..18d8c9dc5 --- /dev/null +++ b/src/queue/PlaylistState.cxx @@ -0,0 +1,244 @@ +/* + * 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. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#include "config.h" +#include "PlaylistState.hxx" +#include "PlaylistError.hxx" +#include "Playlist.hxx" +#include "queue/QueueSave.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "PlayerControl.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "fs/Limits.hxx" +#include "util/CharUtil.hxx" +#include "util/StringUtil.hxx" +#include "Log.hxx" + +#include <string.h> +#include <stdlib.h> + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_SINGLE "single: " +#define PLAYLIST_STATE_FILE_CONSUME "consume: " +#define PLAYLIST_STATE_FILE_CURRENT "current: " +#define PLAYLIST_STATE_FILE_TIME "time: " +#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " +#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" +#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" + +#define PLAYLIST_STATE_FILE_STATE_PLAY "play" +#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" +#define PLAYLIST_STATE_FILE_STATE_STOP "stop" + +#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX + +void +playlist_state_save(BufferedOutputStream &os, const struct playlist &playlist, + PlayerControl &pc) +{ + const auto player_status = pc.GetStatus(); + + os.Write(PLAYLIST_STATE_FILE_STATE); + + if (playlist.playing) { + switch (player_status.state) { + case PlayerState::PAUSE: + os.Write(PLAYLIST_STATE_FILE_STATE_PAUSE "\n"); + break; + default: + os.Write(PLAYLIST_STATE_FILE_STATE_PLAY "\n"); + } + os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist.queue.OrderToPosition(playlist.current)); + os.Format(PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + os.Write(PLAYLIST_STATE_FILE_STATE_STOP "\n"); + + if (playlist.current >= 0) + os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist.queue.OrderToPosition(playlist.current)); + } + + os.Format(PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random); + os.Format(PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat); + os.Format(PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single); + os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume); + os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)pc.GetCrossFade()); + os.Format(PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc.GetMixRampDb()); + os.Format(PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc.GetMixRampDelay()); + os.Write(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n"); + queue_save(os, playlist.queue); + os.Write(PLAYLIST_STATE_FILE_PLAYLIST_END "\n"); +} + +static void +playlist_state_load(TextFile &file, const SongLoader &song_loader, + struct playlist &playlist) +{ + const char *line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, "No playlist in state file"); + return; + } + + while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(file, song_loader, line, playlist.queue); + + line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, + "'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); + break; + } + } + + playlist.queue.IncrementVersion(); +} + +bool +playlist_state_restore(const char *line, TextFile &file, + const SongLoader &song_loader, + struct playlist &playlist, PlayerControl &pc) +{ + int current = -1; + int seek_time = 0; + bool random_mode = false; + + if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE)) + return false; + + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + PlayerState state; + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PlayerState::PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PlayerState::PAUSE; + else + state = PlayerState::STOP; + + while ((line = file.ReadLine()) != nullptr) { + if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) { + seek_time = + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) { + playlist.SetRepeat(pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) { + playlist.SetSingle(pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) { + playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY); + + /* this check discards "nan" which was used + prior to MPD 0.18 */ + if (IsDigitASCII(*p)) + pc.SetMixRampDelay(atof(p)); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) { + random_mode = + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), + "1") == 0; + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line + [strlen + (PLAYLIST_STATE_FILE_CURRENT)])); + } else if (StringStartsWith(line, + PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { + playlist_state_load(file, song_loader, playlist); + } + } + + playlist.SetRandom(pc, random_mode); + + if (!playlist.queue.IsEmpty()) { + if (!playlist.queue.IsValidPosition(current)) + current = 0; + + if (state == PlayerState::PLAY && + config_get_bool(CONF_RESTORE_PAUSED, false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PlayerState::PAUSE; + + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PlayerState::STOP) + pc.UpdateAudio(); + + if (state == PlayerState::STOP /* && config_option */) + playlist.current = current; + else if (seek_time == 0) + playlist.PlayPosition(pc, current); + else + playlist.SeekSongPosition(pc, current, seek_time); + + if (state == PlayerState::PAUSE) + pc.Pause(); + } + + return true; +} + +unsigned +playlist_state_get_hash(const playlist &playlist, + PlayerControl &pc) +{ + const auto player_status = pc.GetStatus(); + + return playlist.queue.version ^ + (player_status.state != PlayerState::STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist.current >= 0 + ? (playlist.queue.OrderToPosition(playlist.current) << 16) + : 0) ^ + ((int)pc.GetCrossFade() << 20) ^ + (unsigned(player_status.state) << 24) ^ + (playlist.queue.random << 27) ^ + (playlist.queue.repeat << 28) ^ + (playlist.queue.single << 29) ^ + (playlist.queue.consume << 30) ^ + (playlist.queue.random << 31); +} diff --git a/src/queue/PlaylistState.hxx b/src/queue/PlaylistState.hxx new file mode 100644 index 000000000..3211b1178 --- /dev/null +++ b/src/queue/PlaylistState.hxx @@ -0,0 +1,53 @@ +/* + * 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. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX + +struct playlist; +struct PlayerControl; +class TextFile; +class BufferedOutputStream; +class SongLoader; + +void +playlist_state_save(BufferedOutputStream &os, const playlist &playlist, + PlayerControl &pc); + +bool +playlist_state_restore(const char *line, TextFile &file, + const SongLoader &song_loader, + playlist &playlist, PlayerControl &pc); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const playlist &playlist, + PlayerControl &c); + +#endif diff --git a/src/queue/PlaylistTag.cxx b/src/queue/PlaylistTag.cxx new file mode 100644 index 000000000..556e7f4e9 --- /dev/null +++ b/src/queue/PlaylistTag.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. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" + +bool +playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + tag.AddItem(tag_type, value); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} + +bool +playlist::ClearSongIdTag(unsigned id, TagType tag_type, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) + tag.RemoveAll(); + else + tag.RemoveType(tag_type); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} diff --git a/src/queue/PlaylistUpdate.cxx b/src/queue/PlaylistUpdate.cxx new file mode 100644 index 000000000..8876711ef --- /dev/null +++ b/src/queue/PlaylistUpdate.cxx @@ -0,0 +1,73 @@ +/* + * 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 "Playlist.hxx" +#include "db/Interface.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "Idle.hxx" +#include "util/Error.hxx" + +static bool +UpdatePlaylistSong(const Database &db, DetachedSong &song) +{ + if (!song.IsInDatabase() || !song.IsFile()) + /* only update Songs instances that are "detached" + from the Database */ + return false; + + const LightSong *original = db.GetSong(song.GetURI(), IgnoreError()); + if (original == nullptr) + /* not found - shouldn't happen, because the update + thread should ensure that all stale Song instances + have been purged */ + return false; + + if (original->mtime == song.GetLastModified()) { + /* not modified */ + db.ReturnSong(original); + return false; + } + + song.SetLastModified(original->mtime); + song.SetTag(*original->tag); + + db.ReturnSong(original); + return true; +} + +void +playlist::DatabaseModified(const Database &db) +{ + bool modified = false; + + for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) { + if (UpdatePlaylistSong(db, queue.Get(i))) { + queue.ModifyAtPosition(i); + modified = true; + } + } + + if (modified) { + queue.IncrementVersion(); + idle_add(IDLE_PLAYLIST); + } +} diff --git a/src/queue/Queue.cxx b/src/queue/Queue.cxx new file mode 100644 index 000000000..99b545ab1 --- /dev/null +++ b/src/queue/Queue.cxx @@ -0,0 +1,482 @@ +/* + * 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 "Queue.hxx" +#include "DetachedSong.hxx" + +Queue::Queue(unsigned _max_length) + :max_length(_max_length), length(0), + version(1), + items(new Item[max_length]), + order(new unsigned[max_length]), + id_table(max_length * HASH_MULT), + repeat(false), + single(false), + consume(false), + random(false) +{ +} + +Queue::~Queue() +{ + Clear(); + + delete[] items; + delete[] order; +} + +int +Queue::GetNextOrder(unsigned _order) const +{ + assert(_order < length); + + if (single && repeat && !consume) + return _order; + else if (_order + 1 < length) + return _order + 1; + else if (repeat && (_order > 0 || !consume)) + /* restart at first song */ + return 0; + else + /* end of queue */ + return -1; +} + +void +Queue::IncrementVersion() +{ + static unsigned long max = ((uint32_t) 1 << 31) - 1; + + version++; + + if (version >= max) { + for (unsigned i = 0; i < length; i++) + items[i].version = 0; + + version = 1; + } +} + +void +Queue::ModifyAtOrder(unsigned _order) +{ + assert(_order < length); + + unsigned position = order[_order]; + ModifyAtPosition(position); +} + +unsigned +Queue::Append(DetachedSong &&song, uint8_t priority) +{ + assert(!IsFull()); + + const unsigned position = length++; + const unsigned id = id_table.Insert(position); + + auto &item = items[position]; + item.song = new DetachedSong(std::move(song)); + item.id = id; + item.version = version; + item.priority = priority; + + order[position] = position; + + return id; +} + +void +Queue::SwapPositions(unsigned position1, unsigned position2) +{ + unsigned id1 = items[position1].id; + unsigned id2 = items[position2].id; + + std::swap(items[position1], items[position2]); + + items[position1].version = version; + items[position2].version = version; + + id_table.Move(id1, position2); + id_table.Move(id2, position1); +} + +void +Queue::MovePostion(unsigned from, unsigned to) +{ + const Item tmp = items[from]; + + /* move songs to one less in from->to */ + + for (unsigned i = from; i < to; i++) + MoveItemTo(i + 1, i); + + /* move songs to one more in to->from */ + + for (unsigned i = from; i > to; i--) + MoveItemTo(i - 1, i); + + /* put song at _to_ */ + + id_table.Move(tmp.id, to); + items[to] = tmp; + items[to].version = version; + + /* now deal with order */ + + if (random) { + for (unsigned i = 0; i < length; i++) { + if (order[i] > from && order[i] <= to) + order[i]--; + else if (order[i] < from && + order[i] >= to) + order[i]++; + else if (from == order[i]) + order[i] = to; + } + } +} + +void +Queue::MoveRange(unsigned start, unsigned end, unsigned to) +{ + Item tmp[end - start]; + // Copy the original block [start,end-1] + for (unsigned i = start; i < end; i++) + tmp[i - start] = items[i]; + + // If to > start, we need to move to-start items to start, starting from end + for (unsigned i = end; i < end + to - start; i++) + MoveItemTo(i, start + i - end); + + // If to < start, we need to move start-to items to newend (= end + to - start), starting from to + // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 + // We have to iterate in this order to avoid writing over something we haven't yet moved + for (int i = start - 1; i >= int(to); i--) + MoveItemTo(i, i + end - start); + + // Copy the original block back in, starting at to. + for (unsigned i = start; i< end; i++) + { + id_table.Move(tmp[i - start].id, to + i - start); + items[to + i - start] = tmp[i-start]; + items[to + i - start].version = version; + } + + if (random) { + // Update the positions in the queue. + // Note that the ranges for these cases are the same as the ranges of + // the loops above. + for (unsigned i = 0; i < length; i++) { + if (order[i] >= end && order[i] < to + end - start) + order[i] -= end - start; + else if (order[i] < start && + order[i] >= to) + order[i] += end - start; + else if (start <= order[i] && order[i] < end) + order[i] += to - start; + } + } +} + +void +Queue::MoveOrder(unsigned from_order, unsigned to_order) +{ + assert(from_order < length); + assert(to_order <= length); + + const unsigned from_position = OrderToPosition(from_order); + + if (from_order < to_order) { + for (unsigned i = from_order; i < to_order; ++i) + order[i] = order[i + 1]; + } else { + for (unsigned i = from_order; i > to_order; --i) + order[i] = order[i - 1]; + } + + order[to_order] = from_position; +} + +void +Queue::DeletePosition(unsigned position) +{ + assert(position < length); + + delete items[position].song; + + const unsigned id = PositionToId(position); + const unsigned _order = PositionToOrder(position); + + --length; + + /* release the song id */ + + id_table.Erase(id); + + /* delete song from songs array */ + + for (unsigned i = position; i < length; i++) + MoveItemTo(i + 1, i); + + /* delete the entry from the order array */ + + for (unsigned i = _order; i < length; i++) + order[i] = order[i + 1]; + + /* readjust values in the order array */ + + for (unsigned i = 0; i < length; i++) + if (order[i] > position) + --order[i]; +} + +void +Queue::Clear() +{ + for (unsigned i = 0; i < length; i++) { + Item *item = &items[i]; + + delete item->song; + + id_table.Erase(item->id); + } + + length = 0; +} + +static void +queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end) +{ + assert(queue != nullptr); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + auto cmp = [queue](unsigned a_pos, unsigned b_pos){ + const Queue::Item &a = queue->items[a_pos]; + const Queue::Item &b = queue->items[b_pos]; + + return a.priority > b.priority; + }; + + std::stable_sort(queue->order + start, queue->order + end, cmp); +} + +void +Queue::ShuffleOrderRange(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + std::shuffle(order + start, order + end, rand); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +Queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end) +{ + assert(random); + assert(start <= end); + assert(end <= length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(this, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = GetOrderPriority(start); + + for (unsigned i = start + 1; i < end; ++i) { + const uint8_t priority = GetOrderPriority(i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + ShuffleOrderRange(group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + ShuffleOrderRange(group_start, end); +} + +void +Queue::ShuffleOrder() +{ + ShuffleOrderRangeWithPriority(0, length); +} + +void +Queue::ShuffleOrderFirst(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(start, distribution(rand)); +} + +void +Queue::ShuffleOrderLast(unsigned start, unsigned end) +{ + rand.AutoCreate(); + + std::uniform_int_distribution<unsigned> distribution(start, end - 1); + SwapOrders(end - 1, distribution(rand)); +} + +void +Queue::ShuffleRange(unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= length); + + rand.AutoCreate(); + + for (unsigned i = start; i < end; i++) { + std::uniform_int_distribution<unsigned> distribution(start, + end - 1); + unsigned ri = distribution(rand); + SwapPositions(i, ri); + } +} + +unsigned +Queue::FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority <= priority && i != exclude_order) + return i; + } + + return length; +} + +unsigned +Queue::CountSamePriority(unsigned start_order, uint8_t priority) const +{ + assert(random); + assert(start_order <= length); + + for (unsigned i = start_order; i < length; ++i) { + const unsigned position = OrderToPosition(i); + const Item *item = &items[position]; + if (item->priority != priority) + return i - start_order; + } + + return length - start_order; +} + +bool +Queue::SetPriority(unsigned position, uint8_t priority, int after_order) +{ + assert(position < length); + + Item *item = &items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = version; + item->priority = priority; + + if (!random) + /* don't reorder if not in random mode */ + return true; + + unsigned _order = PositionToOrder(position); + if (after_order >= 0) { + if (_order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (_order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + OrderToPosition(after_order); + const Item *after_item = + &items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + FindPriorityOrder(after_order + 1, priority, _order); + const unsigned new_order = before_order > _order + ? before_order - 1 + : before_order; + MoveOrder(_order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = CountSamePriority(new_order, priority); + assert(priority_count >= 1); + ShuffleOrderFirst(new_order, new_order + priority_count); + + return true; +} + +bool +Queue::SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(start_position <= end_position); + assert(end_position <= length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)OrderToPosition(after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)PositionToOrder(after_position) + : -1; + + modified |= SetPriority(i, priority, after_order); + } + + return modified; +} diff --git a/src/queue/Queue.hxx b/src/queue/Queue.hxx new file mode 100644 index 000000000..016619e65 --- /dev/null +++ b/src/queue/Queue.hxx @@ -0,0 +1,378 @@ +/* + * 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_QUEUE_HXX +#define MPD_QUEUE_HXX + +#include "Compiler.h" +#include "IdTable.hxx" +#include "util/LazyRandomEngine.hxx" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> + +class DetachedSong; + +/** + * A queue of songs. This is the backend of the playlist: it contains + * an ordered list of songs. + * + * Songs can be addressed in three possible ways: + * + * - the position in the queue + * - the unique id (which stays the same, regardless of moves) + * - the order number (which only differs from "position" in random mode) + */ +struct Queue { + /** + * reserve max_length * HASH_MULT elements in the id + * number space + */ + static constexpr unsigned HASH_MULT = 4; + + /** + * One element of the queue: basically a song plus some queue specific + * information attached. + */ + struct Item { + DetachedSong *song; + + /** the unique id of this item in the queue */ + unsigned id; + + /** when was this item last changed? */ + uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; + }; + + /** configured maximum length of the queue */ + unsigned max_length; + + /** number of songs in the queue */ + unsigned length; + + /** the current version number */ + uint32_t version; + + /** all songs in "position" order */ + Item *items; + + /** map order numbers to positions */ + unsigned *order; + + /** map song ids to positions */ + IdTable id_table; + + /** repeat playback when the end of the queue has been + reached? */ + bool repeat; + + /** play only current song. */ + bool single; + + /** remove each played files. */ + bool consume; + + /** play back songs in random order? */ + bool random; + + /** random number generator for shuffle and random mode */ + LazyRandomEngine rand; + + explicit Queue(unsigned max_length); + + /** + * Deinitializes a queue object. It does not free the queue + * pointer itself. + */ + ~Queue(); + + Queue(const Queue &) = delete; + Queue &operator=(const Queue &) = delete; + + unsigned GetLength() const { + assert(length <= max_length); + + return length; + } + + /** + * Determine if the queue is empty, i.e. there are no songs. + */ + bool IsEmpty() const { + return length == 0; + } + + /** + * Determine if the maximum number of songs has been reached. + */ + bool IsFull() const { + assert(length <= max_length); + + return length >= max_length; + } + + /** + * Is that a valid position number? + */ + bool IsValidPosition(unsigned position) const { + return position < length; + } + + /** + * Is that a valid order number? + */ + bool IsValidOrder(unsigned _order) const { + return _order < length; + } + + int IdToPosition(unsigned id) const { + return id_table.IdToPosition(id); + } + + int PositionToId(unsigned position) const + { + assert(position < length); + + return items[position].id; + } + + gcc_pure + unsigned OrderToPosition(unsigned _order) const { + assert(_order < length); + + return order[_order]; + } + + gcc_pure + unsigned PositionToOrder(unsigned position) const { + assert(position < length); + + for (unsigned i = 0;; ++i) { + assert(i < length); + + if (order[i] == position) + return i; + } + } + + gcc_pure + uint8_t GetPriorityAtPosition(unsigned position) const { + assert(position < length); + + return items[position].priority; + } + + const Item &GetOrderItem(unsigned i) const { + assert(IsValidOrder(i)); + + return items[OrderToPosition(i)]; + } + + uint8_t GetOrderPriority(unsigned i) const { + return GetOrderItem(i).priority; + } + + /** + * Returns the song at the specified position. + */ + DetachedSong &Get(unsigned position) const { + assert(position < length); + + return *items[position].song; + } + + /** + * Returns the song at the specified order number. + */ + DetachedSong &GetOrder(unsigned _order) const { + return Get(OrderToPosition(_order)); + } + + /** + * Is the song at the specified position newer than the specified + * version? + */ + bool IsNewerAtPosition(unsigned position, uint32_t _version) const { + assert(position < length); + + return _version > version || + items[position].version >= _version || + items[position].version == 0; + } + + /** + * Returns the order number following the specified one. This takes + * end of queue and "repeat" mode into account. + * + * @return the next order number, or -1 to stop playback + */ + gcc_pure + int GetNextOrder(unsigned order) const; + + /** + * Increments the queue's version number. This handles integer + * overflow well. + */ + void IncrementVersion(); + + /** + * Marks the specified song as "modified". Call + * IncrementVersion() after all modifications have been made. + * number. + */ + void ModifyAtPosition(unsigned position) { + assert(position < length); + + items[position].version = version; + } + + /** + * Marks the specified song as "modified". Call + * IncrementVersion() after all modifications have been made. + * number. + */ + void ModifyAtOrder(unsigned order); + + /** + * Appends a song to the queue and returns its position. Prior to + * that, the caller must check if the queue is already full. + * + * If a song is not in the database (determined by + * Song::IsInDatabase()), it is freed when removed from the + * queue. + * + * @param priority the priority of this new queue item + */ + unsigned Append(DetachedSong &&song, uint8_t priority); + + /** + * Swaps two songs, addressed by their position. + */ + void SwapPositions(unsigned position1, unsigned position2); + + /** + * Swaps two songs, addressed by their order number. + */ + void SwapOrders(unsigned order1, unsigned order2) { + std::swap(order[order1], order[order2]); + } + + /** + * Moves a song to a new position. + */ + void MovePostion(unsigned from, unsigned to); + + /** + * Moves a range of songs to a new position. + */ + void MoveRange(unsigned start, unsigned end, unsigned to); + + /** + * Removes a song from the playlist. + */ + void DeletePosition(unsigned position); + + /** + * Removes all songs from the playlist. + */ + void Clear(); + + /** + * Initializes the "order" array, and restores "normal" order. + */ + void RestoreOrder() { + for (unsigned i = 0; i < length; ++i) + order[i] = i; + } + + /** + * Shuffle the order of items in the specified range, ignoring + * their priorities. + */ + void ShuffleOrderRange(unsigned start, unsigned end); + + /** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ + void ShuffleOrderRangeWithPriority(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of songs, but does not move them + * physically. This is used in random mode. + */ + void ShuffleOrder(); + + void ShuffleOrderFirst(unsigned start, unsigned end); + + /** + * Shuffles the virtual order of the last song in the specified + * (order) range. This is used in random mode after a song has been + * appended by queue_append(). + */ + void ShuffleOrderLast(unsigned start, unsigned end); + + /** + * Shuffles a (position) range in the queue. The songs are physically + * shuffled, not by using the "order" mapping. + */ + void ShuffleRange(unsigned start, unsigned end); + + bool SetPriority(unsigned position, uint8_t priority, int after_order); + + bool SetPriorityRange(unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + +private: + /** + * Moves a song to a new position in the "order" list. + */ + void MoveOrder(unsigned from_order, unsigned to_order); + + void MoveItemTo(unsigned from, unsigned to) { + unsigned from_id = items[from].id; + + items[to] = items[from]; + items[to].version = version; + id_table.Move(from_id, to); + } + + /** + * Find the first item that has this specified priority or + * higher. + */ + gcc_pure + unsigned FindPriorityOrder(unsigned start_order, uint8_t priority, + unsigned exclude_order) const; + + gcc_pure + unsigned CountSamePriority(unsigned start_order, + uint8_t priority) const; +}; + +#endif diff --git a/src/queue/QueuePrint.cxx b/src/queue/QueuePrint.cxx new file mode 100644 index 000000000..831ecafb9 --- /dev/null +++ b/src/queue/QueuePrint.cxx @@ -0,0 +1,102 @@ +/* + * 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 "QueuePrint.hxx" +#include "Queue.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "client/Client.hxx" + +/** + * Send detailed information about a range of songs in the queue to a + * client. + * + * @param client the client which has requested information + * @param start the index of the first song (including) + * @param end the index of the last song (excluding) + */ +static void +queue_print_song_info(Client &client, const Queue &queue, + unsigned position) +{ + song_print_info(client, queue.Get(position)); + client_printf(client, "Pos: %u\nId: %u\n", + position, queue.PositionToId(position)); + + uint8_t priority = queue.GetPriorityAtPosition(position); + if (priority != 0) + client_printf(client, "Prio: %u\n", priority); +} + +void +queue_print_info(Client &client, const Queue &queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue.GetLength()); + + for (unsigned i = start; i < end; ++i) + queue_print_song_info(client, queue, i); +} + +void +queue_print_uris(Client &client, const Queue &queue, + unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue.GetLength()); + + for (unsigned i = start; i < end; ++i) { + client_printf(client, "%i:", i); + song_print_uri(client, queue.Get(i)); + } +} + +void +queue_print_changes_info(Client &client, const Queue &queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue.GetLength(); i++) { + if (queue.IsNewerAtPosition(i, version)) + queue_print_song_info(client, queue, i); + } +} + +void +queue_print_changes_position(Client &client, const Queue &queue, + uint32_t version) +{ + for (unsigned i = 0; i < queue.GetLength(); i++) + if (queue.IsNewerAtPosition(i, version)) + client_printf(client, "cpos: %i\nId: %i\n", + i, queue.PositionToId(i)); +} + +void +queue_find(Client &client, const Queue &queue, + const SongFilter &filter) +{ + for (unsigned i = 0; i < queue.GetLength(); i++) { + const DetachedSong &song = queue.Get(i); + + if (filter.Match(song)) + queue_print_song_info(client, queue, i); + } +} diff --git a/src/queue/QueuePrint.hxx b/src/queue/QueuePrint.hxx new file mode 100644 index 000000000..1aa876219 --- /dev/null +++ b/src/queue/QueuePrint.hxx @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/* + * This library sends information about songs in the queue to the + * client. + */ + +#ifndef MPD_QUEUE_PRINT_HXX +#define MPD_QUEUE_PRINT_HXX + +#include <stdint.h> + +struct Queue; +class SongFilter; +class Client; + +void +queue_print_info(Client &client, const Queue &queue, + unsigned start, unsigned end); + +void +queue_print_uris(Client &client, const Queue &queue, + unsigned start, unsigned end); + +void +queue_print_changes_info(Client &client, const Queue &queue, + uint32_t version); + +void +queue_print_changes_position(Client &client, const Queue &queue, + uint32_t version); + +void +queue_find(Client &client, const Queue &queue, + const SongFilter &filter); + +#endif diff --git a/src/queue/QueueSave.cxx b/src/queue/QueueSave.cxx new file mode 100644 index 000000000..6d871ac19 --- /dev/null +++ b/src/queue/QueueSave.cxx @@ -0,0 +1,126 @@ +/* + * 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 "QueueSave.hxx" +#include "Queue.hxx" +#include "PlaylistError.hxx" +#include "DetachedSong.hxx" +#include "SongSave.hxx" +#include "SongLoader.hxx" +#include "playlist/PlaylistSong.hxx" +#include "fs/io/TextFile.hxx" +#include "fs/io/BufferedOutputStream.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "fs/Traits.hxx" +#include "Log.hxx" + +#include <stdlib.h> + +#define PRIO_LABEL "Prio: " + +static void +queue_save_database_song(BufferedOutputStream &os, + int idx, const DetachedSong &song) +{ + os.Format("%i:%s\n", idx, song.GetURI()); +} + +static void +queue_save_full_song(BufferedOutputStream &os, const DetachedSong &song) +{ + song_save(os, song); +} + +static void +queue_save_song(BufferedOutputStream &os, int idx, const DetachedSong &song) +{ + if (song.IsInDatabase() && + song.GetStartMS() == 0 && song.GetEndMS() == 0) + /* use the brief format (just the URI) for "full" + database songs */ + queue_save_database_song(os, idx, song); + else + /* use the long format (URI, range, tags) for the + rest, so all metadata survives a MPD restart */ + queue_save_full_song(os, song); +} + +void +queue_save(BufferedOutputStream &os, const Queue &queue) +{ + for (unsigned i = 0; i < queue.GetLength(); i++) { + uint8_t prio = queue.GetPriorityAtPosition(i); + if (prio != 0) + os.Format(PRIO_LABEL "%u\n", prio); + + queue_save_song(os, i, queue.Get(i)); + } +} + +void +queue_load_song(TextFile &file, const SongLoader &loader, + const char *line, Queue &queue) +{ + if (queue.IsFull()) + return; + + uint8_t priority = 0; + if (StringStartsWith(line, PRIO_LABEL)) { + priority = strtoul(line + sizeof(PRIO_LABEL) - 1, nullptr, 10); + + line = file.ReadLine(); + if (line == nullptr) + return; + } + + DetachedSong *song; + + if (StringStartsWith(line, SONG_BEGIN)) { + const char *uri = line + sizeof(SONG_BEGIN) - 1; + + Error error; + song = song_load(file, uri, error); + if (song == nullptr) { + LogError(error); + return; + } + } else { + char *endptr; + long ret = strtol(line, &endptr, 10); + if (ret < 0 || *endptr != ':' || endptr[1] == 0) { + LogError(playlist_domain, + "Malformed playlist line in state file"); + return; + } + + const char *uri = endptr + 1; + + song = new DetachedSong(uri); + } + + if (!playlist_check_translate_song(*song, nullptr, loader)) { + delete song; + return; + } + + queue.Append(std::move(*song), priority); + delete song; +} diff --git a/src/queue/QueueSave.hxx b/src/queue/QueueSave.hxx new file mode 100644 index 000000000..3fb4dc1a6 --- /dev/null +++ b/src/queue/QueueSave.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. + */ + +/* + * This library saves the queue into the state file, and also loads it + * back into memory. + */ + +#ifndef MPD_QUEUE_SAVE_HXX +#define MPD_QUEUE_SAVE_HXX + +struct Queue; +class BufferedOutputStream; +class TextFile; +class SongLoader; + +void +queue_save(BufferedOutputStream &os, const Queue &queue); + +/** + * Loads one song from the state file and appends it to the queue. + */ +void +queue_load_song(TextFile &file, const SongLoader &loader, + const char *line, Queue &queue); + +#endif diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx new file mode 100644 index 000000000..b6f46f167 --- /dev/null +++ b/src/sticker/SongSticker.cxx @@ -0,0 +1,124 @@ +/* + * 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 "SongSticker.hxx" +#include "StickerDatabase.hxx" +#include "db/LightSong.hxx" +#include "db/Interface.hxx" +#include "util/Error.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +std::string +sticker_song_get_value(const LightSong &song, const char *name) +{ + const auto uri = song.GetURI(); + return sticker_load_value("song", uri.c_str(), name); +} + +bool +sticker_song_set_value(const LightSong &song, + const char *name, const char *value) +{ + const auto uri = song.GetURI(); + return sticker_store_value("song", uri.c_str(), name, value); +} + +bool +sticker_song_delete(const LightSong &song) +{ + const auto uri = song.GetURI(); + return sticker_delete("song", uri.c_str()); +} + +bool +sticker_song_delete_value(const LightSong &song, const char *name) +{ + const auto uri = song.GetURI(); + return sticker_delete_value("song", uri.c_str(), name); +} + +struct sticker * +sticker_song_get(const LightSong &song) +{ + const auto uri = song.GetURI(); + return sticker_load("song", uri.c_str()); +} + +struct sticker_song_find_data { + const Database *db; + const char *base_uri; + size_t base_uri_length; + + void (*func)(const LightSong &song, const char *value, + void *user_data); + void *user_data; +}; + +static void +sticker_song_find_cb(const char *uri, const char *value, void *user_data) +{ + struct sticker_song_find_data *data = + (struct sticker_song_find_data *)user_data; + + if (memcmp(uri, data->base_uri, data->base_uri_length) != 0) + /* should not happen, ignore silently */ + return; + + const Database *db = data->db; + const LightSong *song = db->GetSong(uri, IgnoreError()); + if (song != nullptr) { + data->func(*song, value, data->user_data); + db->ReturnSong(song); + } +} + +bool +sticker_song_find(const Database &db, const char *base_uri, const char *name, + void (*func)(const LightSong &song, const char *value, + void *user_data), + void *user_data) +{ + struct sticker_song_find_data data; + data.db = &db; + data.func = func; + data.user_data = user_data; + + char *allocated; + data.base_uri = base_uri; + if (*data.base_uri != 0) + /* append slash to base_uri */ + data.base_uri = allocated = + g_strconcat(data.base_uri, "/", nullptr); + else + /* searching in root directory - no trailing slash */ + allocated = nullptr; + + data.base_uri_length = strlen(data.base_uri); + + bool success = sticker_find("song", data.base_uri, name, + sticker_song_find_cb, &data); + g_free(allocated); + + return success; +} diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx new file mode 100644 index 000000000..5956cd6f9 --- /dev/null +++ b/src/sticker/SongSticker.hxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SONG_STICKER_HXX +#define MPD_SONG_STICKER_HXX + +#include "Compiler.h" + +#include <string> + +struct LightSong; +struct sticker; +class Database; + +/** + * Returns one value from a song's sticker record. The caller must + * free the return value with g_free(). + */ +gcc_pure +std::string +sticker_song_get_value(const LightSong &song, const char *name); + +/** + * Sets a sticker value in the specified song. Overwrites existing + * values. + */ +bool +sticker_song_set_value(const LightSong &song, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All values are deleted. + */ +bool +sticker_song_delete(const LightSong &song); + +/** + * Deletes a sticker value. Does nothing if the sticker did not + * exist. + */ +bool +sticker_song_delete_value(const LightSong &song, const char *name); + +/** + * Loads the sticker for the specified song. + * + * @param song the song object + * @return a sticker object, or NULL on error or if there is no sticker + */ +sticker * +sticker_song_get(const LightSong &song); + +/** + * Finds stickers with the specified name below the specified + * directory. + * + * Caller must lock the #db_mutex. + * + * @param base_uri the base directory to search in + * @param name the name of the sticker + * @return true on success (even if no sticker was found), false on + * failure + */ +bool +sticker_song_find(const Database &db, const char *base_uri, const char *name, + void (*func)(const LightSong &song, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/sticker/StickerDatabase.cxx b/src/sticker/StickerDatabase.cxx new file mode 100644 index 000000000..93eaa900d --- /dev/null +++ b/src/sticker/StickerDatabase.cxx @@ -0,0 +1,604 @@ +/* + * 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 "StickerDatabase.hxx" +#include "fs/Path.hxx" +#include "Idle.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <string> +#include <map> + +#include <sqlite3.h> +#include <assert.h> + +#if SQLITE_VERSION_NUMBER < 3003009 +#define sqlite3_prepare_v2 sqlite3_prepare +#endif + +struct sticker { + std::map<std::string, std::string> table; +}; + +enum sticker_sql { + STICKER_SQL_GET, + STICKER_SQL_LIST, + STICKER_SQL_UPDATE, + STICKER_SQL_INSERT, + STICKER_SQL_DELETE, + STICKER_SQL_DELETE_VALUE, + STICKER_SQL_FIND, +}; + +static const char *const sticker_sql[] = { + //[STICKER_SQL_GET] = + "SELECT value FROM sticker WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_LIST] = + "SELECT name,value FROM sticker WHERE type=? AND uri=?", + //[STICKER_SQL_UPDATE] = + "UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_INSERT] = + "INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)", + //[STICKER_SQL_DELETE] = + "DELETE FROM sticker WHERE type=? AND uri=?", + //[STICKER_SQL_DELETE_VALUE] = + "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", + //[STICKER_SQL_FIND] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", +}; + +static const char sticker_sql_create[] = + "CREATE TABLE IF NOT EXISTS sticker(" + " type VARCHAR NOT NULL, " + " uri VARCHAR NOT NULL, " + " name VARCHAR NOT NULL, " + " value VARCHAR NOT NULL" + ");" + "CREATE UNIQUE INDEX IF NOT EXISTS" + " sticker_value ON sticker(type, uri, name);" + ""; + +static sqlite3 *sticker_db; +static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)]; + +static constexpr Domain sticker_domain("sticker"); + +static void +LogError(sqlite3 *db, const char *msg) +{ + FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db)); +} + +static sqlite3_stmt * +sticker_prepare(const char *sql, Error &error) +{ + int ret; + sqlite3_stmt *stmt; + + ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr); + if (ret != SQLITE_OK) { + error.Format(sticker_domain, ret, + "sqlite3_prepare_v2() failed: %s", + sqlite3_errmsg(sticker_db)); + return nullptr; + } + + return stmt; +} + +bool +sticker_global_init(Path path, Error &error) +{ + assert(!path.IsNull()); + + int ret; + + /* open/create the sqlite database */ + + ret = sqlite3_open(path.c_str(), &sticker_db); + if (ret != SQLITE_OK) { + const std::string utf8 = path.ToUTF8(); + error.Format(sticker_domain, ret, + "Failed to open sqlite database '%s': %s", + utf8.c_str(), sqlite3_errmsg(sticker_db)); + return false; + } + + /* create the table and index */ + + ret = sqlite3_exec(sticker_db, sticker_sql_create, + nullptr, nullptr, nullptr); + if (ret != SQLITE_OK) { + error.Format(sticker_domain, ret, + "Failed to create sticker table: %s", + sqlite3_errmsg(sticker_db)); + return false; + } + + /* prepare the statements we're going to use */ + + for (unsigned i = 0; i < ARRAY_SIZE(sticker_sql); ++i) { + assert(sticker_sql[i] != nullptr); + + sticker_stmt[i] = sticker_prepare(sticker_sql[i], error); + if (sticker_stmt[i] == nullptr) + return false; + } + + return true; +} + +void +sticker_global_finish(void) +{ + if (sticker_db == nullptr) + /* not configured */ + return; + + for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) { + assert(sticker_stmt[i] != nullptr); + + sqlite3_finalize(sticker_stmt[i]); + } + + sqlite3_close(sticker_db); +} + +bool +sticker_enabled(void) +{ + return sticker_db != nullptr; +} + +std::string +sticker_load_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; + int ret; + + assert(sticker_enabled()); + assert(type != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + + if (*name == 0) + return std::string(); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return std::string(); + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return std::string(); + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return std::string(); + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + std::string value; + if (ret == SQLITE_ROW) { + /* record found */ + value = (const char*)sqlite3_column_text(stmt, 0); + } else if (ret == SQLITE_DONE) { + /* no record found */ + } else { + /* error */ + LogError(sticker_db, "sqlite3_step() failed"); + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return value; +} + +static bool +sticker_list_values(std::map<std::string, std::string> &table, + const char *type, const char *uri) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; + int ret; + + assert(type != nullptr); + assert(uri != nullptr); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + switch (ret) { + const char *name, *value; + + case SQLITE_ROW: + name = (const char*)sqlite3_column_text(stmt, 0); + value = (const char*)sqlite3_column_text(stmt, 1); + + table.insert(std::make_pair(name, value)); + break; + case SQLITE_DONE: + break; + case SQLITE_BUSY: + /* no op */ + break; + default: + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + } while (ret != SQLITE_DONE); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return true; +} + +static bool +sticker_update_value(const char *type, const char *uri, + const char *name, const char *value) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; + int ret; + + assert(type != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + assert(*name != 0); + assert(value != nullptr); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + ret = sqlite3_changes(sticker_db); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return ret > 0; +} + +static bool +sticker_insert_value(const char *type, const char *uri, + const char *name, const char *value) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; + int ret; + + assert(type != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + assert(*name != 0); + assert(value != nullptr); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + + idle_add(IDLE_STICKER); + return true; +} + +bool +sticker_store_value(const char *type, const char *uri, + const char *name, const char *value) +{ + assert(sticker_enabled()); + assert(type != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + assert(value != nullptr); + + if (*name == 0) + return false; + + return sticker_update_value(type, uri, name, value) || + sticker_insert_value(type, uri, name, value); +} + +bool +sticker_delete(const char *type, const char *uri) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; + int ret; + + assert(sticker_enabled()); + assert(type != nullptr); + assert(uri != nullptr); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return true; +} + +bool +sticker_delete_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; + int ret; + + assert(sticker_enabled()); + assert(type != nullptr); + assert(uri != nullptr); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + if (ret != SQLITE_DONE) { + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + + ret = sqlite3_changes(sticker_db); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + idle_add(IDLE_STICKER); + return ret > 0; +} + +void +sticker_free(struct sticker *sticker) +{ + delete sticker; +} + +const char * +sticker_get_value(const struct sticker &sticker, const char *name) +{ + auto i = sticker.table.find(name); + if (i == sticker.table.end()) + return nullptr; + + return i->second.c_str(); +} + +void +sticker_foreach(const sticker &sticker, + void (*func)(const char *name, const char *value, + void *user_data), + void *user_data) +{ + for (const auto &i : sticker.table) + func(i.first.c_str(), i.second.c_str(), user_data); +} + +struct sticker * +sticker_load(const char *type, const char *uri) +{ + sticker s; + + if (!sticker_list_values(s.table, type, uri)) + return nullptr; + + if (s.table.empty()) + /* don't return empty sticker objects */ + return nullptr; + + return new sticker(std::move(s)); +} + +bool +sticker_find(const char *type, const char *base_uri, const char *name, + void (*func)(const char *uri, const char *value, + void *user_data), + void *user_data) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; + int ret; + + assert(type != nullptr); + assert(name != nullptr); + assert(func != nullptr); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + if (base_uri == nullptr) + base_uri = ""; + + ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + do { + ret = sqlite3_step(stmt); + switch (ret) { + case SQLITE_ROW: + func((const char*)sqlite3_column_text(stmt, 0), + (const char*)sqlite3_column_text(stmt, 1), + user_data); + break; + case SQLITE_DONE: + break; + case SQLITE_BUSY: + /* no op */ + break; + default: + LogError(sticker_db, "sqlite3_step() failed"); + return false; + } + } while (ret != SQLITE_DONE); + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + + return true; +} diff --git a/src/sticker/StickerDatabase.hxx b/src/sticker/StickerDatabase.hxx new file mode 100644 index 000000000..8993489c4 --- /dev/null +++ b/src/sticker/StickerDatabase.hxx @@ -0,0 +1,161 @@ +/* + * 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. + */ + +/* + * This is the sticker database library. It is the backend of all the + * sticker code in MPD. + * + * "Stickers" are pieces of information attached to existing MPD + * objects (e.g. song files, directories, albums). Clients can create + * arbitrary name/value pairs. MPD itself does not assume any special + * meaning in them. + * + * The goal is to allow clients to share additional (possibly dynamic) + * information about songs, which is neither stored on the client (not + * available to other clients), nor stored in the song files (MPD has + * no write access). + * + * Client developers should create a standard for common sticker + * names, to ensure interoperability. + * + * Examples: song ratings; statistics; deferred tag writes; lyrics; + * ... + * + */ + +#ifndef MPD_STICKER_DATABASE_HXX +#define MPD_STICKER_DATABASE_HXX + +#include "Compiler.h" + +#include <string> + +class Error; +class Path; +struct sticker; + +/** + * Opens the sticker database. + * + * @return true on success, false on error + */ +bool +sticker_global_init(Path path, Error &error); + +/** + * Close the sticker database. + */ +void +sticker_global_finish(void); + +/** + * Returns true if the sticker database is configured and available. + */ +gcc_const +bool +sticker_enabled(void); + +/** + * Returns one value from an object's sticker record. Returns an + * empty string if the value doesn't exist. + */ +std::string +sticker_load_value(const char *type, const char *uri, const char *name); + +/** + * Sets a sticker value in the specified object. Overwrites existing + * values. + */ +bool +sticker_store_value(const char *type, const char *uri, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All sticker values of the + * specified object are deleted. + */ +bool +sticker_delete(const char *type, const char *uri); + +/** + * Deletes a sticker value. Fails if no sticker with this name + * exists. + */ +bool +sticker_delete_value(const char *type, const char *uri, const char *name); + +/** + * Frees resources held by the sticker object. + * + * @param sticker the sticker object to be freed + */ +void +sticker_free(sticker *sticker); + +/** + * Determines a single value in a sticker. + * + * @param sticker the sticker object + * @param name the name of the sticker + * @return the sticker value, or nullptr if none was found + */ +gcc_pure +const char * +sticker_get_value(const sticker &sticker, const char *name); + +/** + * Iterates over all sticker items in a sticker. + * + * @param sticker the sticker object + * @param func a callback function + * @param user_data an opaque pointer for the callback function + */ +void +sticker_foreach(const sticker &sticker, + void (*func)(const char *name, const char *value, + void *user_data), + void *user_data); + +/** + * Loads the sticker for the specified resource. + * + * @param type the resource type, e.g. "song" + * @param uri the URI of the resource, e.g. the song path + * @return a sticker object, or nullptr on error or if there is no sticker + */ +sticker * +sticker_load(const char *type, const char *uri); + +/** + * Finds stickers with the specified name below the specified URI. + * + * @param type the resource type, e.g. "song" + * @param base_uri the URI prefix of the resources, or nullptr if all + * resources should be searched + * @param name the name of the sticker + * @return true on success (even if no sticker was found), false on + * failure + */ +bool +sticker_find(const char *type, const char *base_uri, const char *name, + void (*func)(const char *uri, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/sticker/StickerPrint.cxx b/src/sticker/StickerPrint.cxx new file mode 100644 index 000000000..a952ff203 --- /dev/null +++ b/src/sticker/StickerPrint.cxx @@ -0,0 +1,44 @@ +/* + * 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 "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "client/Client.hxx" + +void +sticker_print_value(Client &client, + const char *name, const char *value) +{ + client_printf(client, "sticker: %s=%s\n", name, value); +} + +static void +print_sticker_cb(const char *name, const char *value, void *data) +{ + Client &client = *(Client *)data; + + sticker_print_value(client, name, value); +} + +void +sticker_print(Client &client, const sticker &sticker) +{ + sticker_foreach(sticker, print_sticker_cb, &client); +} diff --git a/src/sticker/StickerPrint.hxx b/src/sticker/StickerPrint.hxx new file mode 100644 index 000000000..39f3dc09e --- /dev/null +++ b/src/sticker/StickerPrint.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_PRINT_HXX +#define MPD_STICKER_PRINT_HXX + +struct sticker; +class Client; + +/** + * Sends one sticker value to the client. + */ +void +sticker_print_value(Client &client, const char *name, const char *value); + +/** + * Sends all sticker values to the client. + */ +void +sticker_print(Client &client, const sticker &sticker); + +#endif diff --git a/src/storage/CompositeStorage.cxx b/src/storage/CompositeStorage.cxx new file mode 100644 index 000000000..ac6fcecd9 --- /dev/null +++ b/src/storage/CompositeStorage.cxx @@ -0,0 +1,360 @@ +/* + * 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 "CompositeStorage.hxx" +#include "FileInfo.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <set> + +static constexpr Domain composite_domain("composite"); + +/** + * Combines the directory entries of another #StorageDirectoryReader + * instance and the virtual directory entries. + */ +class CompositeDirectoryReader final : public StorageDirectoryReader { + StorageDirectoryReader *other; + + std::set<std::string> names; + std::set<std::string>::const_iterator current, next; + +public: + template<typename M> + CompositeDirectoryReader(StorageDirectoryReader *_other, + const M &map) + :other(_other) { + for (const auto &i : map) + names.insert(i.first); + next = names.begin(); + } + + virtual ~CompositeDirectoryReader() { + delete other; + } + + /* virtual methods from class StorageDirectoryReader */ + virtual const char *Read() override; + virtual bool GetInfo(bool follow, FileInfo &info, + Error &error) override; +}; + +const char * +CompositeDirectoryReader::Read() +{ + if (other != nullptr) { + const char *name = other->Read(); + if (name != nullptr) { + names.erase(name); + return name; + } + + delete other; + other = nullptr; + } + + if (next == names.end()) + return nullptr; + + current = next++; + return current->c_str(); +} + +bool +CompositeDirectoryReader::GetInfo(bool follow, FileInfo &info, + Error &error) +{ + if (other != nullptr) + return other->GetInfo(follow, info, error); + + assert(current != names.end()); + + info.type = FileInfo::Type::DIRECTORY; + info.mtime = 0; + info.device = 0; + info.inode = 0; + return true; +} + +static std::string +NextSegment(const char *&uri_r) +{ + const char *uri = uri_r; + const char *slash = strchr(uri, '/'); + if (slash == nullptr) { + uri_r += strlen(uri); + return std::string(uri); + } else { + uri_r = slash + 1; + return std::string(uri, slash); + } +} + +CompositeStorage::Directory::~Directory() +{ + delete storage; +} + +const CompositeStorage::Directory * +CompositeStorage::Directory::Find(const char *uri) const +{ + const Directory *directory = this; + while (*uri != 0) { + const std::string name = NextSegment(uri); + auto i = directory->children.find(name); + if (i == directory->children.end()) + return nullptr; + + directory = &i->second; + } + + return directory; +} + +CompositeStorage::Directory & +CompositeStorage::Directory::Make(const char *uri) +{ + Directory *directory = this; + while (*uri != 0) { + const std::string name = NextSegment(uri); +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + auto i = directory->children.emplace(std::move(name), + Directory()); +#else + auto i = directory->children.insert(std::make_pair(std::move(name), + Directory())); +#endif + directory = &i.first->second; + } + + return *directory; +} + +bool +CompositeStorage::Directory::Unmount() +{ + if (storage == nullptr) + return false; + + delete storage; + storage = nullptr; + return true; +} + +bool +CompositeStorage::Directory::Unmount(const char *uri) +{ + if (*uri == 0) + return Unmount(); + + const std::string name = NextSegment(uri); + + auto i = children.find(name); + if (i == children.end() || !i->second.Unmount(uri)) + return false; + + if (i->second.IsEmpty()) + children.erase(i); + + return true; + +} + +bool +CompositeStorage::Directory::MapToRelativeUTF8(std::string &buffer, + const char *uri) const +{ + if (storage != nullptr) { + const char *result = storage->MapToRelativeUTF8(uri); + if (result != nullptr) { + buffer = result; + return true; + } + } + + for (const auto &i : children) { + if (i.second.MapToRelativeUTF8(buffer, uri)) { + buffer.insert(buffer.begin(), '/'); + buffer.insert(buffer.begin(), + i.first.begin(), i.first.end()); + return true; + } + } + + return false; +} + +CompositeStorage::CompositeStorage() +{ +} + +CompositeStorage::~CompositeStorage() +{ +} + +Storage * +CompositeStorage::GetMount(const char *uri) +{ + const ScopeLock protect(mutex); + + auto result = FindStorage(uri); + if (*result.uri != 0) + /* not a mount point */ + return nullptr; + + return result.directory->storage; +} + +void +CompositeStorage::Mount(const char *uri, Storage *storage) +{ + const ScopeLock protect(mutex); + + Directory &directory = root.Make(uri); + if (directory.storage != nullptr) + delete directory.storage; + directory.storage = storage; +} + +bool +CompositeStorage::Unmount(const char *uri) +{ + const ScopeLock protect(mutex); + + return root.Unmount(uri); +} + +CompositeStorage::FindResult +CompositeStorage::FindStorage(const char *uri) const +{ + FindResult result{&root, uri}; + + const Directory *directory = &root; + while (*uri != 0) { + const std::string name = NextSegment(uri); + + auto i = directory->children.find(name); + if (i == directory->children.end()) + break; + + directory = &i->second; + if (directory->storage != nullptr) + result = FindResult{directory, uri}; + } + + return result; +} + +CompositeStorage::FindResult +CompositeStorage::FindStorage(const char *uri, Error &error) const +{ + auto result = FindStorage(uri); + if (result.directory == nullptr) + error.Set(composite_domain, "No such directory"); + return result; +} + +bool +CompositeStorage::GetInfo(const char *uri, bool follow, FileInfo &info, + Error &error) +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri, error); + if (f.directory->storage != nullptr && + f.directory->storage->GetInfo(f.uri, follow, info, error)) + return true; + + const Directory *directory = f.directory->Find(f.uri); + if (directory != nullptr) { + error.Clear(); + info.type = FileInfo::Type::DIRECTORY; + info.mtime = 0; + info.device = 0; + info.inode = 0; + return true; + } + + return false; +} + +StorageDirectoryReader * +CompositeStorage::OpenDirectory(const char *uri, + Error &error) +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri, error); + const Directory *directory = f.directory->Find(f.uri); + if (directory == nullptr || directory->children.empty()) { + /* no virtual directories here */ + + if (f.directory->storage == nullptr) + return nullptr; + + return f.directory->storage->OpenDirectory(f.uri, error); + } + + StorageDirectoryReader *other = + f.directory->storage->OpenDirectory(f.uri, IgnoreError()); + return new CompositeDirectoryReader(other, directory->children); +} + +std::string +CompositeStorage::MapUTF8(const char *uri) const +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri); + if (f.directory->storage == nullptr) + return std::string(); + + return f.directory->storage->MapUTF8(f.uri); +} + +AllocatedPath +CompositeStorage::MapFS(const char *uri) const +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri); + if (f.directory->storage == nullptr) + return AllocatedPath::Null(); + + return f.directory->storage->MapFS(f.uri); +} + +const char * +CompositeStorage::MapToRelativeUTF8(const char *uri) const +{ + const ScopeLock protect(mutex); + + if (root.storage != nullptr) { + const char *result = root.storage->MapToRelativeUTF8(uri); + if (result != nullptr) + return result; + } + + if (!root.MapToRelativeUTF8(relative_buffer, uri)) + return nullptr; + + return relative_buffer.c_str(); +} diff --git a/src/storage/CompositeStorage.hxx b/src/storage/CompositeStorage.hxx new file mode 100644 index 000000000..3daa45ee3 --- /dev/null +++ b/src/storage/CompositeStorage.hxx @@ -0,0 +1,166 @@ +/* + * 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_COMPOSITE_STORAGE_HXX +#define MPD_COMPOSITE_STORAGE_HXX + +#include "check.h" +#include "StorageInterface.hxx" +#include "thread/Mutex.hxx" +#include "Compiler.h" + +#include <string> +#include <map> + +class Error; +class Storage; + +/** + * A #Storage implementation that combines multiple other #Storage + * instances in one virtual tree. It is used to "mount" new #Storage + * instances into the storage tree. + * + * This class is thread-safe: mounts may be added and removed at any + * time in any thread. + */ +class CompositeStorage final : public Storage { + /** + * A node in the virtual directory tree. + */ + struct Directory { + /** + * The #Storage mounted n this virtual directory. All + * "leaf" Directory instances must have a #Storage. + * Other Directory instances may have one, and child + * mounts will be "mixed" in. + */ + Storage *storage; + + std::map<std::string, Directory> children; + + Directory():storage(nullptr) {} + ~Directory(); + + gcc_pure + bool IsEmpty() const { + return storage == nullptr && children.empty(); + } + + gcc_pure + const Directory *Find(const char *uri) const; + + Directory &Make(const char *uri); + + bool Unmount(); + bool Unmount(const char *uri); + + gcc_pure + bool MapToRelativeUTF8(std::string &buffer, + const char *uri) const; + }; + + struct FindResult { + const Directory *directory; + const char *uri; + }; + + /** + * Protects the virtual #Directory tree. + * + * TODO: use readers-writer lock + */ + mutable Mutex mutex; + + Directory root; + + mutable std::string relative_buffer; + +public: + CompositeStorage(); + virtual ~CompositeStorage(); + + /** + * Get the #Storage at the specified mount point. Returns + * nullptr if the given URI is not a mount point. + * + * The returned pointer is unprotected. No other thread is + * allowed to unmount the given mount point while the return + * value is being used. + */ + gcc_pure gcc_nonnull_all + Storage *GetMount(const char *uri); + + /** + * Call the given function for each mounted storage, including + * the root storage. Passes mount point URI and the a const + * Storage reference to the function. + */ + template<typename T> + void VisitMounts(T t) const { + const ScopeLock protect(mutex); + std::string uri; + VisitMounts(uri, root, t); + } + + void Mount(const char *uri, Storage *storage); + bool Unmount(const char *uri); + + /* virtual methods from class Storage */ + virtual bool GetInfo(const char *uri, bool follow, FileInfo &info, + Error &error) override; + + virtual StorageDirectoryReader *OpenDirectory(const char *uri, + Error &error) override; + + virtual std::string MapUTF8(const char *uri) const override; + + virtual AllocatedPath MapFS(const char *uri) const override; + + virtual const char *MapToRelativeUTF8(const char *uri) const override; + +private: + template<typename T> + void VisitMounts(std::string &uri, const Directory &directory, + T t) const { + const Storage *const storage = directory.storage; + if (storage != nullptr) + t(uri.c_str(), *storage); + + if (!uri.empty()) + uri.push_back('/'); + + const size_t uri_length = uri.length(); + + for (const auto &i : directory.children) { + uri.resize(uri_length); + uri.append(i.first); + + VisitMounts(uri, i.second, t); + } + } + + gcc_pure + FindResult FindStorage(const char *uri) const; + FindResult FindStorage(const char *uri, Error &error) const; + + const char *MapToRelativeUTF8(const Directory &directory, + const char *uri) const; +}; + +#endif diff --git a/src/storage/Configured.cxx b/src/storage/Configured.cxx new file mode 100644 index 000000000..800a18ba0 --- /dev/null +++ b/src/storage/Configured.cxx @@ -0,0 +1,81 @@ +/* + * 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 "Configured.hxx" +#include "Registry.hxx" +#include "plugins/LocalStorage.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigError.hxx" +#include "fs/StandardDirectory.hxx" +#include "fs/CheckFile.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +static Storage * +CreateConfiguredStorageUri(const char *uri, Error &error) +{ + Storage *storage = CreateStorageURI(uri, error); + if (storage == nullptr && !error.IsDefined()) + error.Format(config_domain, + "Unrecognized storage URI: %s", uri); + return storage; +} + +static AllocatedPath +GetConfiguredMusicDirectory(Error &error) +{ + AllocatedPath path = config_get_path(CONF_MUSIC_DIR, error); + if (path.IsNull() && !error.IsDefined()) + path = GetUserMusicDir(); + + return path; +} + +static Storage * +CreateConfiguredStorageLocal(Error &error) +{ + AllocatedPath path = GetConfiguredMusicDirectory(error); + if (path.IsNull()) + return nullptr; + + path.ChopSeparators(); + CheckDirectoryReadable(path); + return CreateLocalStorage(path); +} + +Storage * +CreateConfiguredStorage(Error &error) +{ + assert(!error.IsDefined()); + + auto uri = config_get_string(CONF_MUSIC_DIR, nullptr); + if (uri != nullptr && uri_has_scheme(uri)) + return CreateConfiguredStorageUri(uri, error); + + return CreateConfiguredStorageLocal(error); +} + +bool +IsStorageConfigured() +{ + return config_get_string(CONF_MUSIC_DIR, nullptr) != nullptr; +} diff --git a/src/storage/Configured.hxx b/src/storage/Configured.hxx new file mode 100644 index 000000000..d78857a26 --- /dev/null +++ b/src/storage/Configured.hxx @@ -0,0 +1,44 @@ +/* + * 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_STORAGE_CONFIG_HXX +#define MPD_STORAGE_CONFIG_HXX + +#include "check.h" +#include "Compiler.h" + +class Error; +class Storage; + +/** + * Read storage configuration settings and create a #Storage instance + * from it. Returns nullptr on error or if no storage is configured + * (no #Error set in that case). + */ +Storage * +CreateConfiguredStorage(Error &error); + +/** + * Returns true if there is configuration for a #Storage instance. + */ +gcc_const +bool +IsStorageConfigured(); + +#endif diff --git a/src/storage/FileInfo.hxx b/src/storage/FileInfo.hxx new file mode 100644 index 000000000..8dd152c0a --- /dev/null +++ b/src/storage/FileInfo.hxx @@ -0,0 +1,62 @@ +/* + * 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_STORAGE_FILE_INFO_HXX +#define MPD_STORAGE_FILE_INFO_HXX + +#include "check.h" + +#include <time.h> +#include <stdint.h> + +struct FileInfo { + enum class Type : uint8_t { + OTHER, + REGULAR, + DIRECTORY, + }; + + Type type; + + /** + * The file size in bytes. Only valid for #Type::REGULAR. + */ + uint64_t size; + + /** + * The modification time. 0 means unknown / not applicable. + */ + time_t mtime; + + /** + * Device id and inode number. 0 means unknown / not + * applicable. + */ + unsigned device, inode; + + bool IsRegular() const { + return type == Type::REGULAR; + } + + bool IsDirectory() const { + return type == Type::DIRECTORY; + } +}; + +#endif diff --git a/src/storage/Registry.cxx b/src/storage/Registry.cxx new file mode 100644 index 000000000..b3fdd1642 --- /dev/null +++ b/src/storage/Registry.cxx @@ -0,0 +1,71 @@ +/* + * 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 "StoragePlugin.hxx" +#include "plugins/LocalStorage.hxx" +#include "plugins/SmbclientStorage.hxx" +#include "plugins/NfsStorage.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <string.h> + +const StoragePlugin *const storage_plugins[] = { + &local_storage_plugin, +#ifdef ENABLE_SMBCLIENT + &smbclient_storage_plugin, +#endif +#ifdef ENABLE_NFS + &nfs_storage_plugin, +#endif + nullptr +}; + +const StoragePlugin * +GetStoragePluginByName(const char *name) +{ + for (auto i = storage_plugins; *i != nullptr; ++i) { + const StoragePlugin &plugin = **i; + if (strcmp(plugin.name, name) == 0) + return *i; + } + + return nullptr; +} + +Storage * +CreateStorageURI(const char *uri, Error &error) +{ + assert(!error.IsDefined()); + + for (auto i = storage_plugins; *i != nullptr; ++i) { + const StoragePlugin &plugin = **i; + + if (plugin.create_uri == nullptr) + continue; + + Storage *storage = plugin.create_uri(uri, error); + if (storage != nullptr || error.IsDefined()) + return storage; + } + + return nullptr; +} diff --git a/src/storage/Registry.hxx b/src/storage/Registry.hxx new file mode 100644 index 000000000..9696b3de1 --- /dev/null +++ b/src/storage/Registry.hxx @@ -0,0 +1,44 @@ +/* + * 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_STORAGE_REGISTRY_HXX +#define MPD_STORAGE_REGISTRY_HXX + +#include "check.h" +#include "Compiler.h" + +struct StoragePlugin; +class Storage; +class Error; + +/** + * nullptr terminated list of all storage plugins which were enabled at + * compile time. + */ +extern const StoragePlugin *const storage_plugins[]; + +gcc_nonnull_all gcc_pure +const StoragePlugin * +GetStoragePluginByName(const char *name); + +gcc_nonnull_all gcc_malloc +Storage * +CreateStorageURI(const char *uri, Error &error); + +#endif diff --git a/src/storage/StorageInterface.cxx b/src/storage/StorageInterface.cxx new file mode 100644 index 000000000..93c50a8ac --- /dev/null +++ b/src/storage/StorageInterface.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StorageInterface.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" + +AllocatedPath +Storage::MapFS(gcc_unused const char *uri_utf8) const +{ + return AllocatedPath::Null(); +} + +AllocatedPath +Storage::MapChildFS(const char *uri_utf8, + const char *child_utf8) const +{ + const auto uri2 = PathTraitsUTF8::Build(uri_utf8, child_utf8); + return MapFS(uri2.c_str()); +} diff --git a/src/storage/StorageInterface.hxx b/src/storage/StorageInterface.hxx new file mode 100644 index 000000000..892e8e43b --- /dev/null +++ b/src/storage/StorageInterface.hxx @@ -0,0 +1,81 @@ +/* + * 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_STORAGE_INTERFACE_HXX +#define MPD_STORAGE_INTERFACE_HXX + +#include "check.h" +#include "fs/AllocatedPath.hxx" +#include "fs/DirectoryReader.hxx" + +#include <string> + +struct FileInfo; +class AllocatedPath; + +class StorageDirectoryReader { +public: + StorageDirectoryReader() = default; + StorageDirectoryReader(const StorageDirectoryReader &) = delete; + virtual ~StorageDirectoryReader() {} + + virtual const char *Read() = 0; + virtual bool GetInfo(bool follow, FileInfo &info, Error &error) = 0; +}; + +class Storage { +public: + Storage() = default; + Storage(const Storage &) = delete; + virtual ~Storage() {} + + virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) = 0; + + virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) = 0; + + /** + * Map the given relative URI to an absolute URI. + */ + gcc_pure + virtual std::string MapUTF8(const char *uri_utf8) const = 0; + + /** + * Map the given relative URI to a local file path. Returns + * AllocatedPath::Null() on error or if this storage does not + * support local files. + */ + gcc_pure + virtual AllocatedPath MapFS(const char *uri_utf8) const; + + gcc_pure + AllocatedPath MapChildFS(const char *uri_utf8, + const char *child_utf8) const; + + /** + * Check if the given URI points inside this storage. If yes, + * then it returns a relative URI (pointing inside the given + * string); if not, returns nullptr. + */ + gcc_pure + virtual const char *MapToRelativeUTF8(const char *uri_utf8) const = 0; +}; + +#endif diff --git a/src/storage/StoragePlugin.hxx b/src/storage/StoragePlugin.hxx new file mode 100644 index 000000000..d91caf24b --- /dev/null +++ b/src/storage/StoragePlugin.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STORAGE_PLUGIN_HXX +#define MPD_STORAGE_PLUGIN_HXX + +#include "check.h" + +class Error; +class Storage; + +struct StoragePlugin { + const char *name; + + Storage *(*create_uri)(const char *uri, Error &error); +}; + +#endif diff --git a/src/storage/plugins/LocalStorage.cxx b/src/storage/plugins/LocalStorage.cxx new file mode 100644 index 000000000..34d11569c --- /dev/null +++ b/src/storage/plugins/LocalStorage.cxx @@ -0,0 +1,218 @@ +/* + * 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 "LocalStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "util/Error.hxx" +#include "fs/FileSystem.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/DirectoryReader.hxx" + +#include <string> + +class LocalDirectoryReader final : public StorageDirectoryReader { + AllocatedPath base_fs; + + DirectoryReader reader; + + std::string name_utf8; + +public: + LocalDirectoryReader(AllocatedPath &&_base_fs) + :base_fs(std::move(_base_fs)), reader(base_fs) {} + + bool HasFailed() { + return reader.HasFailed(); + } + + /* virtual methods from class StorageDirectoryReader */ + virtual const char *Read() override; + virtual bool GetInfo(bool follow, FileInfo &info, + Error &error) override; +}; + +class LocalStorage final : public Storage { + const AllocatedPath base_fs; + const std::string base_utf8; + +public: + explicit LocalStorage(Path _base_fs) + :base_fs(_base_fs), base_utf8(base_fs.ToUTF8()) { + assert(!base_fs.IsNull()); + assert(!base_utf8.empty()); + } + + /* virtual methods from class Storage */ + virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) override; + + virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) override; + + virtual std::string MapUTF8(const char *uri_utf8) const override; + + virtual AllocatedPath MapFS(const char *uri_utf8) const override; + + virtual const char *MapToRelativeUTF8(const char *uri_utf8) const override; + +private: + AllocatedPath MapFS(const char *uri_utf8, Error &error) const; +}; + +static bool +Stat(Path path, bool follow, FileInfo &info, Error &error) +{ + struct stat st; + if (!StatFile(path, st, follow)) { + error.SetErrno(); + + const auto path_utf8 = path.ToUTF8(); + error.FormatPrefix("Failed to stat %s: ", path_utf8.c_str()); + return false; + } + + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; + return true; +} + +std::string +LocalStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base_utf8; + + return PathTraitsUTF8::Build(base_utf8.c_str(), uri_utf8); +} + +AllocatedPath +LocalStorage::MapFS(const char *uri_utf8, Error &error) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base_fs; + + AllocatedPath path_fs = AllocatedPath::FromUTF8(uri_utf8, error); + if (!path_fs.IsNull()) + path_fs = AllocatedPath::Build(base_fs, path_fs); + + return path_fs; +} + +AllocatedPath +LocalStorage::MapFS(const char *uri_utf8) const +{ + return MapFS(uri_utf8, IgnoreError()); +} + +const char * +LocalStorage::MapToRelativeUTF8(const char *uri_utf8) const +{ + return PathTraitsUTF8::Relative(base_utf8.c_str(), uri_utf8); +} + +bool +LocalStorage::GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) +{ + AllocatedPath path_fs = MapFS(uri_utf8, error); + if (path_fs.IsNull()) + return false; + + return Stat(path_fs, follow, info, error); +} + +StorageDirectoryReader * +LocalStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + AllocatedPath path_fs = MapFS(uri_utf8, error); + if (path_fs.IsNull()) + return nullptr; + + LocalDirectoryReader *reader = + new LocalDirectoryReader(std::move(path_fs)); + if (reader->HasFailed()) { + error.FormatErrno("Failed to open '%s'", uri_utf8); + delete reader; + return nullptr; + } + + return reader; +} + +gcc_pure +static bool +SkipNameFS(const char *name_fs) +{ + return name_fs[0] == '.' && + (name_fs[1] == 0 || + (name_fs[1] == '.' && name_fs[2] == 0)); +} + +const char * +LocalDirectoryReader::Read() +{ + while (reader.ReadEntry()) { + const Path name_fs = reader.GetEntry(); + if (SkipNameFS(name_fs.c_str())) + continue; + + name_utf8 = name_fs.ToUTF8(); + if (name_utf8.empty()) + continue; + + return name_utf8.c_str(); + } + + return nullptr; +} + +bool +LocalDirectoryReader::GetInfo(bool follow, FileInfo &info, Error &error) +{ + const AllocatedPath path_fs = + AllocatedPath::Build(base_fs, reader.GetEntry()); + return Stat(path_fs, follow, info, error); +} + +Storage * +CreateLocalStorage(Path base_fs) +{ + return new LocalStorage(base_fs); +} + +const StoragePlugin local_storage_plugin = { + "local", + nullptr, +}; diff --git a/src/storage/plugins/LocalStorage.hxx b/src/storage/plugins/LocalStorage.hxx new file mode 100644 index 000000000..7295d38e7 --- /dev/null +++ b/src/storage/plugins/LocalStorage.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_STORAGE_LOCAL_HXX +#define MPD_STORAGE_LOCAL_HXX + +#include "check.h" +#include "Compiler.h" + +struct StoragePlugin; +class Storage; +class Path; + +extern const StoragePlugin local_storage_plugin; + +gcc_malloc gcc_nonnull_all +Storage * +CreateLocalStorage(Path base_fs); + +#endif diff --git a/src/storage/plugins/NfsStorage.cxx b/src/storage/plugins/NfsStorage.cxx new file mode 100644 index 000000000..72c138feb --- /dev/null +++ b/src/storage/plugins/NfsStorage.cxx @@ -0,0 +1,238 @@ +/* + * 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 "NfsStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "lib/nfs/Domain.hxx" +#include "util/Error.hxx" +#include "thread/Mutex.hxx" + +extern "C" { +#include <nfsc/libnfs.h> +#include <nfsc/libnfs-raw-nfs.h> +} + +#include <sys/stat.h> +#include <fcntl.h> + +class NfsDirectoryReader final : public StorageDirectoryReader { + const std::string base; + + nfs_context *ctx; + nfsdir *dir; + + nfsdirent *ent; + +public: + NfsDirectoryReader(const char *_base, nfs_context *_ctx, nfsdir *_dir) + :base(_base), ctx(_ctx), dir(_dir) {} + + virtual ~NfsDirectoryReader(); + + /* virtual methods from class StorageDirectoryReader */ + virtual const char *Read() override; + virtual bool GetInfo(bool follow, FileInfo &info, + Error &error) override; +}; + +class NfsStorage final : public Storage { + const std::string base; + + nfs_context *ctx; + +public: + NfsStorage(const char *_base, nfs_context *_ctx) + :base(_base), ctx(_ctx) {} + + virtual ~NfsStorage() { + nfs_destroy_context(ctx); + } + + /* virtual methods from class Storage */ + virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) override; + + virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) override; + + virtual std::string MapUTF8(const char *uri_utf8) const override; + + virtual const char *MapToRelativeUTF8(const char *uri_utf8) const override; +}; + +std::string +NfsStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base; + + return PathTraitsUTF8::Build(base.c_str(), uri_utf8); +} + +const char * +NfsStorage::MapToRelativeUTF8(const char *uri_utf8) const +{ + return PathTraitsUTF8::Relative(base.c_str(), uri_utf8); +} + +static bool +GetInfo(nfs_context *ctx, const char *path, FileInfo &info, Error &error) +{ + struct stat st; + int result = nfs_stat(ctx, path, &st); + if (result < 0) { + error.SetErrno(-result, "nfs_stat() failed"); + return false; + } + + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; + return true; +} + +bool +NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow, + FileInfo &info, Error &error) +{ + /* libnfs paths must begin with a slash */ + std::string path(uri_utf8); + path.insert(path.begin(), '/'); + + return ::GetInfo(ctx, path.c_str(), info, error); +} + +StorageDirectoryReader * +NfsStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + /* libnfs paths must begin with a slash */ + std::string path(uri_utf8); + path.insert(path.begin(), '/'); + + nfsdir *dir; + int result = nfs_opendir(ctx, path.c_str(), &dir); + if (result < 0) { + error.SetErrno(-result, "nfs_opendir() failed"); + return nullptr; + } + + return new NfsDirectoryReader(uri_utf8, ctx, dir); +} + +gcc_pure +static bool +SkipNameFS(const char *name) +{ + return name[0] == '.' && + (name[1] == 0 || + (name[1] == '.' && name[2] == 0)); +} + +NfsDirectoryReader::~NfsDirectoryReader() +{ + nfs_closedir(ctx, dir); +} + +const char * +NfsDirectoryReader::Read() +{ + while ((ent = nfs_readdir(ctx, dir)) != nullptr) { + if (!SkipNameFS(ent->name)) + return ent->name; + } + + return nullptr; +} + +bool +NfsDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info, + gcc_unused Error &error) +{ + assert(ent != nullptr); + + switch (ent->type) { + case NF3REG: + info.type = FileInfo::Type::REGULAR; + break; + + case NF3DIR: + info.type = FileInfo::Type::DIRECTORY; + break; + + default: + info.type = FileInfo::Type::OTHER; + break; + } + + info.size = ent->size; + info.mtime = ent->mtime.tv_sec; + info.device = 0; + info.inode = ent->inode; + return true; +} + +static Storage * +CreateNfsStorageURI(const char *base, Error &error) +{ + if (memcmp(base, "nfs://", 6) != 0) + return nullptr; + + const char *p = base + 6; + + const char *mount = strchr(p, '/'); + if (mount == nullptr) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return nullptr; + } + + const std::string server(p, mount); + + 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); + if (result < 0) { + nfs_destroy_context(ctx); + error.SetErrno(-result, "nfs_mount() failed"); + return nullptr; + } + + return new NfsStorage(base, ctx); +} + +const StoragePlugin nfs_storage_plugin = { + "nfs", + CreateNfsStorageURI, +}; diff --git a/src/storage/plugins/NfsStorage.hxx b/src/storage/plugins/NfsStorage.hxx new file mode 100644 index 000000000..f7e18effc --- /dev/null +++ b/src/storage/plugins/NfsStorage.hxx @@ -0,0 +1,29 @@ +/* + * 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_STORAGE_NFS_HXX +#define MPD_STORAGE_NFS_HXX + +#include "check.h" + +struct StoragePlugin; + +extern const StoragePlugin nfs_storage_plugin; + +#endif diff --git a/src/storage/plugins/SmbclientStorage.cxx b/src/storage/plugins/SmbclientStorage.cxx new file mode 100644 index 000000000..a73c8d65c --- /dev/null +++ b/src/storage/plugins/SmbclientStorage.cxx @@ -0,0 +1,211 @@ +/* + * 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 "SmbclientStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "lib/smbclient/Init.hxx" +#include "lib/smbclient/Mutex.hxx" +#include "util/Error.hxx" +#include "thread/Mutex.hxx" + +#include <libsmbclient.h> + +class SmbclientDirectoryReader final : public StorageDirectoryReader { + const std::string base; + const unsigned handle; + + const char *name; + +public: + SmbclientDirectoryReader(std::string &&_base, unsigned _handle) + :base(std::move(_base)), handle(_handle) {} + + virtual ~SmbclientDirectoryReader(); + + /* virtual methods from class StorageDirectoryReader */ + virtual const char *Read() override; + virtual bool GetInfo(bool follow, FileInfo &info, + Error &error) override; +}; + +class SmbclientStorage final : public Storage { + const std::string base; + + SMBCCTX *const ctx; + +public: + SmbclientStorage(const char *_base, SMBCCTX *_ctx) + :base(_base), ctx(_ctx) {} + + virtual ~SmbclientStorage() { + smbclient_mutex.lock(); + smbc_free_context(ctx, 1); + smbclient_mutex.unlock(); + } + + /* virtual methods from class Storage */ + virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) override; + + virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) override; + + virtual std::string MapUTF8(const char *uri_utf8) const override; + + virtual const char *MapToRelativeUTF8(const char *uri_utf8) const override; +}; + +std::string +SmbclientStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base; + + return PathTraitsUTF8::Build(base.c_str(), uri_utf8); +} + +const char * +SmbclientStorage::MapToRelativeUTF8(const char *uri_utf8) const +{ + return PathTraitsUTF8::Relative(base.c_str(), uri_utf8); +} + +static bool +GetInfo(const char *path, FileInfo &info, Error &error) +{ + struct stat st; + smbclient_mutex.lock(); + bool success = smbc_stat(path, &st) == 0; + smbclient_mutex.unlock(); + if (!success) { + error.SetErrno(); + return false; + } + + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; + return true; +} + +bool +SmbclientStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow, + FileInfo &info, Error &error) +{ + const std::string mapped = MapUTF8(uri_utf8); + return ::GetInfo(mapped.c_str(), info, error); +} + +StorageDirectoryReader * +SmbclientStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + std::string mapped = MapUTF8(uri_utf8); + smbclient_mutex.lock(); + int handle = smbc_opendir(mapped.c_str()); + smbclient_mutex.unlock(); + if (handle < 0) { + error.SetErrno(); + return nullptr; + } + + return new SmbclientDirectoryReader(std::move(mapped.c_str()), handle); +} + +gcc_pure +static bool +SkipNameFS(const char *name) +{ + return name[0] == '.' && + (name[1] == 0 || + (name[1] == '.' && name[2] == 0)); +} + +SmbclientDirectoryReader::~SmbclientDirectoryReader() +{ + smbclient_mutex.lock(); + smbc_close(handle); + smbclient_mutex.unlock(); +} + +const char * +SmbclientDirectoryReader::Read() +{ + const ScopeLock protect(smbclient_mutex); + + struct smbc_dirent *e; + while ((e = smbc_readdir(handle)) != nullptr) { + name = e->name; + if (!SkipNameFS(name)) + return name; + } + + return nullptr; +} + +bool +SmbclientDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info, + Error &error) +{ + const std::string path = PathTraitsUTF8::Build(base.c_str(), name); + return ::GetInfo(path.c_str(), info, error); +} + +static Storage * +CreateSmbclientStorageURI(const char *base, Error &error) +{ + if (memcmp(base, "smb://", 6) != 0) + return nullptr; + + if (!SmbclientInit(error)) + 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; + } + + return new SmbclientStorage(base, ctx2); +} + +const StoragePlugin smbclient_storage_plugin = { + "smbclient", + CreateSmbclientStorageURI, +}; diff --git a/src/storage/plugins/SmbclientStorage.hxx b/src/storage/plugins/SmbclientStorage.hxx new file mode 100644 index 000000000..7c198d920 --- /dev/null +++ b/src/storage/plugins/SmbclientStorage.hxx @@ -0,0 +1,29 @@ +/* + * 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_STORAGE_SMBCLIENT_HXX +#define MPD_STORAGE_SMBCLIENT_HXX + +#include "check.h" + +struct StoragePlugin; + +extern const StoragePlugin smbclient_storage_plugin; + +#endif diff --git a/src/system/Clock.cxx b/src/system/Clock.cxx index 347997a44..9baa0c0ca 100644 --- a/src/system/Clock.cxx +++ b/src/system/Clock.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 @@ -31,6 +31,28 @@ #endif unsigned +MonotonicClockS(void) +{ +#ifdef WIN32 + return GetTickCount() / 1000; +#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */ + static mach_timebase_info_data_t base; + if (base.denom == 0) + (void)mach_timebase_info(&base); + + return (unsigned)((mach_absolute_time() * base.numer / 1000) + / (1000000 * base.denom)); +#elif defined(CLOCK_MONOTONIC) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec; +#else + /* we have no monotonic clock, fall back to time() */ + return time(nullptr); +#endif +} + +unsigned MonotonicClockMS(void) { #ifdef WIN32 @@ -96,3 +118,29 @@ MonotonicClockUS(void) #endif } +#ifdef WIN32 + +gcc_const +static unsigned +DeltaFileTimeS(FILETIME a, FILETIME b) +{ + ULARGE_INTEGER a2, b2; + b2.LowPart = b.dwLowDateTime; + b2.HighPart = b.dwHighDateTime; + a2.LowPart = a.dwLowDateTime; + a2.HighPart = a.dwHighDateTime; + return (a2.QuadPart - b2.QuadPart) / 10000000; +} + +unsigned +GetProcessUptimeS() +{ + FILETIME creation_time, exit_time, kernel_time, user_time, now; + GetProcessTimes(GetCurrentProcess(), &creation_time, + &exit_time, &kernel_time, &user_time); + GetSystemTimeAsFileTime(&now); + + return DeltaFileTimeS(now, creation_time); +} + +#endif diff --git a/src/system/Clock.hxx b/src/system/Clock.hxx index 7be1127bf..333a41000 100644 --- a/src/system/Clock.hxx +++ b/src/system/Clock.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 @@ -25,6 +25,13 @@ #include <stdint.h> /** + * Returns the value of a monotonic clock in seconds. + */ +gcc_pure +unsigned +MonotonicClockS(); + +/** * Returns the value of a monotonic clock in milliseconds. */ gcc_pure @@ -38,4 +45,15 @@ gcc_pure uint64_t MonotonicClockUS(); +#ifdef WIN32 + +/** + * Returns the uptime of the current process in seconds. + */ +gcc_pure +unsigned +GetProcessUptimeS(); + +#endif + #endif diff --git a/src/system/EPollFD.cxx b/src/system/EPollFD.cxx index 5721c0194..43e74712f 100644 --- a/src/system/EPollFD.cxx +++ b/src/system/EPollFD.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,6 +22,21 @@ #include "EPollFD.hxx" #include "FatalError.hxx" +#ifdef __BIONIC__ + +#include <sys/syscall.h> +#include <fcntl.h> + +#define EPOLL_CLOEXEC O_CLOEXEC + +static inline int +epoll_create1(int flags) +{ + return syscall(__NR_epoll_create1, flags); +} + +#endif + EPollFD::EPollFD() :fd(::epoll_create1(EPOLL_CLOEXEC)) { diff --git a/src/system/EPollFD.hxx b/src/system/EPollFD.hxx index 41f7ec377..8b9d7d2ba 100644 --- a/src/system/EPollFD.hxx +++ b/src/system/EPollFD.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 @@ -23,6 +23,7 @@ #include <assert.h> #include <sys/epoll.h> #include <unistd.h> +#include <stdint.h> #include "check.h" diff --git a/src/system/EventFD.cxx b/src/system/EventFD.cxx index 3560bf8c3..9ac4c1d94 100644 --- a/src/system/EventFD.cxx +++ b/src/system/EventFD.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 diff --git a/src/system/EventFD.hxx b/src/system/EventFD.hxx index 67a0258ab..2a70461d9 100644 --- a/src/system/EventFD.hxx +++ b/src/system/EventFD.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/system/EventPipe.cxx b/src/system/EventPipe.cxx index b49d1d0f0..b8fc85aed 100644 --- a/src/system/EventPipe.cxx +++ b/src/system/EventPipe.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 diff --git a/src/system/EventPipe.hxx b/src/system/EventPipe.hxx index 86a10b0bb..42b3bb93d 100644 --- a/src/system/EventPipe.hxx +++ b/src/system/EventPipe.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/system/FatalError.cxx b/src/system/FatalError.cxx index f02b4b581..35e94f169 100644 --- a/src/system/FatalError.cxx +++ b/src/system/FatalError.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,12 +23,15 @@ #include "util/Domain.hxx" #include "LogV.hxx" +#ifdef HAVE_GLIB #include <glib.h> +#endif #include <unistd.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #ifdef WIN32 #include <windows.h> @@ -76,19 +79,13 @@ FatalError(const char *msg, const Error &error) } void -FatalError(const char *msg, GError *error) -{ - FormatFatalError("%s: %s", msg, error->message); -} - -void FatalSystemError(const char *msg) { const char *system_error; #ifdef WIN32 system_error = g_win32_error_message(GetLastError()); #else - system_error = g_strerror(errno); + system_error = strerror(errno); #endif FormatError(fatal_error_domain, "%s: %s", msg, system_error); diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx index 2845359ef..d4698b3d9 100644 --- a/src/system/FatalError.hxx +++ b/src/system/FatalError.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 @@ -21,7 +21,6 @@ #define MPD_FATAL_ERROR_HXX #include "check.h" -#include "gerror.h" #include "Compiler.h" class Error; @@ -45,10 +44,6 @@ gcc_noreturn void FatalError(const char *msg, const Error &error); -gcc_noreturn -void -FatalError(const char *msg, GError *error); - /** * Call this after a system call has failed that is not supposed to * fail. Prints the given message, the system error message (from diff --git a/src/system/PeriodClock.hxx b/src/system/PeriodClock.hxx new file mode 100644 index 000000000..1ba74ece2 --- /dev/null +++ b/src/system/PeriodClock.hxx @@ -0,0 +1,149 @@ +/* + * 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_PERIOD_CLOCK_HXX +#define MPD_PERIOD_CLOCK_HXX + +#include "Clock.hxx" + +/** + * This is a stopwatch which saves the timestamp of an event, and can + * check whether a specified time span has passed since then. + */ +class PeriodClock { +protected: + typedef unsigned Stamp; + +private: + Stamp last; + +public: + /** + * Initializes the object, setting the last time stamp to "0", + * i.e. a Check() will always succeed. If you do not want this + * default behaviour, call Update() immediately after creating the + * object. + */ + constexpr + PeriodClock():last(0) {} + +protected: + static Stamp GetNow() { + return MonotonicClockMS(); + } + + constexpr int Elapsed(Stamp now) const { + return last == 0 + ? -1 + : now - last; + } + + constexpr bool Check(Stamp now, unsigned duration) const { + return now >= last + duration; + } + + void Update(Stamp now) { + last = now; + } + +public: + bool IsDefined() const { + return last != 0; + } + + /** + * Resets the clock. + */ + void Reset() { + last = 0; + } + + /** + * Returns the number of milliseconds elapsed since the last + * update(). Returns -1 if update() was never called. + */ + int Elapsed() const { + return Elapsed(GetNow()); + } + + /** + * Combines a call to Elapsed() and Update(). + */ + int ElapsedUpdate() { + const auto now = GetNow(); + int result = Elapsed(now); + Update(now); + return result; + } + + /** + * Checks whether the specified duration has passed since the last + * update. + * + * @param duration the duration in milliseconds + */ + bool Check(unsigned duration) const { + return Check(GetNow(), duration); + } + + /** + * Updates the time stamp, setting it to the current clock. + */ + void Update() { + Update(GetNow()); + } + + /** + * Updates the time stamp, setting it to the current clock plus the + * specified offset. + */ + void UpdateWithOffset(int offset) { + Update(GetNow() + offset); + } + + /** + * Checks whether the specified duration has passed since the last + * update. If yes, it updates the time stamp. + * + * @param duration the duration in milliseconds + */ + bool CheckUpdate(unsigned duration) { + Stamp now = GetNow(); + if (Check(now, duration)) { + Update(now); + return true; + } else + return false; + } + + /** + * Checks whether the specified duration has passed since the last + * update. After that, it updates the time stamp. + * + * @param duration the duration in milliseconds + */ + bool CheckAlwaysUpdate(unsigned duration) { + Stamp now = GetNow(); + bool ret = Check(now, duration); + Update(now); + return ret; + } +}; + +#endif diff --git a/src/system/Resolver.cxx b/src/system/Resolver.cxx index 5e6ea590b..a94217bac 100644 --- a/src/system/Resolver.cxx +++ b/src/system/Resolver.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,11 +22,12 @@ #include "util/Error.hxx" #include "util/Domain.hxx" -#include <glib.h> - #ifndef WIN32 #include <sys/socket.h> #include <netdb.h> +#ifdef HAVE_TCP +#include <netinet/in.h> +#endif #else #include <ws2tcpip.h> #include <winsock.h> @@ -41,17 +42,17 @@ const Domain resolver_domain("resolver"); -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error) +std::string +sockaddr_to_string(const struct sockaddr *sa, size_t length) { #ifdef HAVE_UN if (sa->sa_family == AF_UNIX) { /* return path of UNIX domain sockets */ const sockaddr_un &s_un = *(const sockaddr_un *)sa; if (length < sizeof(s_un) || s_un.sun_path[0] == 0) - return g_strdup("local"); + return "local"; - return g_strdup(s_un.sun_path); + return s_un.sun_path; } #endif @@ -80,17 +81,23 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error) ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST|NI_NUMERICSERV); - if (ret != 0) { - error.Set(resolver_domain, ret, gai_strerror(ret)); - return NULL; - } + if (ret != 0) + return "unknown"; #ifdef HAVE_IPV6 - if (strchr(host, ':') != NULL) - return g_strconcat("[", host, "]:", serv, NULL); + if (strchr(host, ':') != nullptr) { + std::string result("["); + result.append(host); + result.append("]:"); + result.append(serv); + return result; + } #endif - return g_strconcat(host, ":", serv, NULL); + std::string result(host); + result.push_back(':'); + result.append(serv); + return result; } struct addrinfo * @@ -98,41 +105,42 @@ resolve_host_port(const char *host_port, unsigned default_port, int flags, int socktype, Error &error) { - char *p = g_strdup(host_port); - const char *host = p, *port = NULL; + std::string p(host_port); + const char *host = p.c_str(), *port = nullptr; if (host_port[0] == '[') { /* IPv6 needs enclosing square braces, to differentiate between IP colons and the port separator */ - char *q = strchr(p + 1, ']'); - if (q != NULL && q[1] == ':' && q[2] != 0) { - *q = 0; + size_t q = p.find(']', 1); + if (q != p.npos && p[q + 1] == ':' && p[q + 2] != 0) { + p[q] = 0; + port = host + q + 2; ++host; - port = q + 2; } } - if (port == NULL) { + if (port == nullptr) { /* port is after the colon, but only if it's the only colon (don't split IPv6 addresses) */ - char *q = strchr(p, ':'); - if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) { - *q = 0; - port = q + 1; + auto q = p.find(':'); + if (q != p.npos && p[q + 1] != 0 && + p.find(':', q + 1) == p.npos) { + p[q] = 0; + port = host + q + 1; } } char buffer[32]; - if (port == NULL && default_port != 0) { + if (port == nullptr && default_port != 0) { snprintf(buffer, sizeof(buffer), "%u", default_port); port = buffer; } if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0) - host = NULL; + host = nullptr; addrinfo hints; memset(&hints, 0, sizeof(hints)); @@ -142,12 +150,11 @@ resolve_host_port(const char *host_port, unsigned default_port, struct addrinfo *ai; int ret = getaddrinfo(host, port, &hints, &ai); - g_free(p); if (ret != 0) { error.Format(resolver_domain, ret, "Failed to look up '%s': %s", host_port, gai_strerror(ret)); - return NULL; + return nullptr; } return ai; diff --git a/src/system/Resolver.hxx b/src/system/Resolver.hxx index 62ef455a1..54922d98f 100644 --- a/src/system/Resolver.hxx +++ b/src/system/Resolver.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 @@ -22,27 +22,27 @@ #include "Compiler.h" +#include <string> + #include <stddef.h> struct sockaddr; struct addrinfo; class Error; +class Domain; -extern const class Domain resolver_domain; +extern const Domain resolver_domain; /** * Converts the specified socket address into a string in the form - * "IP:PORT". The return value must be freed with g_free() when you - * don't need it anymore. + * "IP:PORT". * * @param sa the sockaddr struct * @param length the length of #sa in bytes - * @param error location to store the error occurring, or NULL to - * ignore errors */ -gcc_malloc -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error); +gcc_pure +std::string +sockaddr_to_string(const sockaddr *sa, size_t length); /** * Resolve a specification in the form "host", "host:port", @@ -54,7 +54,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error); * @return an #addrinfo linked list that must be freed with * freeaddrinfo(), or NULL on error */ -struct addrinfo * +addrinfo * resolve_host_port(const char *host_port, unsigned default_port, int flags, int socktype, Error &error); diff --git a/src/system/SignalFD.cxx b/src/system/SignalFD.cxx index b89775dcd..173a0cc8c 100644 --- a/src/system/SignalFD.cxx +++ b/src/system/SignalFD.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 @@ -20,7 +20,6 @@ #include "config.h" #ifdef USE_SIGNALFD #include "SignalFD.hxx" -#include "fd_util.h" #include "FatalError.hxx" #include <assert.h> diff --git a/src/system/SignalFD.hxx b/src/system/SignalFD.hxx index 7163782d1..11bf30f74 100644 --- a/src/system/SignalFD.hxx +++ b/src/system/SignalFD.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/system/SocketError.cxx b/src/system/SocketError.cxx index 315a86e1f..e138f4dd3 100644 --- a/src/system/SocketError.cxx +++ b/src/system/SocketError.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 @@ -21,7 +21,7 @@ #include "SocketError.hxx" #include "util/Domain.hxx" -#include <glib.h> +#include <string.h> const Domain socket_domain("socket"); @@ -41,6 +41,6 @@ SocketErrorMessage::SocketErrorMessage(socket_error_t code) #else SocketErrorMessage::SocketErrorMessage(socket_error_t code) - :msg(g_strerror(code)) {} + :msg(strerror(code)) {} #endif diff --git a/src/system/SocketError.hxx b/src/system/SocketError.hxx index 22fbd2441..01abc9884 100644 --- a/src/system/SocketError.hxx +++ b/src/system/SocketError.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 @@ -21,7 +21,7 @@ #define MPD_SOCKET_ERROR_HXX #include "Compiler.h" -#include "util/Error.hxx" +#include "util/Error.hxx" // IWYU pragma: export #ifdef WIN32 #include <winsock2.h> @@ -31,11 +31,13 @@ typedef DWORD socket_error_t; typedef int socket_error_t; #endif +class Domain; + /** * A #Domain for #Error for socket I/O errors. The code is an errno * value (or WSAGetLastError() on Windows). */ -extern const class Domain socket_domain; +extern const Domain socket_domain; gcc_pure static inline socket_error_t diff --git a/src/system/SocketUtil.cxx b/src/system/SocketUtil.cxx index 9c4002386..b9df0d59d 100644 --- a/src/system/SocketUtil.cxx +++ b/src/system/SocketUtil.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,8 +22,6 @@ #include "SocketError.hxx" #include "fd_util.h" -#include <glib.h> - #include <unistd.h> #ifndef WIN32 diff --git a/src/system/SocketUtil.hxx b/src/system/SocketUtil.hxx index 5e582ec0d..652788759 100644 --- a/src/system/SocketUtil.hxx +++ b/src/system/SocketUtil.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/system/fd_util.c b/src/system/fd_util.c index b4a7032e6..b53ecda00 100644 --- a/src/system/fd_util.c +++ b/src/system/fd_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -29,10 +29,6 @@ #include "config.h" /* must be first for large file support */ #include "fd_util.h" -#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4)) -#define _GNU_SOURCE -#endif - #include <assert.h> #include <unistd.h> #include <fcntl.h> @@ -73,7 +69,7 @@ fd_mask_flags(int fd, int and_mask, int xor_mask) #endif /* !WIN32 */ -static int +int fd_set_cloexec(int fd, bool enable) { #ifndef WIN32 diff --git a/src/system/fd_util.h b/src/system/fd_util.h index b7a9a6dd3..f4a940e91 100644 --- a/src/system/fd_util.h +++ b/src/system/fd_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -42,10 +42,6 @@ #include <stddef.h> #ifndef WIN32 -#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4)) -#define _GNU_SOURCE -#endif - #include <sys/types.h> #endif @@ -55,6 +51,9 @@ struct sockaddr; extern "C" { #endif +int +fd_set_cloexec(int fd, bool enable); + /** * Wrapper for dup(), which sets the CLOEXEC flag on the new * descriptor. diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx index 73e46e49f..c2498c9e9 100644 --- a/src/tag/Aiff.cxx +++ b/src/tag/Aiff.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 @@ -26,9 +26,7 @@ #include <limits> #include <stdint.h> -#include <sys/types.h> #include <sys/stat.h> -#include <unistd.h> #include <string.h> static constexpr Domain aiff_domain("aiff"); diff --git a/src/tag/Aiff.hxx b/src/tag/Aiff.hxx index 9000be7f8..cd323ee2e 100644 --- a/src/tag/Aiff.hxx +++ b/src/tag/Aiff.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/tag/ApeLoader.cxx b/src/tag/ApeLoader.cxx index 8251efe10..f473c910e 100644 --- a/src/tag/ApeLoader.cxx +++ b/src/tag/ApeLoader.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,8 +22,6 @@ #include "system/ByteOrder.hxx" #include "fs/FileSystem.hxx" -#include <glib.h> - #include <stdint.h> #include <assert.h> #include <stdio.h> @@ -61,9 +59,9 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback) remaining -= sizeof(footer); assert(remaining > 10); - char *buffer = (char *)g_malloc(remaining); + char *buffer = new char[remaining]; if (fread(buffer, 1, remaining, fp) != remaining) { - g_free(buffer); + delete[] buffer; return false; } @@ -98,7 +96,7 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback) remaining -= size; } - g_free(buffer); + delete[] buffer; return true; } diff --git a/src/tag/ApeLoader.hxx b/src/tag/ApeLoader.hxx index 915c363b4..ce82cc35d 100644 --- a/src/tag/ApeLoader.hxx +++ b/src/tag/ApeLoader.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/tag/ApeReplayGain.cxx b/src/tag/ApeReplayGain.cxx index cc65fb79d..6afb3e96e 100644 --- a/src/tag/ApeReplayGain.cxx +++ b/src/tag/ApeReplayGain.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 diff --git a/src/tag/ApeReplayGain.hxx b/src/tag/ApeReplayGain.hxx index 865add6f1..03c899c5c 100644 --- a/src/tag/ApeReplayGain.hxx +++ b/src/tag/ApeReplayGain.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/tag/ApeTag.cxx b/src/tag/ApeTag.cxx index 2df53947a..f714a1624 100644 --- a/src/tag/ApeTag.cxx +++ b/src/tag/ApeTag.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 diff --git a/src/tag/ApeTag.hxx b/src/tag/ApeTag.hxx index e35edc381..edebf076c 100644 --- a/src/tag/ApeTag.hxx +++ b/src/tag/ApeTag.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 @@ -34,6 +34,6 @@ extern const struct tag_table ape_tags[]; */ bool tag_ape_scan2(Path path_fs, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); #endif diff --git a/src/tag/Riff.cxx b/src/tag/Riff.cxx index ac162bc24..c630f082d 100644 --- a/src/tag/Riff.cxx +++ b/src/tag/Riff.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 @@ -26,7 +26,6 @@ #include <limits> #include <stdint.h> -#include <sys/types.h> #include <sys/stat.h> #include <string.h> diff --git a/src/tag/Riff.hxx b/src/tag/Riff.hxx index fbbdfaaf6..a9af67b7a 100644 --- a/src/tag/Riff.hxx +++ b/src/tag/Riff.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/tag/Set.cxx b/src/tag/Set.cxx new file mode 100644 index 000000000..47a8423bf --- /dev/null +++ b/src/tag/Set.cxx @@ -0,0 +1,115 @@ +/* + * 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 "Set.hxx" +#include "TagBuilder.hxx" + +#include <assert.h> + +/** + * Copy all tag items of the specified type. + */ +static bool +CopyTagItem(TagBuilder &dest, TagType dest_type, + const Tag &src, TagType src_type) +{ + bool found = false; + + for (const auto &item : src) { + if (item.type == src_type) { + dest.AddItem(dest_type, item.value); + found = true; + } + } + + return found; +} + +/** + * Copy all tag items of the specified type. Fall back to "Artist" if + * there is no "AlbumArtist". + */ +static void +CopyTagItem(TagBuilder &dest, const Tag &src, TagType type) +{ + if (!CopyTagItem(dest, type, src, type) && + type == TAG_ALBUM_ARTIST) + CopyTagItem(dest, type, src, TAG_ARTIST); +} + +/** + * Copy all tag items of the types in the mask. + */ +static void +CopyTagMask(TagBuilder &dest, const Tag &src, uint32_t mask) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if ((mask & (1u << i)) != 0) + CopyTagItem(dest, src, TagType(i)); +} + +void +TagSet::InsertUnique(const Tag &src, TagType type, const char *value, + uint32_t group_mask) +{ + TagBuilder builder; + if (value == nullptr) + builder.AddEmptyItem(type); + else + builder.AddItem(type, value); + CopyTagMask(builder, src, group_mask); +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + emplace(builder.Commit()); +#else + insert(builder.Commit()); +#endif +} + +bool +TagSet::CheckUnique(TagType dest_type, + const Tag &tag, TagType src_type, + uint32_t group_mask) +{ + bool found = false; + + for (const auto &item : tag) { + if (item.type == src_type) { + InsertUnique(tag, dest_type, item.value, group_mask); + found = true; + } + } + + return found; +} + +void +TagSet::InsertUnique(const Tag &tag, + TagType type, uint32_t group_mask) +{ + static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES, + "Mask is too small"); + + assert((group_mask & (1u << unsigned(type))) == 0); + + if (!CheckUnique(type, tag, type, group_mask) && + (type != TAG_ALBUM_ARTIST || + /* fall back to "Artist" if no "AlbumArtist" was found */ + !CheckUnique(type, tag, TAG_ARTIST, group_mask))) + InsertUnique(tag, type, nullptr, group_mask); +} diff --git a/src/tag/Set.hxx b/src/tag/Set.hxx new file mode 100644 index 000000000..b5acfcb36 --- /dev/null +++ b/src/tag/Set.hxx @@ -0,0 +1,73 @@ +/* + * 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_TAG_SET_HXX +#define MPD_TAG_SET_HXX + +#include "Compiler.h" +#include "Tag.hxx" + +#include <set> + +#include <string.h> +#include <stdint.h> + +/** + * Helper class for #TagSet which compares two #Tag objects. + */ +struct TagLess { + gcc_pure + bool operator()(const Tag &a, const Tag &b) const { + if (a.num_items != b.num_items) + return a.num_items < b.num_items; + + const unsigned n = a.num_items; + for (unsigned i = 0; i < n; ++i) { + const TagItem &ai = *a.items[i]; + const TagItem &bi = *b.items[i]; + if (ai.type != bi.type) + return unsigned(ai.type) < unsigned(bi.type); + + const int cmp = strcmp(ai.value, bi.value); + if (cmp != 0) + return cmp < 0; + } + + return false; + } +}; + +/** + * A set of #Tag objects. + */ +class TagSet : public std::set<Tag, TagLess> { +public: + void InsertUnique(const Tag &tag, + TagType type, uint32_t group_mask); + +private: + void InsertUnique(const Tag &src, TagType type, const char *value, + uint32_t group_mask); + + bool CheckUnique(TagType dest_type, + const Tag &tag, TagType src_type, + uint32_t group_mask); +}; + +#endif diff --git a/src/tag/Tag.cxx b/src/tag/Tag.cxx index 6bf070429..1b338ae8d 100644 --- a/src/tag/Tag.cxx +++ b/src/tag/Tag.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,9 +22,9 @@ #include "TagPool.hxx" #include "TagString.hxx" #include "TagSettings.h" +#include "TagBuilder.hxx" #include "util/ASCII.hxx" -#include <glib.h> #include <assert.h> #include <string.h> @@ -58,12 +58,6 @@ tag_name_parse_i(const char *name) return TAG_NUM_OF_ITEM_TYPES; } -static size_t -items_size(const Tag &tag) -{ - return tag.num_items * sizeof(TagItem *); -} - void Tag::Clear() { @@ -75,28 +69,18 @@ Tag::Clear() tag_pool_put_item(items[i]); tag_pool_lock.unlock(); - g_free(items); + delete[] items; items = nullptr; num_items = 0; } -Tag::~Tag() -{ - tag_pool_lock.lock(); - for (int i = num_items; --i >= 0; ) - tag_pool_put_item(items[i]); - tag_pool_lock.unlock(); - - g_free(items); -} - Tag::Tag(const Tag &other) :time(other.time), has_playlist(other.has_playlist), - items(nullptr), - num_items(other.num_items) + num_items(other.num_items), + items(nullptr) { if (num_items > 0) { - items = (TagItem **)g_malloc(items_size(other)); + items = new TagItem *[num_items]; tag_pool_lock.lock(); for (unsigned i = 0; i < num_items; i++) @@ -108,46 +92,9 @@ Tag::Tag(const Tag &other) Tag * Tag::Merge(const Tag &base, const Tag &add) { - unsigned n; - - /* allocate new tag object */ - - Tag *ret = new Tag(); - ret->time = add.time > 0 ? add.time : base.time; - ret->num_items = base.num_items + add.num_items; - ret->items = ret->num_items > 0 - ? (TagItem **)g_malloc(items_size(*ret)) - : nullptr; - - tag_pool_lock.lock(); - - /* copy all items from "add" */ - - for (unsigned i = 0; i < add.num_items; ++i) - ret->items[i] = tag_pool_dup_item(add.items[i]); - - n = add.num_items; - - /* copy additional items from "base" */ - - for (unsigned i = 0; i < base.num_items; ++i) - if (!add.HasType(base.items[i]->type)) - ret->items[n++] = tag_pool_dup_item(base.items[i]); - - tag_pool_lock.unlock(); - - assert(n <= ret->num_items); - - if (n < ret->num_items) { - /* some tags were not copied - shrink ret->items */ - assert(n > 0); - - ret->num_items = n; - ret->items = (TagItem **) - g_realloc(ret->items, items_size(*ret)); - } - - return ret; + TagBuilder builder(add); + builder.Complement(base); + return builder.CommitNew(); } Tag * @@ -171,9 +118,9 @@ Tag::GetValue(TagType type) const { assert(type < TAG_NUM_OF_ITEM_TYPES); - for (unsigned i = 0; i < num_items; i++) - if (items[i]->type == type) - return items[i]->value; + for (const auto &item : *this) + if (item.type == type) + return item.value; return nullptr; } @@ -183,43 +130,3 @@ Tag::HasType(TagType type) const { return GetValue(type) != nullptr; } - -void -Tag::AddItemInternal(TagType type, const char *value, size_t len) -{ - unsigned int i = num_items; - - char *p = FixTagString(value, len); - if (p != nullptr) { - value = p; - len = strlen(value); - } - - num_items++; - - items = (TagItem **)g_realloc(items, items_size(*this)); - - tag_pool_lock.lock(); - items[i] = tag_pool_get_item(type, value, len); - tag_pool_lock.unlock(); - - g_free(p); -} - -void -Tag::AddItem(TagType type, const char *value, size_t len) -{ - if (ignore_tag_items[type]) - return; - - if (value == nullptr || len == 0) - return; - - AddItemInternal(type, value, len); -} - -void -Tag::AddItem(TagType type, const char *value) -{ - AddItem(type, value, strlen(value)); -} diff --git a/src/tag/Tag.hxx b/src/tag/Tag.hxx index 5846e7a9d..74221417f 100644 --- a/src/tag/Tag.hxx +++ b/src/tag/Tag.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 @@ -20,11 +20,12 @@ #ifndef MPD_TAG_HXX #define MPD_TAG_HXX -#include "TagType.h" -#include "TagItem.hxx" +#include "TagType.h" // IWYU pragma: export +#include "TagItem.hxx" // IWYU pragma: export #include "Compiler.h" #include <algorithm> +#include <iterator> #include <stddef.h> @@ -47,23 +48,23 @@ struct Tag { */ bool has_playlist; + /** the total number of tag items in the #items array */ + unsigned short num_items; + /** an array of tag items */ TagItem **items; - /** the total number of tag items in the #items array */ - unsigned num_items; - /** * Create an empty tag. */ Tag():time(-1), has_playlist(false), - items(nullptr), num_items(0) {} + num_items(0), items(nullptr) {} Tag(const Tag &other); Tag(Tag &&other) :time(other.time), has_playlist(other.has_playlist), - items(other.items), num_items(other.num_items) { + num_items(other.num_items), items(other.items) { other.items = nullptr; other.num_items = 0; } @@ -71,7 +72,9 @@ struct Tag { /** * Free the tag object and all its items. */ - ~Tag(); + ~Tag() { + Clear(); + } Tag &operator=(const Tag &other) = delete; @@ -104,24 +107,6 @@ struct Tag { void Clear(); /** - * Appends a new tag item. - * - * @param type the type of the new tag item - * @param value the value of the tag item (not null-terminated) - * @param len the length of #value - */ - void AddItem(TagType type, const char *value, size_t len); - - /** - * Appends a new tag item. - * - * @param tag the #tag object - * @param type the type of the new tag item - * @param value the value of the tag item (null-terminated) - */ - void AddItem(TagType type, const char *value); - - /** * Merges the data from two tags. If both tags share data for the * same TagType, only data from "add" is used. * @@ -153,8 +138,58 @@ struct Tag { gcc_pure bool HasType(TagType type) const; -private: - void AddItemInternal(TagType type, const char *value, size_t len); + class const_iterator { + friend struct Tag; + const TagItem *const*cursor; + + constexpr const_iterator(const TagItem *const*_cursor) + :cursor(_cursor) {} + + public: + constexpr const TagItem &operator*() const { + return **cursor; + } + + constexpr const TagItem *operator->() const { + return *cursor; + } + + const_iterator &operator++() { + ++cursor; + return *this; + } + + const_iterator operator++(int) { + auto result = cursor++; + return const_iterator{result}; + } + + const_iterator &operator--() { + --cursor; + return *this; + } + + const_iterator operator--(int) { + auto result = cursor--; + return const_iterator{result}; + } + + constexpr bool operator==(const_iterator other) const { + return cursor == other.cursor; + } + + constexpr bool operator!=(const_iterator other) const { + return cursor != other.cursor; + } + }; + + const_iterator begin() const { + return const_iterator{items}; + } + + const_iterator end() const { + return const_iterator{items + num_items}; + } }; /** diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx index 25e5cc24b..bc8ea2ae7 100644 --- a/src/tag/TagBuilder.cxx +++ b/src/tag/TagBuilder.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 @@ -24,23 +24,90 @@ #include "TagString.hxx" #include "Tag.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> +#include <stdlib.h> -void -TagBuilder::Clear() +TagBuilder::TagBuilder(const Tag &other) + :time(other.time), has_playlist(other.has_playlist) { - time = -1; - has_playlist = false; + items.reserve(other.num_items); tag_pool_lock.lock(); + for (unsigned i = 0, n = other.num_items; i != n; ++i) + items.push_back(tag_pool_dup_item(other.items[i])); + tag_pool_lock.unlock(); +} + +TagBuilder::TagBuilder(Tag &&other) + :time(other.time), has_playlist(other.has_playlist) +{ + /* move all TagItem pointers from the Tag object; we don't + need to contact the tag pool, because all we do is move + references */ + items.reserve(other.num_items); + std::copy_n(other.items, other.num_items, std::back_inserter(items)); + + /* discard the pointers from the Tag object */ + other.num_items = 0; + delete[] other.items; + other.items = nullptr; +} + +TagBuilder & +TagBuilder::operator=(const TagBuilder &other) +{ + /* copy all attributes */ + time = other.time; + has_playlist = other.has_playlist; + items = other.items; + + /* increment the tag pool refcounters */ + tag_pool_lock.lock(); for (auto i : items) - tag_pool_put_item(i); + tag_pool_dup_item(i); tag_pool_lock.unlock(); + return *this; +} + +TagBuilder & +TagBuilder::operator=(TagBuilder &&other) +{ + time = other.time; + has_playlist = other.has_playlist; + items = std::move(other.items); + + return *this; +} + +TagBuilder & +TagBuilder::operator=(Tag &&other) +{ + time = other.time; + has_playlist = other.has_playlist; + + /* move all TagItem pointers from the Tag object; we don't + need to contact the tag pool, because all we do is move + references */ items.clear(); + items.reserve(other.num_items); + std::copy_n(other.items, other.num_items, std::back_inserter(items)); + + /* discard the pointers from the Tag object */ + other.num_items = 0; + delete[] other.items; + other.items = nullptr; + + return *this; +} + +void +TagBuilder::Clear() +{ + time = -1; + has_playlist = false; + RemoveAll(); } void @@ -57,7 +124,7 @@ TagBuilder::Commit(Tag &tag) object */ const unsigned n_items = items.size(); tag.num_items = n_items; - tag.items = g_new(TagItem *, n_items); + tag.items = new TagItem *[n_items]; std::copy_n(items.begin(), n_items, tag.items); items.clear(); @@ -66,14 +133,51 @@ TagBuilder::Commit(Tag &tag) Clear(); } -Tag * +Tag TagBuilder::Commit() { + Tag tag; + Commit(tag); + return tag; +} + +Tag * +TagBuilder::CommitNew() +{ Tag *tag = new Tag(); Commit(*tag); return tag; } +bool +TagBuilder::HasType(TagType type) const +{ + for (auto i : items) + if (i->type == type) + return true; + + return false; +} + +void +TagBuilder::Complement(const Tag &other) +{ + if (time <= 0) + time = other.time; + + has_playlist |= other.has_playlist; + + items.reserve(items.size() + other.num_items); + + tag_pool_lock.lock(); + for (unsigned i = 0, n = other.num_items; i != n; ++i) { + TagItem *item = other.items[i]; + if (!HasType(item->type)) + items.push_back(tag_pool_dup_item(item)); + } + tag_pool_lock.unlock(); +} + inline void TagBuilder::AddItemInternal(TagType type, const char *value, size_t length) { @@ -90,7 +194,7 @@ TagBuilder::AddItemInternal(TagType type, const char *value, size_t length) auto i = tag_pool_get_item(type, value, length); tag_pool_lock.unlock(); - g_free(p); + free(p); items.push_back(i); } @@ -113,3 +217,39 @@ TagBuilder::AddItem(TagType type, const char *value) AddItem(type, value, strlen(value)); } + +void +TagBuilder::AddEmptyItem(TagType type) +{ + tag_pool_lock.lock(); + auto i = tag_pool_get_item(type, "", 0); + tag_pool_lock.unlock(); + + items.push_back(i); +} + +void +TagBuilder::RemoveAll() +{ + tag_pool_lock.lock(); + for (auto i : items) + tag_pool_put_item(i); + tag_pool_lock.unlock(); + + items.clear(); +} + +void +TagBuilder::RemoveType(TagType type) +{ + const auto begin = items.begin(), end = items.end(); + + items.erase(std::remove_if(begin, end, + [type](TagItem *item) { + if (item->type != type) + return false; + tag_pool_put_item(item); + return true; + }), + end); +} diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx index ffc60a1b2..6de52b775 100644 --- a/src/tag/TagBuilder.hxx +++ b/src/tag/TagBuilder.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 @@ -63,7 +63,14 @@ public: } TagBuilder(const TagBuilder &other) = delete; - TagBuilder &operator=(const TagBuilder &other) = delete; + + explicit TagBuilder(const Tag &other); + explicit TagBuilder(Tag &&other); + + TagBuilder &operator=(const TagBuilder &other); + TagBuilder &operator=(TagBuilder &&other); + + TagBuilder &operator=(Tag &&other); /** * Returns true if the tag contains no items. This ignores the "time" @@ -90,11 +97,17 @@ public: void Commit(Tag &tag); /** + * Create a new #Tag instance from data in this object. This + * object is empty afterwards. + */ + Tag Commit(); + + /** * Create a new #Tag instance from data in this object. The * returned object is owned by the caller. This object is * empty afterwards. */ - Tag *Commit(); + Tag *CommitNew(); void SetTime(int _time) { time = _time; @@ -109,6 +122,19 @@ public: } /** + * Checks whether the tag contains one or more items with + * the specified type. + */ + gcc_pure + bool HasType(TagType type) const; + + /** + * Copy attributes and items from the other object that do not + * exist in this object. + */ + void Complement(const Tag &other); + + /** * Appends a new tag item. * * @param type the type of the new tag item @@ -127,6 +153,23 @@ public: gcc_nonnull_all void AddItem(TagType type, const char *value); + /** + * Appends a new tag item with an empty value. Do not use + * this unless you know what you're doing - because usually, + * empty values are discarded. + */ + void AddEmptyItem(TagType type); + + /** + * Removes all tag items. + */ + void RemoveAll(); + + /** + * Removes all tag items of the specified type. + */ + void RemoveType(TagType type); + private: gcc_nonnull_all void AddItemInternal(TagType type, const char *value, size_t length); diff --git a/src/tag/TagConfig.cxx b/src/tag/TagConfig.cxx index b8be4fc4c..00f20d1c0 100644 --- a/src/tag/TagConfig.cxx +++ b/src/tag/TagConfig.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 @@ -21,16 +21,16 @@ #include "TagConfig.hxx" #include "TagSettings.h" #include "Tag.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "system/FatalError.hxx" +#include "util/Alloc.hxx" #include "util/ASCII.hxx" - -#include <glib.h> +#include "util/StringUtil.hxx" #include <algorithm> -#include <string.h> +#include <stdlib.h> void TagLoadConfig() @@ -46,14 +46,14 @@ TagLoadConfig() bool quit = false; char *temp, *c, *s; - temp = c = s = g_strdup(value); + temp = c = s = xstrdup(value); do { if (*s == ',' || *s == '\0') { if (*s == '\0') quit = true; *s = '\0'; - c = g_strstrip(c); + c = Strip(c); if (*c == 0) continue; @@ -70,5 +70,5 @@ TagLoadConfig() s++; } while (!quit); - g_free(temp); + free(temp); } diff --git a/src/tag/TagConfig.hxx b/src/tag/TagConfig.hxx index 5ec6766d4..0088e9757 100644 --- a/src/tag/TagConfig.hxx +++ b/src/tag/TagConfig.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 @@ -20,8 +20,6 @@ #ifndef MPD_TAG_CONFIG_HXX #define MPD_TAG_CONFIG_HXX -#include "TagType.h" - void TagLoadConfig(); diff --git a/src/tag/TagHandler.cxx b/src/tag/TagHandler.cxx index 80982ef50..014834b88 100644 --- a/src/tag/TagHandler.cxx +++ b/src/tag/TagHandler.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 diff --git a/src/tag/TagHandler.hxx b/src/tag/TagHandler.hxx index b8c3c6b79..6f737bf56 100644 --- a/src/tag/TagHandler.hxx +++ b/src/tag/TagHandler.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/tag/TagId3.cxx b/src/tag/TagId3.cxx index df70a95e5..cbc293ff6 100644 --- a/src/tag/TagId3.cxx +++ b/src/tag/TagId3.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 @@ -21,23 +21,28 @@ #include "TagId3.hxx" #include "TagHandler.hxx" #include "TagTable.hxx" -#include "Tag.hxx" #include "TagBuilder.hxx" +#include "util/Alloc.hxx" +#include "util/StringUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include "ConfigGlobal.hxx" +#include "config/ConfigGlobal.hxx" #include "Riff.hxx" #include "Aiff.hxx" #include "fs/Path.hxx" #include "fs/FileSystem.hxx" +#ifdef HAVE_GLIB #include <glib.h> +#endif + #include <id3tag.h> +#include <string> + #include <stdio.h> #include <stdlib.h> -#include <errno.h> #include <string.h> # ifndef ID3_FRAME_COMPOSER @@ -70,14 +75,11 @@ tag_is_id3v1(struct id3_tag *tag) static id3_utf8_t * tag_id3_getstring(const struct id3_frame *frame, unsigned i) { - union id3_field *field; - const id3_ucs4_t *ucs4; - - field = id3_frame_field(frame, i); + id3_field *field = id3_frame_field(frame, i); if (field == nullptr) return nullptr; - ucs4 = id3_field_getstring(field); + const id3_ucs4_t *ucs4 = id3_field_getstring(field); if (ucs4 == nullptr) return nullptr; @@ -89,17 +91,16 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i) static id3_utf8_t * import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4) { - id3_utf8_t *utf8, *utf8_stripped; - id3_latin1_t *isostr; - const char *encoding; + id3_utf8_t *utf8; +#ifdef HAVE_GLIB /* use encoding field here? */ + const char *encoding; if (is_id3v1 && (encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) { - isostr = id3_ucs4_latin1duplicate(ucs4); - if (G_UNLIKELY(!isostr)) { + id3_latin1_t *isostr = id3_ucs4_latin1duplicate(ucs4); + if (gcc_unlikely(isostr == nullptr)) return nullptr; - } utf8 = (id3_utf8_t *) g_convert_with_fallback((const char*)isostr, -1, @@ -110,19 +111,24 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4) FormatWarning(id3_domain, "Unable to convert %s string to UTF-8: '%s'", encoding, isostr); - g_free(isostr); + free(isostr); return nullptr; } - g_free(isostr); + free(isostr); } else { +#else + (void)is_id3v1; +#endif utf8 = id3_ucs4_utf8duplicate(ucs4); - if (G_UNLIKELY(!utf8)) { + if (gcc_unlikely(utf8 == nullptr)) return nullptr; - } +#ifdef HAVE_GLIB } +#endif - utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8)); - g_free(utf8); + id3_utf8_t *utf8_stripped = (id3_utf8_t *) + xstrdup(Strip((char *)utf8)); + free(utf8); return utf8_stripped; } @@ -139,17 +145,12 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, TagType type, const struct tag_handler *handler, void *handler_ctx) { - id3_ucs4_t const *ucs4; - id3_utf8_t *utf8; - union id3_field const *field; - unsigned int nstrings, i; - if (frame->nfields != 2) return; /* check the encoding field */ - field = id3_frame_field(frame, 0); + const id3_field *field = id3_frame_field(frame, 0); if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING) return; @@ -160,22 +161,22 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, return; /* Get the number of strings available */ - nstrings = id3_field_getnstrings(field); - for (i = 0; i < nstrings; i++) { - ucs4 = id3_field_getstrings(field, i); + const unsigned nstrings = id3_field_getnstrings(field); + for (unsigned i = 0; i < nstrings; i++) { + const id3_ucs4_t *ucs4 = id3_field_getstrings(field, i); if (ucs4 == nullptr) continue; if (type == TAG_GENRE) ucs4 = id3_genre_name(ucs4); - utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); if (utf8 == nullptr) continue; tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8); - g_free(utf8); + free(utf8); } } @@ -209,28 +210,24 @@ tag_id3_import_comment_frame(struct id3_tag *tag, const struct tag_handler *handler, void *handler_ctx) { - id3_ucs4_t const *ucs4; - id3_utf8_t *utf8; - union id3_field const *field; - if (frame->nfields != 4) return; /* for now I only read the 4th field, with the fullstring */ - field = id3_frame_field(frame, 3); + const id3_field *field = id3_frame_field(frame, 3); if (field == nullptr) return; - ucs4 = id3_field_getfullstring(field); + const id3_ucs4_t *ucs4 = id3_field_getfullstring(field); if (ucs4 == nullptr) return; - utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); if (utf8 == nullptr) return; tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8); - g_free(utf8); + free(utf8); } /** @@ -277,19 +274,15 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag, void *handler_ctx) { for (unsigned i = 0;; ++i) { - const struct id3_frame *frame; - id3_utf8_t *name, *value; - TagType type; - - frame = id3_tag_findframe(id3_tag, "TXXX", i); + const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i); if (frame == nullptr) break; - name = tag_id3_getstring(frame, 1); + id3_utf8_t *name = tag_id3_getstring(frame, 1); if (name == nullptr) continue; - value = tag_id3_getstring(frame, 2); + id3_utf8_t *value = tag_id3_getstring(frame, 2); if (value == nullptr) continue; @@ -297,7 +290,7 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag, (const char *)name, (const char *)value); - type = tag_id3_parse_txxx_name((const char*)name); + TagType type = tag_id3_parse_txxx_name((const char*)name); free(name); if (type != TAG_NUM_OF_ITEM_TYPES) @@ -316,21 +309,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag, const struct tag_handler *handler, void *handler_ctx) { for (unsigned i = 0;; ++i) { - const struct id3_frame *frame; - union id3_field *field; - const id3_latin1_t *name; - const id3_byte_t *value; - id3_length_t length; - - frame = id3_tag_findframe(id3_tag, "UFID", i); + const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i); if (frame == nullptr) break; - field = id3_frame_field(frame, 0); + id3_field *field = id3_frame_field(frame, 0); if (field == nullptr) continue; - name = id3_field_getlatin1(field); + const id3_latin1_t *name = id3_field_getlatin1(field); if (name == nullptr || strcmp((const char *)name, "http://musicbrainz.org") != 0) continue; @@ -339,14 +326,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag, if (field == nullptr) continue; - value = id3_field_getbinarydata(field, &length); + id3_length_t length; + const id3_byte_t *value = + id3_field_getbinarydata(field, &length); if (value == nullptr || length == 0) continue; - char *p = g_strndup((const char *)value, length); + std::string p((const char *)value, length); tag_handler_invoke_tag(handler, handler_ctx, - TAG_MUSICBRAINZ_TRACKID, p); - g_free(p); + TAG_MUSICBRAINZ_TRACKID, p.c_str()); } } @@ -393,73 +381,57 @@ tag_id3_import(struct id3_tag *tag) scan_id3_tag(tag, &add_tag_handler, &tag_builder); return tag_builder.IsEmpty() ? nullptr - : tag_builder.Commit(); + : tag_builder.CommitNew(); } -static int +static size_t fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence) { if (fseek(stream, offset, whence) != 0) return 0; return fread(buf, 1, size, stream); } -static int +static long get_id3v2_footer_size(FILE *stream, long offset, int whence) { id3_byte_t buf[ID3_TAG_QUERYSIZE]; - int bufsize; - - bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); - if (bufsize <= 0) return 0; + size_t bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); + if (bufsize == 0) return 0; return id3_tag_query(buf, bufsize); } static struct id3_tag * tag_id3_read(FILE *stream, long offset, int whence) { - struct id3_tag *tag; - id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; - int tag_size; - int query_buffer_size; - /* It's ok if we get less than we asked for */ - query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, - stream, offset, whence); + id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; + size_t query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, + stream, offset, whence); if (query_buffer_size <= 0) return nullptr; /* Look for a tag header */ - tag_size = id3_tag_query(query_buffer, query_buffer_size); + long tag_size = id3_tag_query(query_buffer, query_buffer_size); if (tag_size <= 0) return nullptr; /* Found a tag. Allocate a buffer and read it in. */ - id3_byte_t *tag_buffer = (id3_byte_t *)g_malloc(tag_size); - if (!tag_buffer) - return nullptr; - + id3_byte_t *tag_buffer = new id3_byte_t[tag_size]; int tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence); if (tag_buffer_size < tag_size) { - g_free(tag_buffer); + delete[] tag_buffer; return nullptr; } - tag = id3_tag_parse(tag_buffer, tag_buffer_size); - - g_free(tag_buffer); - + id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size); + delete[] tag_buffer; return tag; } static struct id3_tag * tag_id3_find_from_beginning(FILE *stream) { - struct id3_tag *tag; - struct id3_tag *seektag; - struct id3_frame *frame; - int seek; - - tag = tag_id3_read(stream, 0, SEEK_SET); + id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET); if (!tag) { return nullptr; } else if (tag_is_id3v1(tag)) { @@ -469,14 +441,15 @@ tag_id3_find_from_beginning(FILE *stream) } /* We have an id3v2 tag, so let's look for SEEK frames */ + id3_frame *frame; while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { /* Found a SEEK frame, get it's value */ - seek = id3_field_getint(id3_frame_field(frame, 0)); + int seek = id3_field_getint(id3_frame_field(frame, 0)); if (seek < 0) break; /* Get the tag specified by the SEEK frame */ - seektag = tag_id3_read(stream, seek, SEEK_CUR); + id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR); if (!seektag || tag_is_id3v1(seektag)) break; @@ -491,20 +464,16 @@ tag_id3_find_from_beginning(FILE *stream) static struct id3_tag * tag_id3_find_from_end(FILE *stream) { - struct id3_tag *tag; - struct id3_tag *v1tag; - int tagsize; - /* Get an id3v1 tag from the end of file for later use */ - v1tag = tag_id3_read(stream, -128, SEEK_END); + id3_tag *v1tag = tag_id3_read(stream, -128, SEEK_END); /* Get the id3v2 tag size from the footer (located before v1tag) */ - tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END); + int tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END); if (tagsize >= 0) return v1tag; /* Get the tag which the footer belongs to */ - tag = tag_id3_read(stream, tagsize, SEEK_CUR); + id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR); if (!tag) return v1tag; @@ -527,16 +496,16 @@ tag_id3_riff_aiff_load(FILE *file) /* too large, don't allocate so much memory */ return nullptr; - id3_byte_t *buffer = (id3_byte_t *)g_malloc(size); + id3_byte_t *buffer = new id3_byte_t[size]; size_t ret = fread(buffer, size, 1, file); if (ret != 1) { LogWarning(id3_domain, "Failed to read RIFF chunk"); - g_free(buffer); + delete[] buffer; return nullptr; } struct id3_tag *tag = id3_tag_parse(buffer, size); - g_free(buffer); + delete[] buffer; return tag; } @@ -545,7 +514,7 @@ tag_id3_load(Path path_fs, Error &error) { FILE *file = FOpen(path_fs, "rb"); if (file == nullptr) { - error.FormatErrno("Failed to open file %s", path_fs); + error.FormatErrno("Failed to open file %s", path_fs.c_str()); return nullptr; } diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx index 749166116..1928d539d 100644 --- a/src/tag/TagId3.hxx +++ b/src/tag/TagId3.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 @@ -33,10 +33,10 @@ class Error; bool tag_id3_scan(Path path_fs, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); Tag * -tag_id3_import(struct id3_tag *); +tag_id3_import(id3_tag *); /** * Loads the ID3 tags from the file into a libid3tag object. The @@ -53,8 +53,8 @@ tag_id3_load(Path path_fs, Error &error); * */ void -scan_id3_tag(struct id3_tag *tag, - const struct tag_handler *handler, void *handler_ctx); +scan_id3_tag(id3_tag *tag, + const tag_handler *handler, void *handler_ctx); #else @@ -62,7 +62,7 @@ scan_id3_tag(struct id3_tag *tag, static inline bool tag_id3_scan(gcc_unused Path path_fs, - gcc_unused const struct tag_handler *handler, + gcc_unused const tag_handler *handler, gcc_unused void *handler_ctx) { return false; diff --git a/src/tag/TagItem.hxx b/src/tag/TagItem.hxx index 7c3100393..489ecde3a 100644 --- a/src/tag/TagItem.hxx +++ b/src/tag/TagItem.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/tag/TagNames.c b/src/tag/TagNames.c index eac6fc59a..477ed5f21 100644 --- a/src/tag/TagNames.c +++ b/src/tag/TagNames.c @@ -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/tag/TagPool.cxx b/src/tag/TagPool.cxx index cc28ea9a6..29f605337 100644 --- a/src/tag/TagPool.cxx +++ b/src/tag/TagPool.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 @@ -20,23 +20,46 @@ #include "config.h" #include "TagPool.hxx" #include "TagItem.hxx" - -#include <glib.h> +#include "util/Cast.hxx" +#include "util/VarSize.hxx" #include <assert.h> #include <string.h> +#include <stdlib.h> Mutex tag_pool_lock; -#define NUM_SLOTS 4096 +static constexpr size_t NUM_SLOTS = 4096; -struct slot { - struct slot *next; +struct TagPoolSlot { + TagPoolSlot *next; unsigned char ref; TagItem item; -} mpd_packed; -static struct slot *slots[NUM_SLOTS]; + TagPoolSlot(TagPoolSlot *_next, TagType type, + const char *value, size_t length) + :next(_next), ref(1) { + item.type = type; + memcpy(item.value, value, length); + item.value[length] = 0; + } + + static TagPoolSlot *Create(TagPoolSlot *_next, TagType type, + const char *value, size_t length); +} gcc_packed; + +TagPoolSlot * +TagPoolSlot::Create(TagPoolSlot *_next, TagType type, + const char *value, size_t length) +{ + TagPoolSlot *dummy; + return NewVarSize<TagPoolSlot>(sizeof(dummy->item.value), + length + 1, + _next, type, + value, length); +} + +static TagPoolSlot *slots[NUM_SLOTS]; static inline unsigned calc_hash_n(TagType type, const char *p, size_t length) @@ -64,35 +87,32 @@ calc_hash(TagType type, const char *p) return hash ^ type; } -static inline struct slot * +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) + constexpr +#endif +static inline TagPoolSlot * tag_item_to_slot(TagItem *item) { - return (struct slot*)(((char*)item) - offsetof(struct slot, item)); + return &ContainerCast(*item, &TagPoolSlot::item); } -static struct slot *slot_alloc(struct slot *next, - TagType type, - const char *value, int length) +static inline TagPoolSlot ** +tag_value_slot_p(TagType type, const char *value, size_t length) { - struct slot *slot; - - slot = (struct slot *) - g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1); - slot->next = next; - slot->ref = 1; - slot->item.type = type; - memcpy(slot->item.value, value, length); - slot->item.value[length] = 0; - return slot; + return &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; +} + +static inline TagPoolSlot ** +tag_value_slot_p(TagType type, const char *value) +{ + return &slots[calc_hash(type, value) % NUM_SLOTS]; } TagItem * tag_pool_get_item(TagType type, const char *value, size_t length) { - struct slot **slot_p, *slot; - - slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; - for (slot = *slot_p; slot != nullptr; slot = slot->next) { + auto slot_p = tag_value_slot_p(type, value, length); + for (auto slot = *slot_p; slot != nullptr; slot = slot->next) { if (slot->item.type == type && length == strlen(slot->item.value) && memcmp(value, slot->item.value, length) == 0 && @@ -103,7 +123,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length) } } - slot = slot_alloc(*slot_p, type, value, length); + auto slot = TagPoolSlot::Create(*slot_p, type, value, length); *slot_p = slot; return &slot->item; } @@ -111,7 +131,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length) TagItem * tag_pool_dup_item(TagItem *item) { - struct slot *slot = tag_item_to_slot(item); + TagPoolSlot *slot = tag_item_to_slot(item); assert(slot->ref > 0); @@ -122,11 +142,10 @@ tag_pool_dup_item(TagItem *item) /* the reference counter overflows above 0xff; duplicate the item, and start with 1 */ size_t length = strlen(item->value); - struct slot **slot_p = - &slots[calc_hash_n(item->type, item->value, - length) % NUM_SLOTS]; - slot = slot_alloc(*slot_p, item->type, - item->value, strlen(item->value)); + auto slot_p = tag_value_slot_p(item->type, + item->value, length); + slot = TagPoolSlot::Create(*slot_p, item->type, + item->value, strlen(item->value)); *slot_p = slot; return &slot->item; } @@ -135,7 +154,7 @@ tag_pool_dup_item(TagItem *item) void tag_pool_put_item(TagItem *item) { - struct slot **slot_p, *slot; + TagPoolSlot **slot_p, *slot; slot = tag_item_to_slot(item); assert(slot->ref > 0); @@ -144,12 +163,12 @@ tag_pool_put_item(TagItem *item) if (slot->ref > 0) return; - for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS]; + for (slot_p = tag_value_slot_p(item->type, item->value); *slot_p != slot; slot_p = &(*slot_p)->next) { assert(*slot_p != nullptr); } *slot_p = slot->next; - g_free(slot); + DeleteVarSize(slot); } diff --git a/src/tag/TagPool.hxx b/src/tag/TagPool.hxx index f41f3a724..990ee87bd 100644 --- a/src/tag/TagPool.hxx +++ b/src/tag/TagPool.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/tag/TagRva2.cxx b/src/tag/TagRva2.cxx index 204001aa7..bbb6d11e6 100644 --- a/src/tag/TagRva2.cxx +++ b/src/tag/TagRva2.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 @@ -21,10 +21,10 @@ #include "TagRva2.hxx" #include "ReplayGainInfo.hxx" +#include <id3tag.h> + #include <stdint.h> #include <string.h> -#include <glib.h> -#include <id3tag.h> enum rva2_channel { CHANNEL_OTHER = 0x00, diff --git a/src/tag/TagRva2.hxx b/src/tag/TagRva2.hxx index 98154041a..df559f472 100644 --- a/src/tag/TagRva2.hxx +++ b/src/tag/TagRva2.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 @@ -32,6 +32,6 @@ struct ReplayGainInfo; * @return true on success */ bool -tag_rva2_parse(struct id3_tag *tag, ReplayGainInfo &replay_gain_info); +tag_rva2_parse(id3_tag *tag, ReplayGainInfo &replay_gain_info); #endif diff --git a/src/tag/TagSettings.c b/src/tag/TagSettings.c index eb2db2ba5..e0c577c2b 100644 --- a/src/tag/TagSettings.c +++ b/src/tag/TagSettings.c @@ -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/tag/TagSettings.h b/src/tag/TagSettings.h index 245de2df5..33f89d4be 100644 --- a/src/tag/TagSettings.h +++ b/src/tag/TagSettings.h @@ -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/tag/TagString.cxx b/src/tag/TagString.cxx index 3e8d8c1b0..0c3868eb5 100644 --- a/src/tag/TagString.cxx +++ b/src/tag/TagString.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,17 @@ #include "config.h" #include "TagString.hxx" +#include "util/Alloc.hxx" +#ifdef HAVE_GLIB #include <glib.h> +#endif #include <assert.h> #include <string.h> +#include <stdlib.h> + +#ifdef HAVE_GLIB /** * Replace invalid sequences with the question mark. @@ -33,7 +39,7 @@ patch_utf8(const char *src, size_t length, const gchar *end) { /* duplicate the string, and replace invalid bytes in that buffer */ - char *dest = g_strdup(src); + char *dest = xstrdup(src); do { dest[end - src] = '?'; @@ -58,15 +64,20 @@ fix_utf8(const char *str, size_t length) /* no, it's not - try to import it from ISO-Latin-1 */ temp = g_convert(str, length, "utf-8", "iso-8859-1", nullptr, &written, nullptr); - if (temp != nullptr) + if (temp != nullptr) { /* success! */ - return temp; + char *p = xstrdup(temp); + g_free(temp); + return p; + } /* no, still broken - there's no medication, just patch invalid sequences */ return patch_utf8(str, length, end); } +#endif + static bool char_is_non_printable(unsigned char ch) { @@ -96,7 +107,7 @@ clear_non_printable(const char *p, size_t length) if (first == nullptr) return nullptr; - dest = g_strndup(p, length); + dest = xstrndup(p, length); for (size_t i = first - p; i < length; ++i) if (char_is_non_printable(dest[i])) @@ -108,19 +119,23 @@ clear_non_printable(const char *p, size_t length) char * FixTagString(const char *p, size_t length) { - char *utf8, *cleared; +#ifdef HAVE_GLIB + // TODO: implement without GLib - utf8 = fix_utf8(p, length); + char *utf8 = fix_utf8(p, length); if (utf8 != nullptr) { p = utf8; length = strlen(p); } +#endif - cleared = clear_non_printable(p, length); + char *cleared = clear_non_printable(p, length); +#ifdef HAVE_GLIB if (cleared == nullptr) cleared = utf8; else - g_free(utf8); + free(utf8); +#endif return cleared; } diff --git a/src/tag/TagString.hxx b/src/tag/TagString.hxx index 79255dcd3..a1a9d9d15 100644 --- a/src/tag/TagString.hxx +++ b/src/tag/TagString.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/tag/TagTable.cxx b/src/tag/TagTable.cxx index b51bc35ba..c6e1cff54 100644 --- a/src/tag/TagTable.cxx +++ b/src/tag/TagTable.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 @@ -51,3 +51,13 @@ tag_table_lookup_i(const struct tag_table *table, const char *name) return TAG_NUM_OF_ITEM_TYPES; } + +const char * +tag_table_lookup(const tag_table *table, TagType type) +{ + for (; table->name != nullptr; ++table) + if (table->type == type) + return table->name; + + return nullptr; +} diff --git a/src/tag/TagTable.hxx b/src/tag/TagTable.hxx index c050f61ac..095b4cbff 100644 --- a/src/tag/TagTable.hxx +++ b/src/tag/TagTable.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 @@ -47,4 +47,13 @@ gcc_pure TagType tag_table_lookup_i(const tag_table *table, const char *name); +/** + * Looks up a #TagType in a tag translation table and returns its + * string representation. Returns nullptr if the specified type was + * not found in the table. + */ +gcc_pure +const char * +tag_table_lookup(const tag_table *table, TagType type); + #endif diff --git a/src/tag/TagType.h b/src/tag/TagType.h index 0b8aa55d4..098198445 100644 --- a/src/tag/TagType.h +++ b/src/tag/TagType.h @@ -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/thread/Cond.hxx b/src/thread/Cond.hxx index ed663dc9d..a05d1c67d 100644 --- a/src/thread/Cond.hxx +++ b/src/thread/Cond.hxx @@ -1,24 +1,34 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2009-2014 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_THREAD_COND_HXX -#define MPD_THREAD_COND_HXX +#ifndef THREAD_COND_HXX +#define THREAD_COND_HXX #ifdef WIN32 diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx index 8bc05b8f5..bb25f6c47 100644 --- a/src/thread/CriticalSection.hxx +++ b/src/thread/CriticalSection.hxx @@ -27,8 +27,8 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_THREAD_CRITICAL_SECTION_HXX -#define MPD_THREAD_CRITICAL_SECTION_HXX +#ifndef THREAD_CRITICAL_SECTION_HXX +#define THREAD_CRITICAL_SECTION_HXX #include <windows.h> diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx deleted file mode 100644 index 9ab08e9fd..000000000 --- a/src/thread/GLibCond.hxx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2013 Max Kellermann <max@duempel.org> - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef MPD_THREAD_GLIB_COND_HXX -#define MPD_THREAD_GLIB_COND_HXX - -#include "GLibMutex.hxx" - -/** - * A wrapper for GCond. - */ -class GLibCond { -#if GLIB_CHECK_VERSION(2,32,0) - GCond cond; -#else - GCond *cond; -#endif - -public: - GLibCond() { -#if GLIB_CHECK_VERSION(2,32,0) - g_cond_init(&cond); -#else - cond = g_cond_new(); -#endif - } - - ~GLibCond() { -#if GLIB_CHECK_VERSION(2,32,0) - g_cond_clear(&cond); -#else - g_cond_free(cond); -#endif - } - - GLibCond(const GLibCond &other) = delete; - GLibCond &operator=(const GLibCond &other) = delete; - -private: - GCond *GetNative() { -#if GLIB_CHECK_VERSION(2,32,0) - return &cond; -#else - return cond; -#endif - } - -public: - void signal() { - g_cond_signal(GetNative()); - } - - void broadcast() { - g_cond_broadcast(GetNative()); - } - - void wait(GLibMutex &mutex) { - g_cond_wait(GetNative(), mutex.GetNative()); - } -}; - -#endif diff --git a/src/thread/GLibMutex.hxx b/src/thread/GLibMutex.hxx deleted file mode 100644 index 2c666c1ea..000000000 --- a/src/thread/GLibMutex.hxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2013 Max Kellermann <max@duempel.org> - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef MPD_THREAD_GLIB_MUTEX_HXX -#define MPD_THREAD_GLIB_MUTEX_HXX - -#include <glib.h> - -/** - * A wrapper for GMutex. - */ -class GLibMutex { - friend class GLibCond; - -#if GLIB_CHECK_VERSION(2,32,0) - GMutex mutex; -#else - GMutex *mutex; -#endif - -public: - GLibMutex() { -#if GLIB_CHECK_VERSION(2,32,0) - g_mutex_init(&mutex); -#else - mutex = g_mutex_new(); -#endif - } - - ~GLibMutex() { -#if GLIB_CHECK_VERSION(2,32,0) - g_mutex_clear(&mutex); -#else - g_mutex_free(mutex); -#endif - } - - GLibMutex(const GLibMutex &other) = delete; - GLibMutex &operator=(const GLibMutex &other) = delete; - -private: - GMutex *GetNative() { -#if GLIB_CHECK_VERSION(2,32,0) - return &mutex; -#else - return mutex; -#endif - } - -public: - void lock() { - g_mutex_lock(GetNative()); - } - - bool try_lock() { - return g_mutex_trylock(GetNative()); - } - - void unlock() { - g_mutex_lock(GetNative()); - } -}; - -#endif diff --git a/src/thread/Id.hxx b/src/thread/Id.hxx index fa1cf2cab..7b10de074 100644 --- a/src/thread/Id.hxx +++ b/src/thread/Id.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/thread/Mutex.hxx b/src/thread/Mutex.hxx index 4ed48c972..c17538549 100644 --- a/src/thread/Mutex.hxx +++ b/src/thread/Mutex.hxx @@ -1,24 +1,34 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2009-2014 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_THREAD_MUTEX_HXX -#define MPD_THREAD_MUTEX_HXX +#ifndef THREAD_MUTEX_HXX +#define THREAD_MUTEX_HXX #ifdef WIN32 diff --git a/src/thread/Name.hxx b/src/thread/Name.hxx new file mode 100644 index 000000000..284d1e147 --- /dev/null +++ b/src/thread/Name.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_THREAD_NAME_HXX +#define MPD_THREAD_NAME_HXX + +#ifdef HAVE_PTHREAD_SETNAME_NP +#include <pthread.h> +#include <stdio.h> +#elif defined(HAVE_PRCTL) +#include <sys/prctl.h> +#endif + +static inline void +SetThreadName(const char *name) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP +#ifdef __APPLE__ + pthread_setname_np(name); +#else + pthread_setname_np(pthread_self(), name); +#endif +#elif defined(HAVE_PRCTL) && defined(PR_SET_NAME) + prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); +#else + (void)name; +#endif +} + +template<typename... Args> +static inline void +FormatThreadName(const char *fmt, gcc_unused Args&&... args) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP + char buffer[16]; + snprintf(buffer, sizeof(buffer), fmt, args...); + SetThreadName(buffer); +#else + (void)fmt; +#endif +} + +#endif diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx index 6f98d3ad0..e0d6623dd 100644 --- a/src/thread/PosixCond.hxx +++ b/src/thread/PosixCond.hxx @@ -27,8 +27,8 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_THREAD_POSIX_COND_HXX -#define MPD_THREAD_POSIX_COND_HXX +#ifndef THREAD_POSIX_COND_HXX +#define THREAD_POSIX_COND_HXX #include "PosixMutex.hxx" @@ -41,7 +41,10 @@ class PosixCond { pthread_cond_t cond; public: - constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {} +#ifndef __BIONIC__ + constexpr +#endif + PosixCond():cond(PTHREAD_COND_INITIALIZER) {} PosixCond(const PosixCond &other) = delete; PosixCond &operator=(const PosixCond &other) = delete; diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx index d50764af4..9d1674dd4 100644 --- a/src/thread/PosixMutex.hxx +++ b/src/thread/PosixMutex.hxx @@ -27,8 +27,8 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_THREAD_POSIX_MUTEX_HXX -#define MPD_THREAD_POSIX_MUTEX_HXX +#ifndef THREAD_POSIX_MUTEX_HXX +#define THREAD_POSIX_MUTEX_HXX #include <pthread.h> @@ -41,7 +41,10 @@ class PosixMutex { pthread_mutex_t mutex; public: - constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {} +#ifndef __BIONIC__ + constexpr +#endif + PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {} PosixMutex(const PosixMutex &other) = delete; PosixMutex &operator=(const PosixMutex &other) = delete; diff --git a/src/thread/Slack.hxx b/src/thread/Slack.hxx new file mode 100644 index 000000000..66b2254a4 --- /dev/null +++ b/src/thread/Slack.hxx @@ -0,0 +1,54 @@ +/* + * 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_SLACK_HXX +#define MPD_THREAD_SLACK_HXX + +#ifdef HAVE_PRCTL +#include <sys/prctl.h> +#endif + +/** + * Set the current thread's timer slack to the specified number of + * nanoseconds (requires Linux 2.6.28). This allows the kernel to + * merge multiple wakeups, which is a trick to save energy. + */ +static inline void +SetThreadTimerSlackNS(unsigned long slack_ns) +{ +#if defined(HAVE_PRCTL) && defined(PR_SET_TIMERSLACK) + prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0); +#else + (void)slack_ns; +#endif +} + +static inline void +SetThreadTimerSlackUS(unsigned long slack_us) +{ + SetThreadTimerSlackNS(slack_us * 1000ul); +} + +static inline void +SetThreadTimerSlackMS(unsigned long slack_ms) +{ + SetThreadTimerSlackNS(slack_ms * 1000000ul); +} + +#endif diff --git a/src/thread/Thread.cxx b/src/thread/Thread.cxx index 67bcf7184..2932d478f 100644 --- a/src/thread/Thread.cxx +++ b/src/thread/Thread.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 @@ -21,6 +21,10 @@ #include "Thread.hxx" #include "util/Error.hxx" +#ifdef ANDROID +#include "java/Global.hxx" +#endif + bool Thread::Start(void (*_f)(void *ctx), void *_ctx, Error &error) { @@ -102,6 +106,11 @@ Thread::ThreadProc(void *ctx) #endif thread.f(thread.ctx); + +#ifdef ANDROID + Java::DetachCurrentThread(); +#endif + return nullptr; } diff --git a/src/thread/Thread.hxx b/src/thread/Thread.hxx index d3bd75455..976ff5625 100644 --- a/src/thread/Thread.hxx +++ b/src/thread/Thread.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 @@ -68,7 +68,7 @@ public: Thread(const Thread &) = delete; #ifndef NDEBUG - virtual ~Thread() { + ~Thread() { /* all Thread objects must be destructed manually by calling Join(), to clean up */ assert(!IsDefined()); diff --git a/src/thread/Util.hxx b/src/thread/Util.hxx new file mode 100644 index 000000000..ff8dbbe10 --- /dev/null +++ b/src/thread/Util.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THREAD_UTIL_HXX +#define THREAD_UTIL_HXX + +#ifdef __linux__ +#include <sched.h> +#include <sys/syscall.h> +#include <unistd.h> +#elif defined(WIN32) +#include <windows.h> +#endif + +#ifdef __linux__ + +static int +ioprio_set(int which, int who, int ioprio) +{ + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +static void +ioprio_set_idle() +{ + static constexpr int _IOPRIO_WHO_PROCESS = 1; + static constexpr int _IOPRIO_CLASS_IDLE = 3; + static constexpr int _IOPRIO_CLASS_SHIFT = 13; + static constexpr int _IOPRIO_IDLE = + (_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7; + + ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE); +} + +#endif + +/** + * Lower the current thread's priority to "idle" (very low). + */ +static inline void +SetThreadIdlePriority() +{ +#ifdef __linux__ +#ifdef SCHED_IDLE + static struct sched_param sched_param; + sched_setscheduler(0, SCHED_IDLE, &sched_param); +#endif + + ioprio_set_idle(); + +#elif defined(WIN32) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE); +#endif +}; + +/** + * Raise the current thread's priority to "real-time" (very high). + */ +static inline void +SetThreadRealtime() +{ +#ifdef __linux__ + struct sched_param sched_param; + sched_param.sched_priority = 50; + + int policy = SCHED_FIFO; +#ifdef SCHED_RESET_ON_FORK + policy |= SCHED_RESET_ON_FORK; +#endif + + sched_setscheduler(0, policy, &sched_param); +#endif +}; + +#endif diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx index c05bc05b2..2ce7271b9 100644 --- a/src/thread/WindowsCond.hxx +++ b/src/thread/WindowsCond.hxx @@ -27,8 +27,8 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_THREAD_WINDOWS_COND_HXX -#define MPD_THREAD_WINDOWS_COND_HXX +#ifndef THREAD_WINDOWS_COND_HXX +#define THREAD_WINDOWS_COND_HXX #include "CriticalSection.hxx" diff --git a/src/unix/Daemon.cxx b/src/unix/Daemon.cxx new file mode 100644 index 000000000..490b2def5 --- /dev/null +++ b/src/unix/Daemon.cxx @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Daemon.hxx" +#include "system/FatalError.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "util/Domain.hxx" +#include "PidFile.hxx" +#include "Log.hxx" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +#ifndef WIN32 +#include <sys/wait.h> +#include <signal.h> +#include <pwd.h> +#include <grp.h> +#endif + +static constexpr Domain daemon_domain("daemon"); + +#ifndef WIN32 + +/** the Unix user name which MPD runs as */ +static char *user_name; + +/** the Unix user id which MPD runs as */ +static uid_t user_uid = (uid_t)-1; + +/** the Unix group id which MPD runs as */ +static gid_t user_gid = (gid_t)-1; + +/** the absolute path of the pidfile */ +static AllocatedPath pidfile = AllocatedPath::Null(); + +/* whether "group" conf. option was given */ +static bool had_group = false; + +/** + * The write end of a pipe that is used to notify the parent process + * that initialization has finished and that it should detach. + */ +static int detach_fd = -1; + +void +daemonize_kill(void) +{ + FILE *fp; + int pid, ret; + + if (pidfile.IsNull()) + FatalError("no pid_file specified in the config file"); + + fp = FOpen(pidfile, "r"); + if (fp == nullptr) { + const std::string utf8 = pidfile.ToUTF8(); + FormatFatalSystemError("Unable to open pid file \"%s\"", + utf8.c_str()); + } + + if (fscanf(fp, "%i", &pid) != 1) { + const std::string utf8 = pidfile.ToUTF8(); + FormatFatalError("unable to read the pid from file \"%s\"", + utf8.c_str()); + } + fclose(fp); + + ret = kill(pid, SIGTERM); + if (ret < 0) + FormatFatalSystemError("unable to kill process %i", + int(pid)); + + exit(EXIT_SUCCESS); +} + +void +daemonize_close_stdin(void) +{ + close(STDIN_FILENO); + open("/dev/null", O_RDONLY); +} + +void +daemonize_set_user(void) +{ + if (user_name == nullptr) + return; + + /* set gid */ + if (user_gid != (gid_t)-1 && user_gid != getgid() && + setgid(user_gid) == -1) { + FormatFatalSystemError("Failed to set group %d", + (int)user_gid); + } + +#ifdef _BSD_SOURCE + /* init supplementary groups + * (must be done before we change our uid) + */ + if (!had_group && + /* no need to set the new user's supplementary groups if + we are already this user */ + user_uid != getuid() && + initgroups(user_name, user_gid) == -1) { + FormatFatalSystemError("Failed to set supplementary groups " + "of user \"%s\"", + user_name); + } +#endif + + /* set uid */ + if (user_uid != (uid_t)-1 && user_uid != getuid() && + setuid(user_uid) == -1) { + FormatFatalSystemError("Failed to set user \"%s\"", + user_name); + } +} + +void +daemonize_begin(bool detach) +{ + /* release the current working directory */ + if (chdir("/") < 0) + FatalError("problems changing to root directory"); + + if (!detach) + /* the rest of this function deals with detaching the + process */ + return; + + /* do this before daemonizing so we can fail gracefully if we + can't write to the pid file */ + PidFile pidfile2(pidfile); + + /* flush all file handles before duplicating the buffers */ + + fflush(nullptr); + + /* create a pipe to synchronize the parent and the child */ + + int fds[2]; + if (pipe(fds) < 0) + FatalSystemError("pipe() failed"); + + /* move to a child process */ + + pid_t pid = fork(); + if (pid < 0) + FatalSystemError("fork() failed"); + + if (pid == 0) { + /* in the child process */ + + pidfile2.Close(); + close(fds[0]); + detach_fd = fds[1]; + + /* detach from the current session */ + setsid(); + + /* continue starting MPD */ + return; + } + + /* in the parent process */ + + close(fds[1]); + + int result; + ssize_t nbytes = read(fds[0], &result, sizeof(result)); + if (nbytes == (ssize_t)sizeof(result)) { + /* the child process was successful */ + pidfile2.Write(pid); + exit(EXIT_SUCCESS); + } + + /* something bad happened in the child process */ + + pidfile2.Delete(pidfile); + + int status; + pid_t pid2 = waitpid(pid, &status, 0); + if (pid2 < 0) + FatalSystemError("waitpid() failed"); + + if (WIFSIGNALED(status)) + FormatFatalError("MPD died from signal %d%s", WTERMSIG(status), + WCOREDUMP(status) ? " (core dumped)" : ""); + + exit(WEXITSTATUS(status)); +} + +void +daemonize_commit() +{ + if (detach_fd >= 0) { + /* tell the parent process to let go of us and exit + indicating success */ + int result = 0; + write(detach_fd, &result, sizeof(result)); + close(detach_fd); + } else + /* the pidfile was not written by the parent because + there is no parent - do it now */ + PidFile(pidfile).Write(); +} + +void +daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile) +{ + if (user) { + struct passwd *pwd = getpwnam(user); + if (pwd == nullptr) + FormatFatalError("no such user \"%s\"", user); + + user_uid = pwd->pw_uid; + user_gid = pwd->pw_gid; + + user_name = strdup(user); + + /* this is needed by libs such as arts */ + setenv("HOME", pwd->pw_dir, true); + } + + if (group) { + struct group *grp = getgrnam(group); + if (grp == nullptr) + FormatFatalError("no such group \"%s\"", group); + user_gid = grp->gr_gid; + had_group = true; + } + + + pidfile = std::move(_pidfile); +} + +void +daemonize_finish(void) +{ + if (!pidfile.IsNull()) { + RemoveFile(pidfile); + pidfile = AllocatedPath::Null(); + } + + free(user_name); +} + +#endif diff --git a/src/unix/Daemon.hxx b/src/unix/Daemon.hxx new file mode 100644 index 000000000..fe5681511 --- /dev/null +++ b/src/unix/Daemon.hxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DAEMON_HXX +#define MPD_DAEMON_HXX + +class AllocatedPath; + +#ifndef WIN32 +void +daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile); +#else +static inline void +daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile) +{ (void)user; (void)group; (void)pidfile; } +#endif + +#ifndef WIN32 +void +daemonize_finish(void); +#else +static inline void +daemonize_finish(void) +{ /* nop */ } +#endif + +/** + * Kill the MPD which is currently running, pid determined from the + * pid file. + */ +#ifndef WIN32 +void +daemonize_kill(void); +#else +#include "system/FatalError.hxx" +static inline void +daemonize_kill(void) +{ + FatalError("--kill is not available on WIN32"); +} +#endif + +/** + * Close stdin (fd 0) and re-open it as /dev/null. + */ +#ifndef WIN32 +void +daemonize_close_stdin(void); +#else +static inline void +daemonize_close_stdin(void) {} +#endif + +/** + * Change to the configured Unix user. + */ +#ifndef WIN32 +void +daemonize_set_user(void); +#else +static inline void +daemonize_set_user(void) +{ /* nop */ } +#endif + +#ifndef WIN32 +void +daemonize_begin(bool detach); +#else +static inline void +daemonize_begin(bool detach) +{ (void)detach; } +#endif + +#ifndef WIN32 +void +daemonize_commit(); +#else +static inline void +daemonize_commit() {} +#endif + +#endif diff --git a/src/unix/PidFile.hxx b/src/unix/PidFile.hxx new file mode 100644 index 000000000..a242c7810 --- /dev/null +++ b/src/unix/PidFile.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PID_FILE_HXX +#define MPD_PID_FILE_HXX + +#include "fs/FileSystem.hxx" +#include "fs/AllocatedPath.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +class PidFile { + FILE *file; + +public: + PidFile(const AllocatedPath &path):file(nullptr) { + if (path.IsNull()) + return; + + file = FOpen(path, "w"); + if (file == nullptr) { + const std::string utf8 = path.ToUTF8(); + FormatFatalSystemError("Failed to create pid file \"%s\"", + path.c_str()); + } + } + + PidFile(const PidFile &) = delete; + + void Close() { + if (file == nullptr) + return; + + fclose(file); + } + + void Delete(const AllocatedPath &path) { + if (file == nullptr) { + assert(path.IsNull()); + return; + } + + assert(!path.IsNull()); + + fclose(file); + RemoveFile(path); + } + + void Write(pid_t pid) { + if (file == nullptr) + return; + + fprintf(file, "%lu\n", (unsigned long)pid); + fclose(file); + } + + void Write() { + if (file == nullptr) + return; + + Write(getpid()); + } +}; + +#endif diff --git a/src/unix/SignalHandlers.cxx b/src/unix/SignalHandlers.cxx new file mode 100644 index 000000000..4aef4fa71 --- /dev/null +++ b/src/unix/SignalHandlers.cxx @@ -0,0 +1,82 @@ +/* + * 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 "SignalHandlers.hxx" +#include "event/SignalMonitor.hxx" + +#ifndef WIN32 + +#include "Log.hxx" +#include "LogInit.hxx" +#include "event/Loop.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" + +#include <signal.h> + +static constexpr Domain signal_handlers_domain("signal_handlers"); + +static void +HandleShutdownSignal() +{ + SignalMonitorGetEventLoop().Break(); +} + +static void +x_sigaction(int signum, const struct sigaction *act) +{ + if (sigaction(signum, act, NULL) < 0) + FatalSystemError("sigaction() failed"); +} + +static void +handle_reload_event(void) +{ + LogDebug(signal_handlers_domain, "got SIGHUP, reopening log files"); + cycle_log_files(); +} + +#endif + +void +SignalHandlersInit(EventLoop &loop) +{ + SignalMonitorInit(loop); + +#ifndef WIN32 + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + x_sigaction(SIGPIPE, &sa); + + SignalMonitorRegister(SIGINT, HandleShutdownSignal); + SignalMonitorRegister(SIGTERM, HandleShutdownSignal); + + SignalMonitorRegister(SIGHUP, handle_reload_event); +#endif +} + +void +SignalHandlersFinish() +{ + SignalMonitorFinish(); +} diff --git a/src/unix/SignalHandlers.hxx b/src/unix/SignalHandlers.hxx new file mode 100644 index 000000000..551b373c1 --- /dev/null +++ b/src/unix/SignalHandlers.hxx @@ -0,0 +1,31 @@ +/* + * 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_SIGNAL_HANDLERS_HXX +#define MPD_SIGNAL_HANDLERS_HXX + +class EventLoop; + +void +SignalHandlersInit(EventLoop &loop); + +void +SignalHandlersFinish(); + +#endif diff --git a/src/util/Alloc.cxx b/src/util/Alloc.cxx new file mode 100644 index 000000000..ec3579470 --- /dev/null +++ b/src/util/Alloc.cxx @@ -0,0 +1,76 @@ +/* + * 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 "Alloc.hxx" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +gcc_noreturn +static void +oom() +{ + (void)write(STDERR_FILENO, "Out of memory\n", 14); + _exit(1); +} + +void * +xalloc(size_t size) +{ + void *p = malloc(size); + if (gcc_unlikely(p == nullptr)) + oom(); + + return p; +} + +void * +xmemdup(const void *s, size_t size) +{ + void *p = xalloc(size); + memcpy(p, s, size); + return p; +} + +char * +xstrdup(const char *s) +{ + char *p = strdup(s); + if (gcc_unlikely(p == nullptr)) + oom(); + + return p; +} + +char * +xstrndup(const char *s, size_t n) +{ +#ifdef WIN32 + char *p = (char *)xalloc(n + 1); + memcpy(p, s, n); + p[n] = 0; +#else + char *p = strndup(s, n); + if (gcc_unlikely(p == nullptr)) + oom(); +#endif + + return p; +} diff --git a/src/util/Alloc.hxx b/src/util/Alloc.hxx new file mode 100644 index 000000000..15c123b7a --- /dev/null +++ b/src/util/Alloc.hxx @@ -0,0 +1,67 @@ +/* + * 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_ALLOC_HXX +#define MPD_ALLOC_HXX + +#include "Compiler.h" + +#include <stddef.h> + +/** + * Allocate memory. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc +void * +xalloc(size_t size); + +/** + * Duplicate memory. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +void * +xmemdup(const void *s, size_t size); + +/** + * Duplicate a string. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +char * +xstrdup(const char *s); + +/** + * Duplicate a string. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +char * +xstrndup(const char *s, size_t n); + +#endif diff --git a/src/util/ByteReverse.cxx b/src/util/ByteReverse.cxx index 910c1e2a5..5cc8692a7 100644 --- a/src/util/ByteReverse.cxx +++ b/src/util/ByteReverse.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 diff --git a/src/util/ByteReverse.hxx b/src/util/ByteReverse.hxx index d6380213a..0c060c0cb 100644 --- a/src/util/ByteReverse.hxx +++ b/src/util/ByteReverse.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/util/Cast.hxx b/src/util/Cast.hxx new file mode 100644 index 000000000..887137da4 --- /dev/null +++ b/src/util/Cast.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2013-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CAST_HXX +#define CAST_HXX + +#include "Compiler.h" + +#include <stddef.h> + +/** + * Offset the given pointer by the specified number of bytes. + */ +static inline constexpr void * +OffsetPointer(void *p, ptrdiff_t offset) +{ + return (char *)p + offset; +} + +/** + * Offset the given pointer by the specified number of bytes. + */ +static inline constexpr const void * +OffsetPointer(const void *p, ptrdiff_t offset) +{ + return (const char *)p + offset; +} + +template<typename T, typename U> +static inline constexpr T * +OffsetCast(U *p, ptrdiff_t offset) +{ + return reinterpret_cast<T *>(OffsetPointer(p, offset)); +} + +template<typename T, typename U> +static inline constexpr T * +OffsetCast(const U *p, ptrdiff_t offset) +{ + return reinterpret_cast<const T *>(OffsetPointer(p, offset)); +} + +template<class C, class A> +static constexpr inline ptrdiff_t +ContainerAttributeOffset(const C *null_c, const A C::*p) +{ + return ptrdiff_t((const char *)null_c - (const char *)&(null_c->*p)); +} + +template<class C, class A> +static constexpr inline ptrdiff_t +ContainerAttributeOffset(const A C::*p) +{ + return ContainerAttributeOffset<C, A>(nullptr, p); +} + +/** + * Cast the given pointer to a struct member to its parent structure. + */ +template<class C, class A> +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +constexpr +#endif +static inline C & +ContainerCast(A &a, A C::*member) +{ + return *OffsetCast<C, A>(&a, ContainerAttributeOffset<C, A>(member)); +} + +/** + * Cast the given pointer to a struct member to its parent structure. + */ +template<class C, class A> +#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +constexpr +#endif +static inline const C & +ContainerCast(const A &a, A C::*member) +{ + return *OffsetCast<const C, const A>(&a, ContainerAttributeOffset<C, A>(member)); +} + +#endif diff --git a/src/util/CharUtil.hxx b/src/util/CharUtil.hxx index dd964f9c3..84a88a94e 100644 --- a/src/util/CharUtil.hxx +++ b/src/util/CharUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2013 Max Kellermann <max@duempel.org> + * Copyright (C) 2011-2014 Max Kellermann <max@duempel.org> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,75 +27,90 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CHAR_UTIL_HPP -#define CHAR_UTIL_HPP +#ifndef CHAR_UTIL_HXX +#define CHAR_UTIL_HXX constexpr static inline bool IsASCII(const unsigned char ch) { - return ch < 0x80; + return ch < 0x80; } constexpr static inline bool IsASCII(const char ch) { - return IsASCII((unsigned char)ch); + return IsASCII((unsigned char)ch); } +constexpr static inline bool IsWhitespaceOrNull(const char ch) { - return (unsigned char)ch <= 0x20; + return (unsigned char)ch <= 0x20; } +constexpr static inline bool IsWhitespaceNotNull(const char ch) { - return ch > 0 && ch <= 0x20; + return ch > 0 && ch <= 0x20; +} + +/** + * Is the given character whitespace? This calls the faster one of + * IsWhitespaceOrNull() or IsWhitespaceNotNull(). Use this if you + * want the fastest implementation, and you don't care if a null byte + * matches. + */ +constexpr +static inline bool +IsWhitespaceFast(const char ch) +{ + return IsWhitespaceOrNull(ch); } constexpr static inline bool IsPrintableASCII(char ch) { - return (signed char)ch >= 0x20; + return (signed char)ch >= 0x20; } constexpr static inline bool IsDigitASCII(char ch) { - return ch >= '0' && ch <= '9'; + return ch >= '0' && ch <= '9'; } constexpr static inline bool IsUpperAlphaASCII(char ch) { - return ch >= 'A' && ch <= 'Z'; + return ch >= 'A' && ch <= 'Z'; } constexpr static inline bool IsLowerAlphaASCII(char ch) { - return ch >= 'a' && ch <= 'z'; + return ch >= 'a' && ch <= 'z'; } constexpr static inline bool IsAlphaASCII(char ch) { - return IsUpperAlphaASCII(ch) || IsLowerAlphaASCII(ch); + return IsUpperAlphaASCII(ch) || IsLowerAlphaASCII(ch); } constexpr static inline bool IsAlphaNumericASCII(char ch) { - return IsAlphaASCII(ch) || IsDigitASCII(ch); + return IsAlphaASCII(ch) || IsDigitASCII(ch); } /** @@ -106,9 +121,22 @@ constexpr static inline char ToUpperASCII(char ch) { - return ch >= 'a' && ch <= 'z' - ? (ch - ('a' - 'A')) - : ch; + return ch >= 'a' && ch <= 'z' + ? (ch - ('a' - 'A')) + : ch; +} + +/** + * Convert the specified ASCII character (0x00..0x7f) to lower case. + * Unlike toupper(), it ignores the system locale. + */ +constexpr +static inline char +ToLowerASCII(char ch) +{ + return ch >= 'A' && ch <= 'Z' + ? (ch + ('a' - 'A')) + : ch; } #endif diff --git a/src/util/CircularBuffer.hxx b/src/util/CircularBuffer.hxx new file mode 100644 index 000000000..da6f412a5 --- /dev/null +++ b/src/util/CircularBuffer.hxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CIRCULAR_BUFFER_HPP +#define CIRCULAR_BUFFER_HPP + +#include "WritableBuffer.hxx" + +#include <assert.h> +#include <stddef.h> + +/** + * A circular buffer. + * + * This class does not manage buffer memory. It will not allocate or + * free any memory, it only manages the contents of an existing + * buffer given to the constructor. + * + * Everything between #head and #tail is valid data (may wrap around). + * If both are equal, then the buffer is empty. Due to this + * implementation detail, the buffer is empty when #size-1 items are + * stored; the last buffer cell cannot be used. + */ +template<typename T> +class CircularBuffer { +public: + typedef WritableBuffer<T> Range; + typedef typename Range::pointer_type pointer_type; + typedef typename Range::size_type size_type; + +protected: + /** + * The next index to be read. + */ + size_type head; + + /** + * The next index to be written to. + */ + size_type tail; + + const size_type capacity; + const pointer_type data; + +public: + constexpr CircularBuffer(pointer_type _data, size_type _capacity) + :head(0), tail(0), capacity(_capacity), data(_data) {} + + CircularBuffer(const CircularBuffer &other) = delete; + +protected: + constexpr size_type Next(size_type i) const { + return i + 1 == capacity + ? 0 + : i + 1; + } + +public: + void Clear() { + head = tail = 0; + } + + constexpr size_type GetCapacity() const { + return capacity; + } + + constexpr bool IsEmpty() const { + return head == tail; + } + + constexpr bool IsFull() const { + return Next(tail) == head; + } + + /** + * Returns the number of elements stored in this buffer. + */ + constexpr size_type GetSize() const { + return head <= tail + ? tail - head + : capacity - head + tail; + } + + /** + * Returns the number of elements that can be added to this + * buffer. + */ + constexpr size_type GetSpace() const { + /* space = capacity - size - 1 */ + return (head <= tail + ? capacity - tail + head + : head - tail) + - 1; + } + + /** + * Prepares writing. Returns a buffer range which may be written. + * When you are finished, call Append(). + */ + Range Write() { + assert(head < capacity); + assert(tail < capacity); + + size_type end = tail < head + ? head - 1 + /* the "head==0" is there so we don't write + the last cell, as this situation cannot be + represented by head/tail */ + : capacity - (head == 0); + + return Range(data + tail, end - tail); + } + + /** + * Expands the tail of the buffer, after data has been written + * to the buffer returned by Write(). + */ + void Append(size_type n) { + assert(head < capacity); + assert(tail < capacity); + assert(n < capacity); + assert(tail + n <= capacity); + assert(head <= tail || tail + n < head); + + tail += n; + + if (tail == capacity) { + assert(head > 0); + tail = 0; + } + } + + /** + * Return a buffer range which may be read. The buffer pointer is + * writable, to allow modifications while parsing. + */ + Range Read() { + assert(head < capacity); + assert(tail < capacity); + + return Range(data + head, (tail < head ? capacity : tail) - head); + } + + /** + * Marks a chunk as consumed. + */ + void Consume(size_type n) { + assert(head < capacity); + assert(tail < capacity); + assert(n < capacity); + assert(head + n <= capacity); + assert(tail < head || head + n <= tail); + + head += n; + if (head == capacity) + head = 0; + } +}; + +#endif diff --git a/src/util/Clamp.hxx b/src/util/Clamp.hxx new file mode 100644 index 000000000..3217ef9f7 --- /dev/null +++ b/src/util/Clamp.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLAMP_HPP +#define CLAMP_HPP + +#include "Compiler.h" + +/** + * Clamps the specified value in a range. Returns #min or #max if the + * value is outside. + */ +template<typename T> +static inline constexpr const T & +Clamp(const T &value, const T &min, const T &max) +{ + return gcc_unlikely(value < min) + ? min + : (gcc_unlikely(value > max) + ? max : value); +} + +#endif diff --git a/src/util/ConstBuffer.hxx b/src/util/ConstBuffer.hxx new file mode 100644 index 000000000..4d0a49e98 --- /dev/null +++ b/src/util/ConstBuffer.hxx @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2013-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONST_BUFFER_HPP +#define CONST_BUFFER_HPP + +#include "Compiler.h" + +#include <cstddef> + +#ifndef NDEBUG +#include <assert.h> +#endif + +template<typename T> +struct ConstBuffer; + +template<> +struct ConstBuffer<void> { + typedef size_t size_type; + typedef const void *pointer_type; + typedef pointer_type const_pointer_type; + typedef pointer_type iterator; + typedef pointer_type const_iterator; + + pointer_type data; + size_type size; + + ConstBuffer() = default; + + constexpr ConstBuffer(std::nullptr_t):data(nullptr), size(0) {} + + constexpr ConstBuffer(pointer_type _data, size_type _size) + :data(_data), size(_size) {} + + constexpr static ConstBuffer Null() { + return ConstBuffer(nullptr, 0); + } + + constexpr static ConstBuffer<void> FromVoid(ConstBuffer<void> other) { + return other; + } + + constexpr ConstBuffer<void> ToVoid() const { + return *this; + } + + constexpr bool IsNull() const { + return data == nullptr; + } + + constexpr bool IsEmpty() const { + return size == 0; + } +}; + +/** + * A reference to a memory area that is read-only. + */ +template<typename T> +struct ConstBuffer { + typedef size_t size_type; + typedef const T &reference_type; + typedef reference_type const_reference_type; + typedef const T *pointer_type; + typedef pointer_type const_pointer_type; + typedef pointer_type iterator; + typedef pointer_type const_iterator; + + pointer_type data; + size_type size; + + ConstBuffer() = default; + + constexpr ConstBuffer(std::nullptr_t):data(nullptr), size(0) {} + + constexpr ConstBuffer(pointer_type _data, size_type _size) + :data(_data), size(_size) {} + + constexpr static ConstBuffer Null() { + return ConstBuffer(nullptr, 0); + } + + /** + * Cast a ConstBuffer<void> to a ConstBuffer<T>. A "void" + * buffer records its size in bytes, and when casting to "T", + * the assertion below ensures that the size is a multiple of + * sizeof(T). + */ +#ifdef NDEBUG + constexpr +#endif + static ConstBuffer<T> FromVoid(ConstBuffer<void> other) { + static_assert(sizeof(T) > 0, "Empty base type"); +#ifndef NDEBUG + assert(other.size % sizeof(T) == 0); +#endif + return ConstBuffer<T>(pointer_type(other.data), + other.size / sizeof(T)); + } + + constexpr ConstBuffer<void> ToVoid() const { + static_assert(sizeof(T) > 0, "Empty base type"); + return ConstBuffer<void>(data, size * sizeof(T)); + } + + constexpr bool IsNull() const { + return data == nullptr; + } + + constexpr bool IsEmpty() const { + return size == 0; + } + + template<typename U> + gcc_pure + bool Contains(U &&u) const { + for (const auto &i : *this) + if (u == i) + return true; + + return false; + } + + constexpr iterator begin() const { + return data; + } + + constexpr iterator end() const { + return data + size; + } + + constexpr const_iterator cbegin() const { + return data; + } + + constexpr const_iterator cend() const { + return data + size; + } + +#ifdef NDEBUG + constexpr +#endif + reference_type operator[](size_type i) const { +#ifndef NDEBUG + assert(i < size); +#endif + + return data[i]; + } + + /** + * Returns a reference to the first element. Buffer must not + * be empty. + */ +#ifdef NDEBUG + constexpr +#endif + reference_type front() const { +#ifndef NDEBUG + assert(!IsEmpty()); +#endif + return data[0]; + } + + /** + * Returns a reference to the last element. Buffer must not + * be empty. + */ +#ifdef NDEBUG + constexpr +#endif + reference_type back() const { +#ifndef NDEBUG + assert(!IsEmpty()); +#endif + return data[size - 1]; + } + + /** + * Remove the first element (by moving the head pointer, does + * not actually modify the buffer). Buffer must not be empty. + */ + void pop_front() { +#ifndef NDEBUG + assert(!IsEmpty()); +#endif + + ++data; + --size; + } + + /** + * Remove the last element (by moving the tail pointer, does + * not actually modify the buffer). Buffer must not be empty. + */ + void pop_back() { +#ifndef NDEBUG + assert(!IsEmpty()); +#endif + + --size; + } + + /** + * Remove the first element and return a reference to it. + * Buffer must not be empty. + */ + reference_type shift() { + reference_type result = front(); + pop_front(); + return result; + } + + void skip_front(size_type n) { +#ifndef NDEBUG + assert(size >= n); +#endif + + data += n; + size -= n; + } +}; + +#endif diff --git a/src/util/Domain.hxx b/src/util/Domain.hxx index bbdbf8371..6dce7b731 100644 --- a/src/util/Domain.hxx +++ b/src/util/Domain.hxx @@ -1,24 +1,34 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_DOMAIN_HXX -#define MPD_DOMAIN_HXX +#ifndef DOMAIN_HXX +#define DOMAIN_HXX class Domain { const char *const name; diff --git a/src/util/DynamicFifoBuffer.hxx b/src/util/DynamicFifoBuffer.hxx new file mode 100644 index 000000000..36384bda6 --- /dev/null +++ b/src/util/DynamicFifoBuffer.hxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2013 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FIFO_BUFFER_HPP +#define FIFO_BUFFER_HPP + +#include "ForeignFifoBuffer.hxx" + +/** + * A first-in-first-out buffer: you can append data at the end, and + * read data from the beginning. This class automatically shifts the + * buffer as needed. It is not thread safe. + */ +template<typename T> +class DynamicFifoBuffer : protected ForeignFifoBuffer<T> { +public: + typedef typename ForeignFifoBuffer<T>::size_type size_type; + typedef typename ForeignFifoBuffer<T>::pointer_type pointer_type; + typedef typename ForeignFifoBuffer<T>::const_pointer_type const_pointer_type; + + explicit DynamicFifoBuffer(size_type _capacity) + :ForeignFifoBuffer<T>(new T[_capacity], _capacity) {} + ~DynamicFifoBuffer() { + delete[] GetBuffer(); + } + + DynamicFifoBuffer(const DynamicFifoBuffer &) = delete; + + using ForeignFifoBuffer<T>::GetCapacity; + using ForeignFifoBuffer<T>::Clear; + using ForeignFifoBuffer<T>::IsEmpty; + using ForeignFifoBuffer<T>::IsFull; + using ForeignFifoBuffer<T>::GetAvailable; + using ForeignFifoBuffer<T>::Read; + using ForeignFifoBuffer<T>::Consume; + using ForeignFifoBuffer<T>::Write; + using ForeignFifoBuffer<T>::Append; + + void Grow(size_type new_capacity) { + assert(new_capacity > GetCapacity()); + + T *old_data = GetBuffer(); + T *new_data = new T[new_capacity]; + ForeignFifoBuffer<T>::MoveBuffer(new_data, new_capacity); + delete[] old_data; + } + + void WantWrite(size_type n) { + if (ForeignFifoBuffer<T>::WantWrite(n)) + /* we already have enough space */ + return; + + const size_type in_use = GetAvailable(); + const size_type required_capacity = in_use + n; + size_type new_capacity = GetCapacity(); + do { + new_capacity <<= 1; + } while (new_capacity < required_capacity); + + Grow(new_capacity); + } + + /** + * Write data to the buffer, growing it as needed. Returns a + * writable pointer. + */ + pointer_type Write(size_type n) { + WantWrite(n); + return Write().data; + } + + /** + * Append data to the buffer, growing it as needed. + */ + void Append(const_pointer_type p, size_type n) { + std::copy_n(p, n, Write(n)); + Append(n); + } + +protected: + using ForeignFifoBuffer<T>::GetBuffer; +}; + +#endif diff --git a/src/util/Error.cxx b/src/util/Error.cxx index 5675f4d81..92b2cc5d0 100644 --- a/src/util/Error.cxx +++ b/src/util/Error.cxx @@ -1,31 +1,44 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "Error.hxx" #include "Domain.hxx" +#ifdef WIN32 #include <glib.h> +#endif #include <errno.h> #include <stdarg.h> #include <stdio.h> +#include <string.h> const Domain errno_domain("errno"); @@ -70,7 +83,7 @@ Error::FormatPrefix(const char *fmt, ...) void Error::SetErrno(int e) { - Set(errno_domain, e, g_strerror(e)); + Set(errno_domain, e, strerror(e)); } void @@ -82,7 +95,7 @@ Error::SetErrno() void Error::SetErrno(int e, const char *prefix) { - Format(errno_domain, e, "%s: %s", prefix, g_strerror(e)); + Format(errno_domain, e, "%s: %s", prefix, strerror(e)); } void @@ -120,11 +133,42 @@ Error::FormatErrno(const char *fmt, ...) #ifdef WIN32 void -Error::SetLastError(const char *prefix) +Error::SetLastError(DWORD _code, const char *prefix) { - DWORD _code = GetLastError(); const char *msg = g_win32_error_message(_code); Format(win32_domain, int(_code), "%s: %s", prefix, msg); } +void +Error::SetLastError(const char *prefix) +{ + SetLastError(GetLastError(), prefix); +} + +void +Error::FormatLastError(DWORD _code, const char *fmt, ...) +{ + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + SetLastError(_code, buffer); +} + +void +Error::FormatLastError(const char *fmt, ...) +{ + DWORD _code = GetLastError(); + + char buffer[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + SetLastError(_code, buffer); +} + #endif diff --git a/src/util/Error.hxx b/src/util/Error.hxx index ec8867c6c..ab66ae5cb 100644 --- a/src/util/Error.hxx +++ b/src/util/Error.hxx @@ -1,30 +1,40 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_ERROR_HXX -#define MPD_ERROR_HXX +#ifndef ERROR_HXX +#define ERROR_HXX #include "check.h" #include "Compiler.h" #include <string> -#include <algorithm> +#include <utility> #include <assert.h> @@ -141,17 +151,29 @@ public: message.insert(0, prefix); } + gcc_printf(2,3) void FormatPrefix(const char *fmt, ...); void SetErrno(int e); void SetErrno(); void SetErrno(int e, const char *prefix); void SetErrno(const char *prefix); + + gcc_printf(2,3) void FormatErrno(const char *prefix, ...); + + gcc_printf(3,4) void FormatErrno(int e, const char *prefix, ...); #ifdef WIN32 + void SetLastError(DWORD _code, const char *prefix); void SetLastError(const char *prefix); + + gcc_printf(3,4) + void FormatLastError(DWORD code, const char *fmt, ...); + + gcc_printf(2,3) + void FormatLastError(const char *fmt, ...); #endif }; diff --git a/src/util/FifoBuffer.hxx b/src/util/FifoBuffer.hxx deleted file mode 100644 index 75d2d2ef2..000000000 --- a/src/util/FifoBuffer.hxx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2003-2010 Max Kellermann <max@duempel.org> - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef FIFO_BUFFER_HPP -#define FIFO_BUFFER_HPP - -#include "WritableBuffer.hxx" - -#include <utility> -#include <algorithm> - -#include <assert.h> -#include <stddef.h> - -/** - * A first-in-first-out buffer: you can append data at the end, and - * read data from the beginning. This class automatically shifts the - * buffer as needed. It is not thread safe. - */ -template<class T, size_t size> -class FifoBuffer { -public: - typedef size_t size_type; - -public: - typedef WritableBuffer<T> Range; - -protected: - size_type head, tail; - T data[size]; - -public: - constexpr - FifoBuffer():head(0), tail(0) {} - -protected: - void Shift() { - if (head == 0) - return; - - assert(head <= size); - assert(tail <= size); - assert(tail >= head); - - std::move(data + head, data + tail, data); - - tail -= head; - head = 0; - } - -public: - void Clear() { - head = tail = 0; - } - - bool IsEmpty() const { - return head == tail; - } - - bool IsFull() const { - return head == 0 && tail == size; - } - - /** - * Prepares writing. Returns a buffer range which may be written. - * When you are finished, call append(). - */ - Range Write() { - Shift(); - return Range(data + tail, size - tail); - } - - /** - * Expands the tail of the buffer, after data has been written to - * the buffer returned by write(). - */ - void Append(size_type n) { - assert(tail <= size); - assert(n <= size); - assert(tail + n <= size); - - tail += n; - } - - /** - * Return a buffer range which may be read. The buffer pointer is - * writable, to allow modifications while parsing. - */ - Range Read() { - return Range(data + head, tail - head); - } - - /** - * Marks a chunk as consumed. - */ - void Consume(size_type n) { - assert(tail <= size); - assert(head <= tail); - assert(n <= tail); - assert(head + n <= tail); - - head += n; - } -}; - -#endif diff --git a/src/util/ForeignFifoBuffer.hxx b/src/util/ForeignFifoBuffer.hxx new file mode 100644 index 000000000..b829fb030 --- /dev/null +++ b/src/util/ForeignFifoBuffer.hxx @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2003-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FOREIGN_FIFO_BUFFER_HXX +#define FOREIGN_FIFO_BUFFER_HXX + +#include "WritableBuffer.hxx" + +#include <utility> +#include <algorithm> + +#include <cstddef> + +#include <assert.h> + +/** + * A first-in-first-out buffer: you can append data at the end, and + * read data from the beginning. This class automatically shifts the + * buffer as needed. It is not thread safe. + * + * This class does not manage buffer memory. It will not allocate or + * free any memory, it only manages the contents of an existing buffer + * given to the constructor. + */ +template<typename T> +class ForeignFifoBuffer { +public: + typedef size_t size_type; + typedef WritableBuffer<T> Range; + typedef typename Range::pointer_type pointer_type; + typedef typename Range::const_pointer_type const_pointer_type; + +protected: + size_type head, tail, capacity; + T *data; + +public: + explicit constexpr ForeignFifoBuffer(std::nullptr_t n) + :head(0), tail(0), capacity(0), data(n) {} + + constexpr ForeignFifoBuffer(T *_data, size_type _capacity) + :head(0), tail(0), capacity(_capacity), data(_data) {} + + ForeignFifoBuffer(ForeignFifoBuffer &&src) + :head(src.head), tail(src.tail), + capacity(src.capacity), data(src.data) { + src.SetNull(); + } + + ForeignFifoBuffer &operator=(ForeignFifoBuffer &&src) { + head = src.head; + tail = src.tail; + capacity = src.capacity; + data = src.data; + src.SetNull(); + return *this; + } + + void Swap(ForeignFifoBuffer<T> &other) { + std::swap(head, other.head); + std::swap(tail, other.tail); + std::swap(capacity, other.capacity); + std::swap(data, other.data); + } + + constexpr bool IsNull() const { + return data == nullptr; + } + + constexpr bool IsDefined() const { + return !IsNull(); + } + + T *GetBuffer() { + return data; + } + + constexpr size_type GetCapacity() const { + return capacity; + } + + void SetNull() { + head = tail = 0; + capacity = 0; + data = nullptr; + } + + void SetBuffer(T *_data, size_type _capacity) { + assert(_data != nullptr); + assert(_capacity > 0); + + head = tail = 0; + capacity = _capacity; + data = _data; + } + + void MoveBuffer(T *new_data, size_type new_capacity) { + assert(new_capacity >= tail - head); + + std::move(data + head, data + tail, new_data); + data = new_data; + capacity = new_capacity; + tail -= head; + head = 0; + } + + void Clear() { + head = tail = 0; + } + + constexpr bool IsEmpty() const { + return head == tail; + } + + constexpr bool IsFull() const { + return head == 0 && tail == capacity; + } + + /** + * Prepares writing. Returns a buffer range which may be written. + * When you are finished, call append(). + */ + Range Write() { + if (IsEmpty()) + Clear(); + else if (tail == capacity) + Shift(); + + return Range(data + tail, capacity - tail); + } + + bool WantWrite(size_type n) { + if (tail + n <= capacity) + /* enough space after the tail */ + return true; + + const size_type in_use = tail - head; + const size_type required_capacity = in_use + n; + if (required_capacity > capacity) + return false; + + Shift(); + assert(tail + n <= capacity); + return true; + } + + /** + * Expands the tail of the buffer, after data has been written to + * the buffer returned by write(). + */ + void Append(size_type n) { + assert(tail <= capacity); + assert(n <= capacity); + assert(tail + n <= capacity); + + tail += n; + } + + constexpr size_type GetAvailable() const { + return tail - head; + } + + /** + * Return a buffer range which may be read. The buffer pointer is + * writable, to allow modifications while parsing. + */ + constexpr Range Read() const { + return Range(data + head, tail - head); + } + + /** + * Marks a chunk as consumed. + */ + void Consume(size_type n) { + assert(tail <= capacity); + assert(head <= tail); + assert(n <= tail); + assert(head + n <= tail); + + head += n; + } + + size_type Read(pointer_type p, size_type n) { + auto range = Read(); + if (n > range.size) + n = range.size; + std::copy_n(range.data, n, p); + Consume(n); + return n; + } + + /** + * Move as much data as possible from the specified buffer. + * + * @return the number of items moved + */ + size_type MoveFrom(ForeignFifoBuffer<T> &src) { + auto r = src.Read(); + auto w = Write(); + size_t n = std::min(r.size, w.size); + + std::move(r.data, r.data + n, w.data); + Append(n); + src.Consume(n); + return n; + } + +protected: + void Shift() { + if (head == 0) + return; + + assert(head <= capacity); + assert(tail <= capacity); + assert(tail >= head); + + std::move(data + head, data + tail, data); + + tail -= head; + head = 0; + } +}; + +#endif diff --git a/src/util/FormatString.cxx b/src/util/FormatString.cxx index c13d0fb52..d222a505c 100644 --- a/src/util/FormatString.cxx +++ b/src/util/FormatString.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,10 +19,13 @@ #include "FormatString.hxx" -#include <string.h> #include <stdio.h> #include <stdlib.h> +#ifdef WIN32 +#include <string.h> +#endif + char * FormatNewV(const char *fmt, va_list args) { diff --git a/src/util/FormatString.hxx b/src/util/FormatString.hxx index bb4263107..dc1ac3c67 100644 --- a/src/util/FormatString.hxx +++ b/src/util/FormatString.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/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx index d1c55c965..1da049f2f 100644 --- a/src/util/HugeAllocator.cxx +++ b/src/util/HugeAllocator.cxx @@ -1,20 +1,30 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "HugeAllocator.hxx" diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx index f44a6e3b8..e85f936dc 100644 --- a/src/util/HugeAllocator.hxx +++ b/src/util/HugeAllocator.hxx @@ -1,24 +1,34 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_HUGE_ALLOCATOR_HXX -#define MPD_HUGE_ALLOCATOR_HXX +#ifndef HUGE_ALLOCATOR_HXX +#define HUGE_ALLOCATOR_HXX #include "Compiler.h" @@ -53,6 +63,28 @@ HugeFree(void *p, size_t size); void HugeDiscard(void *p, size_t size); +#elif defined(WIN32) +#include <windows.h> + +gcc_malloc +static inline void * +HugeAllocate(size_t size) +{ + return VirtualAlloc(nullptr, size, MEM_LARGE_PAGES, PAGE_READWRITE); +} + +static inline void +HugeFree(void *p, gcc_unused size_t size) +{ + VirtualFree(p, 0, MEM_RELEASE); +} + +static inline void +HugeDiscard(void *p, size_t size) +{ + VirtualAlloc(p, size, MEM_RESET, PAGE_NOACCESS); +} + #else /* not Linux: fall back to standard C calls */ diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx index 0f90ebb2e..b0aac913c 100644 --- a/src/util/LazyRandomEngine.cxx +++ b/src/util/LazyRandomEngine.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 diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx index bfe4bb60c..4156b3bb1 100644 --- a/src/util/LazyRandomEngine.hxx +++ b/src/util/LazyRandomEngine.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/util/OptionDef.hxx b/src/util/OptionDef.hxx new file mode 100644 index 000000000..dd82154c4 --- /dev/null +++ b/src/util/OptionDef.hxx @@ -0,0 +1,63 @@ +/* + * 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_UTIL_OPTIONDEF_HXX +#define MPD_UTIL_OPTIONDEF_HXX + +/** + * Command line option definition. + */ +class OptionDef +{ + const char *long_option; + char short_option; + const char *desc; +public: + constexpr OptionDef(const char *_long_option, const char *_desc) + : long_option(_long_option), + short_option(0), + desc(_desc) { } + + constexpr OptionDef(const char *_long_option, + char _short_option, const char *_desc) + : long_option(_long_option), + short_option(_short_option), + desc(_desc) { } + + bool HasLongOption() const { return long_option != nullptr; } + bool HasShortOption() const { return short_option != 0; } + bool HasDescription() const { return desc != nullptr; } + + const char *GetLongOption() const { + assert(HasLongOption()); + return long_option; + } + + char GetShortOption() const { + assert(HasShortOption()); + return short_option; + } + + const char *GetDescription() const { + assert(HasDescription()); + return desc; + } +}; + +#endif diff --git a/src/util/OptionParser.cxx b/src/util/OptionParser.cxx new file mode 100644 index 000000000..b10008527 --- /dev/null +++ b/src/util/OptionParser.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "OptionParser.hxx" +#include "OptionDef.hxx" + +#include <string.h> + +bool OptionParser::CheckOption(const OptionDef &opt) +{ + assert(option != nullptr); + + if (is_long) + return opt.HasLongOption() && + strcmp(option, opt.GetLongOption()) == 0; + + return opt.HasShortOption() && + option[0] == opt.GetShortOption() && + option[1] == '\0'; +} + +bool OptionParser::ParseNext() +{ + assert(HasEntries()); + char *arg = *argv; + ++argv; + --argc; + if (arg[0] == '-') { + if (arg[1] == '-') { + option = arg + 2; + is_long = true; + } + else { + option = arg + 1; + is_long = false; + } + option_raw = arg; + return true; + } + option = nullptr; + option_raw = nullptr; + return false; +} diff --git a/src/util/OptionParser.hxx b/src/util/OptionParser.hxx new file mode 100644 index 000000000..b9d34adbb --- /dev/null +++ b/src/util/OptionParser.hxx @@ -0,0 +1,88 @@ +/* + * 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_UTIL_OPTIONPARSER_HXX +#define MPD_UTIL_OPTIONPARSER_HXX + +#include <assert.h> + +class OptionDef; + +/** + * Command line option parser. + */ +class OptionParser +{ + int argc; + char **argv; + char *option; + char *option_raw; + bool is_long; +public: + /** + * Constructs #OptionParser. + */ + OptionParser(int _argc, char **_argv) + : argc(_argc - 1), argv(_argv + 1), + option(nullptr), option_raw(nullptr), is_long(false) { } + + /** + * Checks if there are command line entries to process. + */ + bool HasEntries() const { return argc > 0; } + + /** + * Gets the last parsed option. + */ + char *GetOption() { + assert(option_raw != nullptr); + return option_raw; + } + + /** + * Checks if current option is a specified option. + */ + bool CheckOption(const OptionDef& opt); + + /** + * Checks if current option is a specified option + * or specified alternative option. + */ + bool CheckOption(const OptionDef& opt, const OptionDef &alt_opt) { + return CheckOption(opt) || CheckOption(alt_opt); + } + + /** + * Parses current command line entry. + * Returns true on success, false otherwise. + * Regardless of result, advances current position to the next + * command line entry. + */ + bool ParseNext(); + + /** + * Checks if specified string is a command line option. + */ + static bool IsOption(const char *s) { + assert(s != nullptr); + return s[0] == '-'; + } +}; + +#endif diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx index d9b193dd1..e4624bbec 100644 --- a/src/util/PeakBuffer.cxx +++ b/src/util/PeakBuffer.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 @@ -18,46 +18,39 @@ */ #include "PeakBuffer.hxx" -#include "HugeAllocator.hxx" -#include "fifo_buffer.h" +#include "DynamicFifoBuffer.hxx" #include <algorithm> #include <assert.h> -#include <stdint.h> #include <string.h> PeakBuffer::~PeakBuffer() { - if (normal_buffer != nullptr) - fifo_buffer_free(normal_buffer); - - if (peak_buffer != nullptr) - HugeFree(peak_buffer, peak_size); + delete normal_buffer; + delete peak_buffer; } bool PeakBuffer::IsEmpty() const { - return (normal_buffer == nullptr || - fifo_buffer_is_empty(normal_buffer)) && - (peak_buffer == nullptr || - fifo_buffer_is_empty(peak_buffer)); + return (normal_buffer == nullptr || normal_buffer->IsEmpty()) && + (peak_buffer == nullptr || peak_buffer->IsEmpty()); } -const void * -PeakBuffer::Read(size_t *length_r) const +WritableBuffer<void> +PeakBuffer::Read() const { if (normal_buffer != nullptr) { - const void *p = fifo_buffer_read(normal_buffer, length_r); - if (p != nullptr) - return p; + const auto p = normal_buffer->Read(); + if (!p.IsEmpty()) + return p.ToVoid(); } if (peak_buffer != nullptr) { - const void *p = fifo_buffer_read(peak_buffer, length_r); - if (p != nullptr) - return p; + const auto p = peak_buffer->Read(); + if (!p.IsEmpty()) + return p.ToVoid(); } return nullptr; @@ -66,15 +59,15 @@ PeakBuffer::Read(size_t *length_r) const void PeakBuffer::Consume(size_t length) { - if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) { - fifo_buffer_consume(normal_buffer, length); + if (normal_buffer != nullptr && !normal_buffer->IsEmpty()) { + normal_buffer->Consume(length); return; } - if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { - fifo_buffer_consume(peak_buffer, length); - if (fifo_buffer_is_empty(peak_buffer)) { - HugeFree(peak_buffer, peak_size); + if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) { + peak_buffer->Consume(length); + if (peak_buffer->IsEmpty()) { + delete peak_buffer; peak_buffer = nullptr; } @@ -83,7 +76,7 @@ PeakBuffer::Consume(size_t length) } static size_t -AppendTo(fifo_buffer *buffer, const void *data, size_t length) +AppendTo(DynamicFifoBuffer<uint8_t> &buffer, const void *data, size_t length) { assert(data != nullptr); assert(length > 0); @@ -91,14 +84,13 @@ AppendTo(fifo_buffer *buffer, const void *data, size_t length) size_t total = 0; do { - size_t max_length; - void *p = fifo_buffer_write(buffer, &max_length); - if (p == nullptr) + const auto p = buffer.Write(); + if (p.IsEmpty()) break; - const size_t nbytes = std::min(length, max_length); - memcpy(p, data, nbytes); - fifo_buffer_append(buffer, nbytes); + const size_t nbytes = std::min(length, p.size); + memcpy(p.data, data, nbytes); + buffer.Append(nbytes); data = (const uint8_t *)data + nbytes; length -= nbytes; @@ -114,15 +106,15 @@ PeakBuffer::Append(const void *data, size_t length) if (length == 0) return true; - if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) { - size_t nbytes = AppendTo(peak_buffer, data, length); + if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) { + size_t nbytes = AppendTo(*peak_buffer, data, length); return nbytes == length; } if (normal_buffer == nullptr) - normal_buffer = fifo_buffer_new(normal_size); + normal_buffer = new DynamicFifoBuffer<uint8_t>(normal_size); - size_t nbytes = AppendTo(normal_buffer, data, length); + size_t nbytes = AppendTo(*normal_buffer, data, length); if (nbytes > 0) { data = (const uint8_t *)data + nbytes; length -= nbytes; @@ -132,13 +124,11 @@ PeakBuffer::Append(const void *data, size_t length) if (peak_buffer == nullptr) { if (peak_size > 0) - peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); + peak_buffer = new DynamicFifoBuffer<uint8_t>(peak_size); if (peak_buffer == nullptr) return false; - - fifo_buffer_init(peak_buffer, peak_size); } - nbytes = AppendTo(peak_buffer, data, length); + nbytes = AppendTo(*peak_buffer, data, length); return nbytes == length; } diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx index a3f385e3e..702a3dee0 100644 --- a/src/util/PeakBuffer.hxx +++ b/src/util/PeakBuffer.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 @@ -20,11 +20,14 @@ #ifndef MPD_PEAK_BUFFER_HXX #define MPD_PEAK_BUFFER_HXX +#include "WritableBuffer.hxx" #include "Compiler.h" #include <stddef.h> +#include <stdint.h> -struct fifo_buffer; +template<typename T> struct WritableBuffer; +template<typename T> class DynamicFifoBuffer; /** * A FIFO-like buffer that will allocate more memory on demand to @@ -34,7 +37,7 @@ struct fifo_buffer; class PeakBuffer { size_t normal_size, peak_size; - fifo_buffer *normal_buffer, *peak_buffer; + DynamicFifoBuffer<uint8_t> *normal_buffer, *peak_buffer; public: PeakBuffer(size_t _normal_size, size_t _peak_size) @@ -57,7 +60,9 @@ public: gcc_pure bool IsEmpty() const; - const void *Read(size_t *length_r) const; + gcc_pure + WritableBuffer<void> Read() const; + void Consume(size_t length); bool Append(const void *data, size_t length); diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx index dff850036..02ef8818c 100644 --- a/src/util/RefCount.hxx +++ b/src/util/RefCount.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 * * Redistribution and use in source and binary forms, with or without diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx index 6cde75f34..63ca087ae 100644 --- a/src/util/SliceBuffer.hxx +++ b/src/util/SliceBuffer.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/util/SplitString.cxx b/src/util/SplitString.cxx new file mode 100644 index 000000000..75e799279 --- /dev/null +++ b/src/util/SplitString.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "SplitString.hxx" + +#include <string.h> + +SplitString::SplitString(const char *s, char separator) + :first(nullptr) +{ + const char *x = strchr(s, separator); + if (x == nullptr) + return; + + size_t length = x - s; + second = x + 1; + + first = new char[length + 1]; + memcpy(first, s, length); + first[length] = 0; +} diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx new file mode 100644 index 000000000..96ffb21ec --- /dev/null +++ b/src/util/SplitString.hxx @@ -0,0 +1,71 @@ +/* + * 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_SPLIT_STRING_HXX +#define MPD_SPLIT_STRING_HXX + +#include "Compiler.h" + +#include <assert.h> + +/** + * Split a given constant string at a separator character. Duplicates + * the first part to be able to null-terminate it. + */ +class SplitString { + char *first; + const char *second; + +public: + SplitString(const char *s, char separator); + + ~SplitString() { + delete[] first; + } + + /** + * Was the separator found? + */ + bool IsDefined() const { + return first != nullptr; + } + + /** + * Is the first part empty? + */ + bool IsEmpty() const { + assert(IsDefined()); + + return *first == 0; + } + + const char *GetFirst() const { + assert(IsDefined()); + + return first; + } + + const char *GetSecond() const { + assert(IsDefined()); + + return second; + } +}; + +#endif diff --git a/src/util/StaticFifoBuffer.hxx b/src/util/StaticFifoBuffer.hxx new file mode 100644 index 000000000..c1b64bb1e --- /dev/null +++ b/src/util/StaticFifoBuffer.hxx @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STATIC_FIFO_BUFFER_HPP +#define STATIC_FIFO_BUFFER_HPP + +#include "WritableBuffer.hxx" + +#include <utility> +#include <algorithm> + +#include <assert.h> +#include <stddef.h> + +/** + * A first-in-first-out buffer: you can append data at the end, and + * read data from the beginning. This class automatically shifts the + * buffer as needed. It is not thread safe. + */ +template<class T, size_t size> +class StaticFifoBuffer { +public: + typedef size_t size_type; + +public: + typedef WritableBuffer<T> Range; + +protected: + size_type head, tail; + T data[size]; + +public: + constexpr + StaticFifoBuffer():head(0), tail(0) {} + +protected: + void Shift() { + if (head == 0) + return; + + assert(head <= size); + assert(tail <= size); + assert(tail >= head); + + std::move(data + head, data + tail, data); + + tail -= head; + head = 0; + } + +public: + void Clear() { + head = tail = 0; + } + + bool IsEmpty() const { + return head == tail; + } + + bool IsFull() const { + return head == 0 && tail == size; + } + + /** + * Prepares writing. Returns a buffer range which may be written. + * When you are finished, call append(). + */ + Range Write() { + if (IsEmpty()) + Clear(); + else if (tail == size) + Shift(); + + return Range(data + tail, size - tail); + } + + /** + * Expands the tail of the buffer, after data has been written to + * the buffer returned by write(). + */ + void Append(size_type n) { + assert(tail <= size); + assert(n <= size); + assert(tail + n <= size); + + tail += n; + } + + /** + * Return a buffer range which may be read. The buffer pointer is + * writable, to allow modifications while parsing. + */ + Range Read() { + return Range(data + head, tail - head); + } + + /** + * Marks a chunk as consumed. + */ + void Consume(size_type n) { + assert(tail <= size); + assert(head <= tail); + assert(n <= tail); + assert(head + n <= tail); + + head += n; + } +}; + +#endif diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx index 7e295bf90..bcade2b3b 100644 --- a/src/util/StringUtil.cxx +++ b/src/util/StringUtil.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 @@ -21,10 +21,13 @@ #include "CharUtil.hxx" #include "ASCII.hxx" +#include <algorithm> + #include <assert.h> +#include <string.h> const char * -strchug_fast(const char *p) +StripLeft(const char *p) { while (IsWhitespaceNotNull(*p)) ++p; @@ -32,6 +35,79 @@ strchug_fast(const char *p) return p; } +const char * +StripLeft(const char *p, const char *end) +{ + while (p < end && IsWhitespaceOrNull(*p)) + ++p; + + return p; +} + +const char * +StripRight(const char *p, const char *end) +{ + while (end > p && IsWhitespaceOrNull(end[-1])) + --end; + + return end; +} + +size_t +StripRight(const char *p, size_t length) +{ + while (length > 0 && IsWhitespaceOrNull(p[length - 1])) + --length; + + return length; +} + +void +StripRight(char *p) +{ + size_t old_length = strlen(p); + size_t new_length = StripRight(p, old_length); + p[new_length] = 0; +} + +char * +Strip(char *p) +{ + p = StripLeft(p); + StripRight(p); + return p; +} + +bool +StringStartsWith(const char *haystack, const char *needle) +{ + const size_t length = strlen(needle); + return memcmp(haystack, needle, length) == 0; +} + +bool +StringEndsWith(const char *haystack, const char *needle) +{ + const size_t haystack_length = strlen(haystack); + const size_t needle_length = strlen(needle); + + return haystack_length >= needle_length && + memcmp(haystack + haystack_length - needle_length, + needle, needle_length) == 0; +} + +char * +CopyString(char *gcc_restrict dest, const char *gcc_restrict src, size_t size) +{ + size_t length = strlen(src); + if (length >= size) + length = size - 1; + + char *p = std::copy(src, src + length, dest); + *p = '\0'; + return p; +} + bool string_array_contains(const char *const* haystack, const char *needle) { diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx index 1c67910a9..9beda5441 100644 --- a/src/util/StringUtil.hxx +++ b/src/util/StringUtil.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 @@ -22,24 +22,86 @@ #include "Compiler.h" +#include <stddef.h> + /** * Returns a pointer to the first non-whitespace character in the * string, or to the end of the string. - * - * This is a faster version of g_strchug(), because it does not move - * data. */ gcc_pure const char * -strchug_fast(const char *p); +StripLeft(const char *p); gcc_pure static inline char * -strchug_fast(char *p) +StripLeft(char *p) { - return const_cast<char *>(strchug_fast((const char *)p)); + return const_cast<char *>(StripLeft((const char *)p)); } +gcc_pure +const char * +StripLeft(const char *p, const char *end); + +/** + * Determine the string's end as if it was stripped on the right side. + */ +gcc_pure +const char * +StripRight(const char *p, const char *end); + +/** + * Determine the string's end as if it was stripped on the right side. + */ +gcc_pure +static inline char * +StripRight(char *p, char *end) +{ + return const_cast<char *>(StripRight((const char *)p, + (const char *)end)); +} + +/** + * Determine the string's length as if it was stripped on the right + * side. + */ +gcc_pure +size_t +StripRight(const char *p, size_t length); + +/** + * Strip trailing whitespace by null-terminating the string. + */ +void +StripRight(char *p); + +/** + * Skip whitespace at the beginning and terminate the string after the + * last non-whitespace character. + */ +char * +Strip(char *p); + +gcc_pure +bool +StringStartsWith(const char *haystack, const char *needle); + +gcc_pure +bool +StringEndsWith(const char *haystack, const char *needle); + +/** + * Copy a string. If the buffer is too small, then the string is + * truncated. This is a safer version of strncpy(). + * + * @param size the size of the destination buffer (including the null + * terminator) + * @return a pointer to the null terminator + */ +gcc_nonnull_all +char * +CopyString(char *dest, const char *src, size_t size); + /** * Checks whether a string array contains the specified string. * diff --git a/src/util/TextFile.hxx b/src/util/TextFile.hxx new file mode 100644 index 000000000..3d7d2d0df --- /dev/null +++ b/src/util/TextFile.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_FILE_HXX +#define TEXT_FILE_HXX + +#include <string.h> + +template<typename B> +char * +ReadBufferedLine(B &buffer) +{ + auto r = buffer.Read(); + char *newline = reinterpret_cast<char*>(memchr(r.data, '\n', r.size)); + if (newline == nullptr) + return nullptr; + + buffer.Consume(newline + 1 - r.data); + + if (newline > r.data && newline[-1] == '\r') + --newline; + *newline = 0; + return r.data; +} + +#endif diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx index 1c8af23fd..19322b70d 100644 --- a/src/util/Tokenizer.cxx +++ b/src/util/Tokenizer.cxx @@ -1,20 +1,30 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2009-2014 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" @@ -24,11 +34,6 @@ #include "Error.hxx" #include "Domain.hxx" -#include <glib.h> - -#include <assert.h> -#include <string.h> - static constexpr Domain tokenizer_domain("tokenizer"); static inline bool @@ -62,11 +67,11 @@ Tokenizer::NextWord(Error &error) whitespace or end-of-string */ while (*++input != 0) { - if (IsWhitespaceOrNull(*input)) { + if (IsWhitespaceFast(*input)) { /* a whitespace: the word ends here */ *input = 0; /* skip all following spaces, too */ - input = strchug_fast(input + 1); + input = StripLeft(input + 1); break; } @@ -107,11 +112,11 @@ Tokenizer::NextUnquoted(Error &error) whitespace or end-of-string */ while (*++input != 0) { - if (IsWhitespaceOrNull(*input)) { + if (IsWhitespaceFast(*input)) { /* a whitespace: the word ends here */ *input = 0; /* skip all following spaces, too */ - input = strchug_fast(input + 1); + input = StripLeft(input + 1); break; } @@ -171,7 +176,7 @@ Tokenizer::NextString(Error &error) line) */ ++input; - if (!IsWhitespaceOrNull(*input)) { + if (!IsWhitespaceFast(*input)) { error.Set(tokenizer_domain, "Space expected after closing '\"'"); return nullptr; @@ -180,7 +185,7 @@ Tokenizer::NextString(Error &error) /* finish the string and return it */ *dest = 0; - input = strchug_fast(input); + input = StripLeft(input); return word; } diff --git a/src/util/Tokenizer.hxx b/src/util/Tokenizer.hxx index a689dc31d..dc2646589 100644 --- a/src/util/Tokenizer.hxx +++ b/src/util/Tokenizer.hxx @@ -1,24 +1,34 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2009-2014 Max Kellermann <max@duempel.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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * 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. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef MPD_TOKENIZER_HXX -#define MPD_TOKENIZER_HXX +#ifndef TOKENIZER_HXX +#define TOKENIZER_HXX class Error; diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx index 2609db2cf..a549f7938 100644 --- a/src/util/UriUtil.cxx +++ b/src/util/UriUtil.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 @@ -27,6 +27,16 @@ bool uri_has_scheme(const char *uri) return strstr(uri, "://") != nullptr; } +std::string +uri_get_scheme(const char *uri) +{ + const char *end = strstr(uri, "://"); + if (end == nullptr) + end = uri; + + return std::string(uri, end); +} + /* suffixes should be ascii only characters */ const char * uri_get_suffix(const char *uri) @@ -88,6 +98,8 @@ uri_remove_auth(const char *uri) auth = uri + 7; else if (memcmp(uri, "https://", 8) == 0) auth = uri + 8; + else if (memcmp(uri, "ftp://", 6) == 0) + auth = uri + 6; else /* unrecognized URI */ return std::string(); diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx index 78d0a6bff..8e00f8cd8 100644 --- a/src/util/UriUtil.hxx +++ b/src/util/UriUtil.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 @@ -31,6 +31,13 @@ gcc_pure bool uri_has_scheme(const char *uri); +/** + * Returns the scheme name of the specified URI, or an empty string. + */ +gcc_pure +std::string +uri_get_scheme(const char *uri); + gcc_pure const char * uri_get_suffix(const char *uri); diff --git a/src/util/VarSize.hxx b/src/util/VarSize.hxx new file mode 100644 index 000000000..04f1bf580 --- /dev/null +++ b/src/util/VarSize.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008-2014 Max Kellermann <max@duempel.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MPD_VAR_SIZE_HXX +#define MPD_VAR_SIZE_HXX + +#include "Alloc.hxx" +#include "Compiler.h" + +#include <type_traits> +#include <utility> +#include <new> + +/** + * Allocate and construct a variable-size object. That is useful for + * example when you want to store a variable-length string as the last + * attribute without the overhead of a second allocation. + * + * @param T a struct/class with a variable-size last attribute + * @param declared_tail_size the declared size of the last element in + * #T + * @param real_tail_size the real required size of the last element in + * #T + */ +template<class T, typename... Args> +gcc_malloc +T * +NewVarSize(size_t declared_tail_size, size_t real_tail_size, Args&&... args) +{ + static_assert(std::is_standard_layout<T>::value, + "Not standard-layout"); + + /* determine the total size of this instance */ + size_t size = sizeof(T) - declared_tail_size + real_tail_size; + + /* allocate memory */ + T *instance = (T *)xalloc(size); + + /* call the constructor */ + new(instance) T(std::forward<Args>(args)...); + + return instance; +} + +template<typename T> +gcc_nonnull_all +void +DeleteVarSize(T *instance) +{ + /* call the destructor */ + instance->T::~T(); + + /* free memory */ + free(instance); +} + +#endif diff --git a/src/util/WritableBuffer.hxx b/src/util/WritableBuffer.hxx index 4e529cfad..f9e6d4a96 100644 --- a/src/util/WritableBuffer.hxx +++ b/src/util/WritableBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Max Kellermann <max@duempel.org> + * Copyright (C) 2013-2014 Max Kellermann <max@duempel.org> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -32,7 +32,45 @@ #include "Compiler.h" -#include <stddef.h> +#include <cstddef> + +#ifndef NDEBUG +#include <assert.h> +#endif + +template<typename T> +struct WritableBuffer; + +template<> +struct WritableBuffer<void> { + typedef size_t size_type; + typedef void *pointer_type; + typedef const void *const_pointer_type; + typedef pointer_type iterator; + typedef const_pointer_type const_iterator; + + pointer_type data; + size_type size; + + WritableBuffer() = default; + + constexpr WritableBuffer(std::nullptr_t):data(nullptr), size(0) {} + + constexpr WritableBuffer(pointer_type _data, size_type _size) + :data(_data), size(_size) {} + + constexpr static WritableBuffer Null() { + return { nullptr, 0 }; + } + + constexpr bool IsNull() const { + return data == nullptr; + } + + constexpr bool IsEmpty() const { + return size == 0; + } +}; /** * A reference to a memory area that is writable. @@ -41,47 +79,144 @@ */ template<typename T> struct WritableBuffer { - typedef size_t size_type; - typedef T *pointer_type; - typedef const T *const_pointer_type; - typedef pointer_type iterator; - typedef const_pointer_type const_iterator; + typedef size_t size_type; + typedef T &reference_type; + typedef const T &const_reference_type; + typedef T *pointer_type; + typedef const T *const_pointer_type; + typedef pointer_type iterator; + typedef const_pointer_type const_iterator; - pointer_type data; - size_type size; + pointer_type data; + size_type size; - WritableBuffer() = default; + WritableBuffer() = default; - constexpr WritableBuffer(pointer_type _data, size_type _size) - :data(_data), size(_size) {} + constexpr WritableBuffer(std::nullptr_t):data(nullptr), size(0) {} - constexpr static WritableBuffer Null() { - return { nullptr, 0 }; - } + constexpr WritableBuffer(pointer_type _data, size_type _size) + :data(_data), size(_size) {} + + constexpr static WritableBuffer Null() { + return { nullptr, 0 }; + } + + /** + * Cast a WritableBuffer<void> to a WritableBuffer<T>. A "void" + * buffer records its size in bytes, and when casting to "T", + * the assertion below ensures that the size is a multiple of + * sizeof(T). + */ +#ifdef NDEBUG + constexpr +#endif + static WritableBuffer<T> FromVoid(WritableBuffer<void> other) { + static_assert(sizeof(T) > 0, "Empty base type"); +#ifndef NDEBUG + assert(other.size % sizeof(T) == 0); +#endif + return WritableBuffer<T>(pointer_type(other.data), + other.size / sizeof(T)); + } - constexpr bool IsNull() const { - return data == nullptr; - } + constexpr WritableBuffer<void> ToVoid() const { + static_assert(sizeof(T) > 0, "Empty base type"); + return WritableBuffer<void>(data, size * sizeof(T)); + } + + constexpr bool IsNull() const { + return data == nullptr; + } + + constexpr bool IsEmpty() const { + return size == 0; + } + + constexpr iterator begin() const { + return data; + } + + constexpr iterator end() const { + return data + size; + } + + constexpr const_iterator cbegin() const { + return data; + } + + constexpr const_iterator cend() const { + return data + size; + } + +#ifdef NDEBUG + constexpr +#endif + reference_type operator[](size_type i) const { +#ifndef NDEBUG + assert(i < size); +#endif + + return data[i]; + } + + /** + * Returns a reference to the first element. Buffer must not + * be empty. + */ +#ifdef NDEBUG + constexpr +#endif + reference_type front() const { +#ifndef NDEBUG + assert(!IsEmpty()); +#endif + return data[0]; + } + + /** + * Returns a reference to the last element. Buffer must not + * be empty. + */ +#ifdef NDEBUG + constexpr +#endif + reference_type back() const { +#ifndef NDEBUG + assert(!IsEmpty()); +#endif + return data[size - 1]; + } - constexpr bool IsEmpty() const { - return size == 0; - } + /** + * Remove the first element (by moving the head pointer, does + * not actually modify the buffer). Buffer must not be empty. + */ + void pop_front() { + assert(!IsEmpty()); - constexpr iterator begin() const { - return data; - } + ++data; + --size; + } - constexpr iterator end() const { - return data + size; - } + /** + * Remove the last element (by moving the tail pointer, does + * not actually modify the buffer). Buffer must not be empty. + */ + void pop_back() { + assert(!IsEmpty()); - constexpr const_iterator cbegin() const { - return data; - } + --size; + } - constexpr const_iterator cend() const { - return data + size; - } + /** + * Remove the first element and return a reference to it. + * Buffer must not be empty. + */ + reference_type shift() { + reference_type result = front(); + pop_front(); + return result; + } }; #endif diff --git a/src/util/bit_reverse.c b/src/util/bit_reverse.c index ba8a23ef1..9226c4261 100644 --- a/src/util/bit_reverse.c +++ b/src/util/bit_reverse.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 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/util/bit_reverse.h b/src/util/bit_reverse.h index a02f01b3f..b39b02e92 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 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/util/fifo_buffer.c b/src/util/fifo_buffer.c deleted file mode 100644 index 162ddf946..000000000 --- a/src/util/fifo_buffer.c +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include "fifo_buffer.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> - -struct fifo_buffer { - size_t size, start, end; - unsigned char buffer[sizeof(size_t)]; -}; - -struct fifo_buffer * -fifo_buffer_new(size_t size) -{ - struct fifo_buffer *buffer; - - assert(size > 0); - - buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) - - sizeof(buffer->buffer) + size); - - buffer->size = size; - buffer->start = 0; - buffer->end = 0; - - return buffer; -} - -void -fifo_buffer_init(struct fifo_buffer *buffer, size_t size) -{ - buffer->size = size - (sizeof(*buffer) - sizeof(buffer->buffer)); - buffer->start = 0; - buffer->end = 0; -} - -static void -fifo_buffer_move(struct fifo_buffer *buffer); - -struct fifo_buffer * -fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size) -{ - if (buffer == NULL) - return new_size > 0 - ? fifo_buffer_new(new_size) - : NULL; - - /* existing data must fit in new size */ - assert(new_size >= buffer->end - buffer->start); - - if (new_size == 0) { - fifo_buffer_free(buffer); - return NULL; - } - - /* compress the buffer when we're shrinking and the tail of - the buffer would exceed the new size */ - if (buffer->end > new_size) - fifo_buffer_move(buffer); - - /* existing data must fit in new size: second check */ - assert(buffer->end <= new_size); - - buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) + - new_size); - buffer->size = new_size; - return buffer; -} - -void -fifo_buffer_free(struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - g_free(buffer); -} - -size_t -fifo_buffer_capacity(const struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - return buffer->size; -} - -size_t -fifo_buffer_available(const struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - return buffer->end - buffer->start; -} - -void -fifo_buffer_clear(struct fifo_buffer *buffer) -{ - assert(buffer != NULL); - - buffer->start = 0; - buffer->end = 0; -} - -const void * -fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(length_r != NULL); - - if (buffer->start == buffer->end) - /* the buffer is empty */ - return NULL; - - *length_r = buffer->end - buffer->start; - return buffer->buffer + buffer->start; -} - -void -fifo_buffer_consume(struct fifo_buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(buffer->start + length <= buffer->end); - - buffer->start += length; -} - -/** - * Move data to the beginning of the buffer, to make room at the end. - */ -static void -fifo_buffer_move(struct fifo_buffer *buffer) -{ - if (buffer->start == 0) - return; - - if (buffer->end > buffer->start) - memmove(buffer->buffer, - buffer->buffer + buffer->start, - buffer->end - buffer->start); - - buffer->end -= buffer->start; - buffer->start = 0; -} - -void * -fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r) -{ - assert(buffer != NULL); - assert(buffer->end <= buffer->size); - assert(max_length_r != NULL); - - if (buffer->end == buffer->size) { - fifo_buffer_move(buffer); - if (buffer->end == buffer->size) - return NULL; - } else if (buffer->start > 0 && buffer->start == buffer->end) { - buffer->start = 0; - buffer->end = 0; - } - - *max_length_r = buffer->size - buffer->end; - return buffer->buffer + buffer->end; -} - -void -fifo_buffer_append(struct fifo_buffer *buffer, size_t length) -{ - assert(buffer != NULL); - assert(buffer->end >= buffer->start); - assert(buffer->end + length <= buffer->size); - - buffer->end += length; -} - -bool -fifo_buffer_is_empty(struct fifo_buffer *buffer) -{ - return buffer->start == buffer->end; -} - -bool -fifo_buffer_is_full(struct fifo_buffer *buffer) -{ - return buffer->start == 0 && buffer->end == buffer->size; -} diff --git a/src/util/fifo_buffer.h b/src/util/fifo_buffer.h deleted file mode 100644 index ccea97d86..000000000 --- a/src/util/fifo_buffer.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** \file - * - * This is a general purpose FIFO buffer library. You may append data - * at the end, while another instance reads data from the beginning. - * It is optimized for zero-copy usage: you get pointers to the real - * buffer, where you may operate on. - * - * This library is not thread safe. - */ - -#ifndef MPD_FIFO_BUFFER_H -#define MPD_FIFO_BUFFER_H - -#include <stdbool.h> -#include <stddef.h> - -struct fifo_buffer; - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Creates a new #fifo_buffer object. Free this object with - * fifo_buffer_free(). - * - * @param size the size of the buffer in bytes - * @return the new #fifo_buffer object - */ -struct fifo_buffer * -fifo_buffer_new(size_t size); - -void -fifo_buffer_init(struct fifo_buffer *buffer, size_t size); - -/** - * Change the capacity of the #fifo_buffer, while preserving existing - * data. - * - * @param buffer the old buffer, may be NULL - * @param new_size the requested new size of the #fifo_buffer; must - * not be smaller than the data which is stored in the old buffer - * @return the new buffer, may be NULL if the requested new size is 0 - */ -struct fifo_buffer * -fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size); - -/** - * Frees the resources consumed by this #fifo_buffer object. - */ -void -fifo_buffer_free(struct fifo_buffer *buffer); - -/** - * Return the capacity of the buffer, i.e. the size that was passed to - * fifo_buffer_new(). - */ -size_t -fifo_buffer_capacity(const struct fifo_buffer *buffer); - -/** - * Return the number of bytes currently stored in the buffer. - */ -size_t -fifo_buffer_available(const struct fifo_buffer *buffer); - -/** - * Clears all data currently in this #fifo_buffer object. This does - * not overwrite the actuall buffer; it just resets the internal - * pointers. - */ -void -fifo_buffer_clear(struct fifo_buffer *buffer); - -/** - * Reads from the beginning of the buffer. To remove consumed data - * from the buffer, call fifo_buffer_consume(). - * - * @param buffer the #fifo_buffer object - * @param length_r the maximum amount to read is returned here - * @return a pointer to the beginning of the buffer, or NULL if the - * buffer is empty - */ -const void * -fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r); - -/** - * Marks data at the beginning of the buffer as "consumed". - * - * @param buffer the #fifo_buffer object - * @param length the number of bytes which were consumed - */ -void -fifo_buffer_consume(struct fifo_buffer *buffer, size_t length); - -/** - * Prepares writing to the buffer. This returns a buffer which you - * can write to. To commit the write operation, call - * fifo_buffer_append(). - * - * @param buffer the #fifo_buffer object - * @param max_length_r the maximum amount to write is returned here - * @return a pointer to the end of the buffer, or NULL if the buffer - * is already full - */ -void * -fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r); - -/** - * Commits the write operation initiated by fifo_buffer_write(). - * - * @param buffer the #fifo_buffer object - * @param length the number of bytes which were written - */ -void -fifo_buffer_append(struct fifo_buffer *buffer, size_t length); - -/** - * Checks if the buffer is empty. - */ -bool -fifo_buffer_is_empty(struct fifo_buffer *buffer); - -/** - * Checks if the buffer is full. - */ -bool -fifo_buffer_is_full(struct fifo_buffer *buffer); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/util/growing_fifo.c b/src/util/growing_fifo.c deleted file mode 100644 index 88431f60e..000000000 --- a/src/util/growing_fifo.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "growing_fifo.h" -#include "fifo_buffer.h" - -#include <assert.h> -#include <string.h> - -/** - * Align buffer sizes at 8 kB boundaries. Must be a power of two. - */ -static const size_t GROWING_FIFO_ALIGN = 8192; - -/** - * Align the specified size to the next #GROWING_FIFO_ALIGN boundary. - */ -static size_t -align(size_t size) -{ - return ((size - 1) | (GROWING_FIFO_ALIGN - 1)) + 1; -} - -struct fifo_buffer * -growing_fifo_new(void) -{ - return fifo_buffer_new(GROWING_FIFO_ALIGN); -} - -void * -growing_fifo_write(struct fifo_buffer **buffer_p, size_t length) -{ - assert(buffer_p != NULL); - - struct fifo_buffer *buffer = *buffer_p; - assert(buffer != NULL); - - size_t max_length; - void *p = fifo_buffer_write(buffer, &max_length); - if (p != NULL && max_length >= length) - return p; - - /* grow */ - size_t new_size = fifo_buffer_available(buffer) + length; - assert(new_size > fifo_buffer_capacity(buffer)); - *buffer_p = buffer = fifo_buffer_realloc(buffer, align(new_size)); - - /* try again */ - p = fifo_buffer_write(buffer, &max_length); - assert(p != NULL); - assert(max_length >= length); - - return p; -} - -void -growing_fifo_append(struct fifo_buffer **buffer_p, - const void *data, size_t length) -{ - void *p = growing_fifo_write(buffer_p, length); - memcpy(p, data, length); - fifo_buffer_append(*buffer_p, length); -} diff --git a/src/util/growing_fifo.h b/src/util/growing_fifo.h deleted file mode 100644 index 723c3b3ff..000000000 --- a/src/util/growing_fifo.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** \file - * - * Helper functions for our FIFO buffer library (fifo_buffer.h) that - * allows growing the buffer on demand. - * - * This library is not thread safe. - */ - -#ifndef MPD_GROWING_FIFO_H -#define MPD_GROWING_FIFO_H - -#include <stddef.h> - -struct fifo_buffer; - -/** - * Allocate a new #fifo_buffer with the default size. - */ -struct fifo_buffer * -growing_fifo_new(void); - -/** - * Prepares writing to the buffer, see fifo_buffer_write() for - * details. The difference is that this function will automatically - * grow the buffer if it is too small. - * - * The caller is responsible for limiting the capacity of the buffer. - * - * @param length the number of bytes that will be written - * @return a pointer to the end of the buffer (will not be NULL) - */ -void * -growing_fifo_write(struct fifo_buffer **buffer_p, size_t length); - -/** - * A helper function that combines growing_fifo_write(), memcpy(), - * fifo_buffer_append(). - */ -void -growing_fifo_append(struct fifo_buffer **buffer_p, - const void *data, size_t length); - -#endif diff --git a/src/util/list.h b/src/util/list.h deleted file mode 100644 index 73d99befa..000000000 --- a/src/util/list.h +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright (C) 2003-2012 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. - */ - -/* - * This code was imported from the Linux kernel. - * - */ - -#ifndef _LINUX_LIST_H -#define _LINUX_LIST_H - -#ifdef __clang__ -/* allow typeof() */ -#pragma GCC diagnostic ignored "-Wlanguage-extension-token" -#endif - -/** - * container_of - cast a member of a structure out to the containing structure - * @ptr: the pointer to the member. - * @type: the type of the container struct this is embedded in. - * @member: the name of the member within the struct. - * - */ -#define container_of(ptr, type, member) \ - ((type *)((uint8_t *)ptr - offsetof(type, member))) - -/* - * These are non-NULL pointers that will result in page faults - * under normal circumstances, used to verify that nobody uses - * non-initialized list entries. - */ -#define LIST_POISON1 ((struct list_head *)(void *) 0x00100100) -#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200) - -/* - * Simple doubly linked list implementation. - * - * Some of the internal functions ("__xxx") are useful when - * manipulating whole lists rather than single entries, as - * sometimes we already know the next/prev entries and we can - * generate better code by using them directly rather than - * using the generic single-entry routines. - */ - -struct list_head { - struct list_head *next, *prev; -}; - -#define LIST_HEAD_INIT(name) { &(name), &(name) } - -#define LIST_HEAD(name) \ - struct list_head name = LIST_HEAD_INIT(name) - -static inline void INIT_LIST_HEAD(struct list_head *list) -{ - list->next = list; - list->prev = list; -} - -/* - * Insert a new entry between two known consecutive entries. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -#ifndef CONFIG_DEBUG_LIST -static inline void __list_add(struct list_head *new_item, - struct list_head *prev, - struct list_head *next) -{ - next->prev = new_item; - new_item->next = next; - new_item->prev = prev; - prev->next = new_item; -} -#else -extern void __list_add(struct list_head *new_item, - struct list_head *prev, - struct list_head *next); -#endif - -/** - * list_add - add a new entry - * @new_item: new entry to be added - * @head: list head to add it after - * - * Insert a new entry after the specified head. - * This is good for implementing stacks. - */ -static inline void list_add(struct list_head *new_item, struct list_head *head) -{ - __list_add(new_item, head, head->next); -} - - -/** - * list_add_tail - add a new entry - * @new_item: new entry to be added - * @head: list head to add it before - * - * Insert a new entry before the specified head. - * This is useful for implementing queues. - */ -static inline void -list_add_tail(struct list_head *new_item, struct list_head *head) -{ - __list_add(new_item, head->prev, head); -} - -/* - * Delete a list entry by making the prev/next entries - * point to each other. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -static inline void __list_del(struct list_head * prev, struct list_head * next) -{ - next->prev = prev; - prev->next = next; -} - -/** - * list_del - deletes entry from list. - * @entry: the element to delete from the list. - * Note: list_empty() on entry does not return true after this, the entry is - * in an undefined state. - */ -#ifndef CONFIG_DEBUG_LIST -static inline void __list_del_entry(struct list_head *entry) -{ - __list_del(entry->prev, entry->next); -} - -static inline void list_del(struct list_head *entry) -{ - __list_del(entry->prev, entry->next); - entry->next = LIST_POISON1; - entry->prev = LIST_POISON2; -} -#else -extern void __list_del_entry(struct list_head *entry); -extern void list_del(struct list_head *entry); -#endif - -/** - * list_replace - replace old entry by new one - * @old : the element to be replaced - * @new_item : the new element to insert - * - * If @old was empty, it will be overwritten. - */ -static inline void list_replace(struct list_head *old, - struct list_head *new_item) -{ - new_item->next = old->next; - new_item->next->prev = new_item; - new_item->prev = old->prev; - new_item->prev->next = new_item; -} - -static inline void list_replace_init(struct list_head *old, - struct list_head *new_item) -{ - list_replace(old, new_item); - INIT_LIST_HEAD(old); -} - -/** - * list_del_init - deletes entry from list and reinitialize it. - * @entry: the element to delete from the list. - */ -static inline void list_del_init(struct list_head *entry) -{ - __list_del_entry(entry); - INIT_LIST_HEAD(entry); -} - -/** - * list_move - delete from one list and add as another's head - * @list: the entry to move - * @head: the head that will precede our entry - */ -static inline void list_move(struct list_head *list, struct list_head *head) -{ - __list_del_entry(list); - list_add(list, head); -} - -/** - * list_move_tail - delete from one list and add as another's tail - * @list: the entry to move - * @head: the head that will follow our entry - */ -static inline void list_move_tail(struct list_head *list, - struct list_head *head) -{ - __list_del_entry(list); - list_add_tail(list, head); -} - -/** - * list_is_last - tests whether @list is the last entry in list @head - * @list: the entry to test - * @head: the head of the list - */ -static inline int list_is_last(const struct list_head *list, - const struct list_head *head) -{ - return list->next == head; -} - -/** - * list_empty - tests whether a list is empty - * @head: the list to test. - */ -static inline int list_empty(const struct list_head *head) -{ - return head->next == head; -} - -/** - * list_empty_careful - tests whether a list is empty and not being modified - * @head: the list to test - * - * Description: - * tests whether a list is empty _and_ checks that no other CPU might be - * in the process of modifying either member (next or prev) - * - * NOTE: using list_empty_careful() without synchronization - * can only be safe if the only activity that can happen - * to the list entry is list_del_init(). Eg. it cannot be used - * if another CPU could re-list_add() it. - */ -static inline int list_empty_careful(const struct list_head *head) -{ - struct list_head *next = head->next; - return (next == head) && (next == head->prev); -} - -/** - * list_rotate_left - rotate the list to the left - * @head: the head of the list - */ -static inline void list_rotate_left(struct list_head *head) -{ - struct list_head *first; - - if (!list_empty(head)) { - first = head->next; - list_move_tail(first, head); - } -} - -/** - * list_is_singular - tests whether a list has just one entry. - * @head: the list to test. - */ -static inline int list_is_singular(const struct list_head *head) -{ - return !list_empty(head) && (head->next == head->prev); -} - -static inline void __list_cut_position(struct list_head *list, - struct list_head *head, struct list_head *entry) -{ - struct list_head *new_first = entry->next; - list->next = head->next; - list->next->prev = list; - list->prev = entry; - entry->next = list; - head->next = new_first; - new_first->prev = head; -} - -/** - * list_cut_position - cut a list into two - * @list: a new list to add all removed entries - * @head: a list with entries - * @entry: an entry within head, could be the head itself - * and if so we won't cut the list - * - * This helper moves the initial part of @head, up to and - * including @entry, from @head to @list. You should - * pass on @entry an element you know is on @head. @list - * should be an empty list or a list you do not care about - * losing its data. - * - */ -static inline void list_cut_position(struct list_head *list, - struct list_head *head, struct list_head *entry) -{ - if (list_empty(head)) - return; - if (list_is_singular(head) && - (head->next != entry && head != entry)) - return; - if (entry == head) - INIT_LIST_HEAD(list); - else - __list_cut_position(list, head, entry); -} - -static inline void __list_splice(const struct list_head *list, - struct list_head *prev, - struct list_head *next) -{ - struct list_head *first = list->next; - struct list_head *last = list->prev; - - first->prev = prev; - prev->next = first; - - last->next = next; - next->prev = last; -} - -/** - * list_splice - join two lists, this is designed for stacks - * @list: the new list to add. - * @head: the place to add it in the first list. - */ -static inline void list_splice(const struct list_head *list, - struct list_head *head) -{ - if (!list_empty(list)) - __list_splice(list, head, head->next); -} - -/** - * list_splice_tail - join two lists, each list being a queue - * @list: the new list to add. - * @head: the place to add it in the first list. - */ -static inline void list_splice_tail(struct list_head *list, - struct list_head *head) -{ - if (!list_empty(list)) - __list_splice(list, head->prev, head); -} - -/** - * list_splice_init - join two lists and reinitialise the emptied list. - * @list: the new list to add. - * @head: the place to add it in the first list. - * - * The list at @list is reinitialised - */ -static inline void list_splice_init(struct list_head *list, - struct list_head *head) -{ - if (!list_empty(list)) { - __list_splice(list, head, head->next); - INIT_LIST_HEAD(list); - } -} - -/** - * list_splice_tail_init - join two lists and reinitialise the emptied list - * @list: the new list to add. - * @head: the place to add it in the first list. - * - * Each of the lists is a queue. - * The list at @list is reinitialised - */ -static inline void list_splice_tail_init(struct list_head *list, - struct list_head *head) -{ - if (!list_empty(list)) { - __list_splice(list, head->prev, head); - INIT_LIST_HEAD(list); - } -} - -/** - * list_entry - get the struct for this entry - * @ptr: the &struct list_head pointer. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_struct within the struct. - */ -#define list_entry(ptr, type, member) \ - container_of(ptr, type, member) - -/** - * list_first_entry - get the first element from a list - * @ptr: the list head to take the element from. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_struct within the struct. - * - * Note, that list is expected to be not empty. - */ -#define list_first_entry(ptr, type, member) \ - list_entry((ptr)->next, type, member) - -/** - * list_for_each - iterate over a list - * @pos: the &struct list_head to use as a loop cursor. - * @head: the head for your list. - */ -#define list_for_each(pos, head) \ - for (pos = (head)->next; pos != (head); pos = pos->next) - -/** - * __list_for_each - iterate over a list - * @pos: the &struct list_head to use as a loop cursor. - * @head: the head for your list. - * - * This variant doesn't differ from list_for_each() any more. - * We don't do prefetching in either case. - */ -#define __list_for_each(pos, head) \ - for (pos = (head)->next; pos != (head); pos = pos->next) - -/** - * list_for_each_prev - iterate over a list backwards - * @pos: the &struct list_head to use as a loop cursor. - * @head: the head for your list. - */ -#define list_for_each_prev(pos, head) \ - for (pos = (head)->prev; pos != (head); pos = pos->prev) - -/** - * list_for_each_safe - iterate over a list safe against removal of list entry - * @pos: the &struct list_head to use as a loop cursor. - * @n: another &struct list_head to use as temporary storage - * @head: the head for your list. - */ -#define list_for_each_safe(pos, n, head) \ - for (pos = (head)->next, n = pos->next; pos != (head); \ - pos = n, n = pos->next) - -/** - * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry - * @pos: the &struct list_head to use as a loop cursor. - * @n: another &struct list_head to use as temporary storage - * @head: the head for your list. - */ -#define list_for_each_prev_safe(pos, n, head) \ - for (pos = (head)->prev, n = pos->prev; \ - pos != (head); \ - pos = n, n = pos->prev) - -/** - * list_for_each_entry - iterate over list of given type - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - */ -#define list_for_each_entry(pos, head, member) \ - for (pos = list_entry((head)->next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = list_entry(pos->member.next, typeof(*pos), member)) - -/** - * list_for_each_entry_reverse - iterate backwards over list of given type. - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - */ -#define list_for_each_entry_reverse(pos, head, member) \ - for (pos = list_entry((head)->prev, typeof(*pos), member); \ - &pos->member != (head); \ - pos = list_entry(pos->member.prev, typeof(*pos), member)) - -/** - * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() - * @pos: the type * to use as a start point - * @head: the head of the list - * @member: the name of the list_struct within the struct. - * - * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). - */ -#define list_prepare_entry(pos, head, member) \ - ((pos) ? : list_entry(head, typeof(*pos), member)) - -/** - * list_for_each_entry_continue - continue iteration over list of given type - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Continue to iterate over list of given type, continuing after - * the current position. - */ -#define list_for_each_entry_continue(pos, head, member) \ - for (pos = list_entry(pos->member.next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = list_entry(pos->member.next, typeof(*pos), member)) - -/** - * list_for_each_entry_continue_reverse - iterate backwards from the given point - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Start to iterate over list of given type backwards, continuing after - * the current position. - */ -#define list_for_each_entry_continue_reverse(pos, head, member) \ - for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ - &pos->member != (head); \ - pos = list_entry(pos->member.prev, typeof(*pos), member)) - -/** - * list_for_each_entry_from - iterate over list of given type from the current point - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Iterate over list of given type, continuing from current position. - */ -#define list_for_each_entry_from(pos, head, member) \ - for (; &pos->member != (head); \ - pos = list_entry(pos->member.next, typeof(*pos), member)) - -/** - * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - */ -#define list_for_each_entry_safe(pos, n, head, member) \ - for (pos = list_entry((head)->next, typeof(*pos), member), \ - n = list_entry(pos->member.next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = n, n = list_entry(n->member.next, typeof(*n), member)) - -/** - * list_for_each_entry_safe_continue - continue list iteration safe against removal - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Iterate over list of given type, continuing after current point, - * safe against removal of list entry. - */ -#define list_for_each_entry_safe_continue(pos, n, head, member) \ - for (pos = list_entry(pos->member.next, typeof(*pos), member), \ - n = list_entry(pos->member.next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = n, n = list_entry(n->member.next, typeof(*n), member)) - -/** - * list_for_each_entry_safe_from - iterate over list from current point safe against removal - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Iterate over list of given type from current point, safe against - * removal of list entry. - */ -#define list_for_each_entry_safe_from(pos, n, head, member) \ - for (n = list_entry(pos->member.next, typeof(*pos), member); \ - &pos->member != (head); \ - pos = n, n = list_entry(n->member.next, typeof(*n), member)) - -/** - * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_struct within the struct. - * - * Iterate backwards over list of given type, safe against removal - * of list entry. - */ -#define list_for_each_entry_safe_reverse(pos, n, head, member) \ - for (pos = list_entry((head)->prev, typeof(*pos), member), \ - n = list_entry(pos->member.prev, typeof(*pos), member); \ - &pos->member != (head); \ - pos = n, n = list_entry(n->member.prev, typeof(*n), member)) - -/** - * list_safe_reset_next - reset a stale list_for_each_entry_safe loop - * @pos: the loop cursor used in the list_for_each_entry_safe loop - * @n: temporary storage used in list_for_each_entry_safe - * @member: the name of the list_struct within the struct. - * - * list_safe_reset_next is not safe to use in general if the list may be - * modified concurrently (eg. the lock is dropped in the loop body). An - * exception to this is if the cursor element (pos) is pinned in the list, - * and list_safe_reset_next is called after re-taking the lock and before - * completing the current iteration of the loop body. - */ -#define list_safe_reset_next(pos, n, member) \ - n = list_entry(pos->member.next, typeof(*pos), member) - -#endif diff --git a/src/util/list_sort.c b/src/util/list_sort.c deleted file mode 100644 index 8534f3360..000000000 --- a/src/util/list_sort.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2012 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. - */ - -/* - * This code was imported from the Linux kernel. - * - */ - -#include "list_sort.h" -#include "list.h" -#include "Macros.hxx" -#include "Compiler.h" - -#include <string.h> - -#define unlikely gcc_unlikely - -#define MAX_LIST_LENGTH_BITS 20 - -/* - * Returns a list organized in an intermediate format suited - * to chaining of merge() calls: null-terminated, no reserved or - * sentinel head node, "prev" links not maintained. - */ -static struct list_head *merge(void *priv, - int (*cmp)(void *priv, struct list_head *a, - struct list_head *b), - struct list_head *a, struct list_head *b) -{ - struct list_head head, *tail = &head; - - while (a && b) { - /* if equal, take 'a' -- important for sort stability */ - if ((*cmp)(priv, a, b) <= 0) { - tail->next = a; - a = a->next; - } else { - tail->next = b; - b = b->next; - } - tail = tail->next; - } - tail->next = a?a:b; - return head.next; -} - -/* - * Combine final list merge with restoration of standard doubly-linked - * list structure. This approach duplicates code from merge(), but - * runs faster than the tidier alternatives of either a separate final - * prev-link restoration pass, or maintaining the prev links - * throughout. - */ -static void merge_and_restore_back_links(void *priv, - int (*cmp)(void *priv, struct list_head *a, - struct list_head *b), - struct list_head *head, - struct list_head *a, struct list_head *b) -{ - struct list_head *tail = head; - - while (a && b) { - /* if equal, take 'a' -- important for sort stability */ - if ((*cmp)(priv, a, b) <= 0) { - tail->next = a; - a->prev = tail; - a = a->next; - } else { - tail->next = b; - b->prev = tail; - b = b->next; - } - tail = tail->next; - } - tail->next = a ? a : b; - - do { - /* - * In worst cases this loop may run many iterations. - * Continue callbacks to the client even though no - * element comparison is needed, so the client's cmp() - * routine can invoke cond_resched() periodically. - */ - (*cmp)(priv, tail->next, tail->next); - - tail->next->prev = tail; - tail = tail->next; - } while (tail->next); - - tail->next = head; - head->prev = tail; -} - -/** - * list_sort - sort a list - * @priv: private data, opaque to list_sort(), passed to @cmp - * @head: the list to sort - * @cmp: the elements comparison function - * - * This function implements "merge sort", which has O(nlog(n)) - * complexity. - * - * The comparison function @cmp must return a negative value if @a - * should sort before @b, and a positive value if @a should sort after - * @b. If @a and @b are equivalent, and their original relative - * ordering is to be preserved, @cmp must return 0. - */ -void list_sort(void *priv, struct list_head *head, - int (*cmp)(void *priv, struct list_head *a, - struct list_head *b)) -{ - struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists - -- last slot is a sentinel */ - int lev; /* index into part[] */ - int max_lev = 0; - struct list_head *list; - - if (list_empty(head)) - return; - - memset(part, 0, sizeof(part)); - - head->prev->next = NULL; - list = head->next; - - while (list) { - struct list_head *cur = list; - list = list->next; - cur->next = NULL; - - for (lev = 0; part[lev]; lev++) { - cur = merge(priv, cmp, part[lev], cur); - part[lev] = NULL; - } - if (lev > max_lev) { - max_lev = lev; - } - part[lev] = cur; - } - - for (lev = 0; lev < max_lev; lev++) - if (part[lev]) - list = merge(priv, cmp, part[lev], list); - - merge_and_restore_back_links(priv, cmp, head, part[max_lev], list); -} diff --git a/src/util/list_sort.h b/src/util/list_sort.h deleted file mode 100644 index 7a65020b9..000000000 --- a/src/util/list_sort.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2012 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. - */ - -/* - * This code was imported from the Linux kernel. - * - */ - -#ifndef _LINUX_LIST_SORT_H -#define _LINUX_LIST_SORT_H - -struct list_head; - -void list_sort(void *priv, struct list_head *head, - int (*cmp)(void *priv, struct list_head *a, - struct list_head *b)); -#endif diff --git a/src/win32/Win32Main.cxx b/src/win32/Win32Main.cxx index 0d2a70348..75a1e9a23 100644 --- a/src/win32/Win32Main.cxx +++ b/src/win32/Win32Main.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 diff --git a/src/zeroconf/AvahiPoll.cxx b/src/zeroconf/AvahiPoll.cxx new file mode 100644 index 000000000..20d5d74e6 --- /dev/null +++ b/src/zeroconf/AvahiPoll.cxx @@ -0,0 +1,146 @@ +/* + * 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 "AvahiPoll.hxx" +#include "event/SocketMonitor.hxx" +#include "event/TimeoutMonitor.hxx" + +static unsigned +FromAvahiWatchEvent(AvahiWatchEvent e) +{ + return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) | + (e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) | + (e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) | + (e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0); +} + +static AvahiWatchEvent +ToAvahiWatchEvent(unsigned e) +{ + return AvahiWatchEvent((e & SocketMonitor::READ ? AVAHI_WATCH_IN : 0) | + (e & SocketMonitor::WRITE ? AVAHI_WATCH_OUT : 0) | + (e & SocketMonitor::ERROR ? AVAHI_WATCH_ERR : 0) | + (e & SocketMonitor::HANGUP ? AVAHI_WATCH_HUP : 0)); +} + +struct AvahiWatch final : private SocketMonitor { +private: + const AvahiWatchCallback callback; + void *const userdata; + + AvahiWatchEvent received; + +public: + AvahiWatch(int _fd, AvahiWatchEvent _event, + AvahiWatchCallback _callback, void *_userdata, + EventLoop &_loop) + :SocketMonitor(_fd, _loop), + callback(_callback), userdata(_userdata), + received(AvahiWatchEvent(0)) { + Schedule(FromAvahiWatchEvent(_event)); + } + + static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) { + w->Schedule(FromAvahiWatchEvent(event)); + } + + static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) { + return w->received; + } + + static void WatchFree(AvahiWatch *w) { + delete w; + } + +protected: + virtual bool OnSocketReady(unsigned flags) { + received = ToAvahiWatchEvent(flags); + callback(this, Get(), received, userdata); + received = AvahiWatchEvent(0); + return true; + } +}; + +static constexpr unsigned +TimevalToMS(const timeval &tv) +{ + return tv.tv_sec * 1000 + (tv.tv_usec + 500) / 1000; +} + +struct AvahiTimeout final : private TimeoutMonitor { +private: + const AvahiTimeoutCallback callback; + void *const userdata; + +public: + AvahiTimeout(const struct timeval *tv, + AvahiTimeoutCallback _callback, void *_userdata, + EventLoop &_loop) + :TimeoutMonitor(_loop), + callback(_callback), userdata(_userdata) { + if (tv != nullptr) + Schedule(TimevalToMS(*tv)); + } + + static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) { + if (tv != nullptr) + t->Schedule(TimevalToMS(*tv)); + else + t->Cancel(); + } + + static void TimeoutFree(AvahiTimeout *t) { + delete t; + } + +protected: + virtual void OnTimeout() { + callback(this, userdata); + } +}; + +MyAvahiPoll::MyAvahiPoll(EventLoop &_loop):event_loop(_loop) +{ + watch_new = WatchNew; + watch_update = AvahiWatch::WatchUpdate; + watch_get_events = AvahiWatch::WatchGetEvents; + watch_free = AvahiWatch::WatchFree; + timeout_new = TimeoutNew; + timeout_update = AvahiTimeout::TimeoutUpdate; + timeout_free = AvahiTimeout::TimeoutFree; +} + +AvahiWatch * +MyAvahiPoll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event, + AvahiWatchCallback callback, void *userdata) { + const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; + + return new AvahiWatch(fd, event, callback, userdata, + poll.event_loop); +} + +AvahiTimeout * +MyAvahiPoll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv, + AvahiTimeoutCallback callback, void *userdata) { + const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; + + return new AvahiTimeout(tv, callback, userdata, + poll.event_loop); +} diff --git a/src/zeroconf/AvahiPoll.hxx b/src/zeroconf/AvahiPoll.hxx new file mode 100644 index 000000000..e194d3370 --- /dev/null +++ b/src/zeroconf/AvahiPoll.hxx @@ -0,0 +1,48 @@ +/* + * 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_AVAHI_POLL_HXX +#define MPD_AVAHI_POLL_HXX + +#include "check.h" +#include "Compiler.h" + +#include <avahi-common/watch.h> + +class EventLoop; + +class MyAvahiPoll final : public AvahiPoll { + EventLoop &event_loop; + +public: + MyAvahiPoll(EventLoop &_loop); + +private: + static AvahiWatch *WatchNew(const AvahiPoll *api, int fd, + AvahiWatchEvent event, + AvahiWatchCallback callback, + void *userdata); + + static AvahiTimeout *TimeoutNew(const AvahiPoll *api, + const struct timeval *tv, + AvahiTimeoutCallback callback, + void *userdata); +}; + +#endif diff --git a/src/zeroconf/ZeroconfAvahi.cxx b/src/zeroconf/ZeroconfAvahi.cxx new file mode 100644 index 000000000..35f3dc9dc --- /dev/null +++ b/src/zeroconf/ZeroconfAvahi.cxx @@ -0,0 +1,279 @@ +/* + * 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 "ZeroconfAvahi.hxx" +#include "AvahiPoll.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include <dbus/dbus.h> + +static constexpr Domain avahi_domain("avahi"); + +static char *avahiName; +static bool avahi_running; +static MyAvahiPoll *avahi_poll; +static AvahiClient *avahiClient; +static AvahiEntryGroup *avahiGroup; + +static void avahiRegisterService(AvahiClient * c); + +/* Callback when the EntryGroup changes state */ +static void avahiGroupCallback(AvahiEntryGroup * g, + AvahiEntryGroupState state, + gcc_unused void *userdata) +{ + char *n; + assert(g); + + FormatDebug(avahi_domain, + "Service group changed to state %d", state); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + FormatDefault(avahi_domain, + "Service '%s' successfully established.", + avahiName); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + /* A service name collision happened. Let's pick a new name */ + n = avahi_alternative_service_name(avahiName); + avahi_free(avahiName); + avahiName = n; + + FormatDefault(avahi_domain, + "Service name collision, renaming service to '%s'", + avahiName); + + /* And recreate the services */ + avahiRegisterService(avahi_entry_group_get_client(g)); + break; + + case AVAHI_ENTRY_GROUP_FAILURE: + FormatError(avahi_domain, + "Entry group failure: %s", + avahi_strerror(avahi_client_errno + (avahi_entry_group_get_client(g)))); + /* Some kind of failure happened while we were registering our services */ + avahi_running = false; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + LogDebug(avahi_domain, "Service group is UNCOMMITED"); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + LogDebug(avahi_domain, "Service group is REGISTERING"); + } +} + +/* Registers a new service with avahi */ +static void avahiRegisterService(AvahiClient * c) +{ + FormatDebug(avahi_domain, "Registering service %s/%s", + SERVICE_TYPE, avahiName); + + int ret; + assert(c); + + /* If this is the first time we're called, + * let's create a new entry group */ + if (!avahiGroup) { + avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, nullptr); + if (!avahiGroup) { + FormatError(avahi_domain, + "Failed to create avahi EntryGroup: %s", + avahi_strerror(avahi_client_errno(c))); + goto fail; + } + } + + /* Add the service */ + /* TODO: This currently binds to ALL interfaces. + * We could maybe add a service per actual bound interface, + * if that's better. */ + ret = avahi_entry_group_add_service(avahiGroup, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AvahiPublishFlags(0), + avahiName, SERVICE_TYPE, nullptr, + nullptr, listen_port, nullptr); + if (ret < 0) { + FormatError(avahi_domain, "Failed to add service %s: %s", + SERVICE_TYPE, avahi_strerror(ret)); + goto fail; + } + + /* Tell the server to register the service group */ + ret = avahi_entry_group_commit(avahiGroup); + if (ret < 0) { + FormatError(avahi_domain, "Failed to commit service group: %s", + avahi_strerror(ret)); + goto fail; + } + return; + +fail: + avahi_running = false; +} + +/* Callback when avahi changes state */ +static void avahiClientCallback(AvahiClient * c, AvahiClientState state, + gcc_unused void *userdata) +{ + int reason; + assert(c); + + /* Called whenever the client or server state changes */ + FormatDebug(avahi_domain, "Client changed to state %d", state); + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + LogDebug(avahi_domain, "Client is RUNNING"); + + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if (!avahiGroup) + avahiRegisterService(c); + break; + + case AVAHI_CLIENT_FAILURE: + reason = avahi_client_errno(c); + if (reason == AVAHI_ERR_DISCONNECTED) { + LogDefault(avahi_domain, + "Client Disconnected, will reconnect shortly"); + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = nullptr; + } + if (avahiClient) + avahi_client_free(avahiClient); + avahiClient = + avahi_client_new(avahi_poll, + AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, nullptr, + &reason); + if (!avahiClient) { + FormatWarning(avahi_domain, + "Could not reconnect: %s", + avahi_strerror(reason)); + avahi_running = false; + } + } else { + FormatWarning(avahi_domain, + "Client failure: %s (terminal)", + avahi_strerror(reason)); + avahi_running = false; + } + break; + + case AVAHI_CLIENT_S_COLLISION: + LogDebug(avahi_domain, "Client is COLLISION"); + + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if (avahiGroup) { + LogDebug(avahi_domain, "Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_S_REGISTERING: + LogDebug(avahi_domain, "Client is REGISTERING"); + + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + + if (avahiGroup) { + LogDebug(avahi_domain, "Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_CONNECTING: + LogDebug(avahi_domain, "Client is CONNECTING"); + break; + } +} + +void +AvahiInit(EventLoop &loop, const char *serviceName) +{ + LogDebug(avahi_domain, "Initializing interface"); + + if (!avahi_is_valid_service_name(serviceName)) + FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName); + + avahiName = avahi_strdup(serviceName); + + avahi_running = true; + + avahi_poll = new MyAvahiPoll(loop); + + int error; + avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, nullptr, &error); + + if (!avahiClient) { + FormatError(avahi_domain, "Failed to create client: %s", + avahi_strerror(error)); + AvahiDeinit(); + } +} + +void +AvahiDeinit(void) +{ + LogDebug(avahi_domain, "Shutting down interface"); + + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = nullptr; + } + + if (avahiClient) { + avahi_client_free(avahiClient); + avahiClient = nullptr; + } + + delete avahi_poll; + avahi_poll = nullptr; + + avahi_free(avahiName); + avahiName = nullptr; + + dbus_shutdown(); +} diff --git a/src/zeroconf/ZeroconfAvahi.hxx b/src/zeroconf/ZeroconfAvahi.hxx new file mode 100644 index 000000000..09a199f55 --- /dev/null +++ b/src/zeroconf/ZeroconfAvahi.hxx @@ -0,0 +1,31 @@ +/* + * 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_ZEROCONF_AVAHI_HXX +#define MPD_ZEROCONF_AVAHI_HXX + +class EventLoop; + +void +AvahiInit(EventLoop &loop, const char *service_name); + +void +AvahiDeinit(); + +#endif diff --git a/src/zeroconf/ZeroconfBonjour.cxx b/src/zeroconf/ZeroconfBonjour.cxx new file mode 100644 index 000000000..5b5de1247 --- /dev/null +++ b/src/zeroconf/ZeroconfBonjour.cxx @@ -0,0 +1,108 @@ +/* + * 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 "ZeroconfBonjour.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "event/SocketMonitor.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "Compiler.h" + +#include <glib.h> + +#include <dns_sd.h> + +static constexpr Domain bonjour_domain("bonjour"); + +class BonjourMonitor final : public SocketMonitor { + DNSServiceRef service_ref; + +public: + BonjourMonitor(EventLoop &_loop, DNSServiceRef _service_ref) + :SocketMonitor(DNSServiceRefSockFD(_service_ref), _loop), + service_ref(_service_ref) { + ScheduleRead(); + } + + ~BonjourMonitor() { + DNSServiceRefDeallocate(service_ref); + } + +protected: + virtual bool OnSocketReady(gcc_unused unsigned flags) override { + DNSServiceProcessResult(service_ref); + return false; + } +}; + +static BonjourMonitor *bonjour_monitor; + +static void +dnsRegisterCallback(gcc_unused DNSServiceRef sdRef, + gcc_unused DNSServiceFlags flags, + DNSServiceErrorType errorCode, const char *name, + gcc_unused const char *regtype, + gcc_unused const char *domain, + gcc_unused void *context) +{ + if (errorCode != kDNSServiceErr_NoError) { + LogError(bonjour_domain, + "Failed to register zeroconf service"); + + bonjour_monitor->Cancel(); + } else { + FormatDebug(bonjour_domain, + "Registered zeroconf service with name '%s'", + name); + } +} + +void +BonjourInit(EventLoop &loop, const char *service_name) +{ + DNSServiceRef dnsReference; + DNSServiceErrorType error = DNSServiceRegister(&dnsReference, + 0, 0, service_name, + SERVICE_TYPE, nullptr, nullptr, + g_htons(listen_port), 0, + nullptr, + dnsRegisterCallback, + nullptr); + + if (error != kDNSServiceErr_NoError) { + LogError(bonjour_domain, + "Failed to register zeroconf service"); + + if (dnsReference) { + DNSServiceRefDeallocate(dnsReference); + dnsReference = nullptr; + } + return; + } + + bonjour_monitor = new BonjourMonitor(loop, dnsReference); +} + +void +BonjourDeinit() +{ + delete bonjour_monitor; +} diff --git a/src/zeroconf/ZeroconfBonjour.hxx b/src/zeroconf/ZeroconfBonjour.hxx new file mode 100644 index 000000000..cff52815e --- /dev/null +++ b/src/zeroconf/ZeroconfBonjour.hxx @@ -0,0 +1,31 @@ +/* + * 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_ZEROCONF_BONJOUR_HXX +#define MPD_ZEROCONF_BONJOUR_HXX + +class EventLoop; + +void +BonjourInit(EventLoop &loop, const char *service_name); + +void +BonjourDeinit(); + +#endif diff --git a/src/zeroconf/ZeroconfGlue.cxx b/src/zeroconf/ZeroconfGlue.cxx new file mode 100644 index 000000000..95797491b --- /dev/null +++ b/src/zeroconf/ZeroconfGlue.cxx @@ -0,0 +1,83 @@ +/* + * 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 "ZeroconfGlue.hxx" +#include "ZeroconfAvahi.hxx" +#include "ZeroconfBonjour.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "Listen.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "Compiler.h" + +static constexpr Domain zeroconf_domain("zeroconf"); + +/* The default service name to publish + * (overridden by 'zeroconf_name' config parameter) + */ +#define SERVICE_NAME "Music Player" + +#define DEFAULT_ZEROCONF_ENABLED 1 + +static int zeroconfEnabled; + +void +ZeroconfInit(gcc_unused EventLoop &loop) +{ + const char *serviceName; + + zeroconfEnabled = config_get_bool(CONF_ZEROCONF_ENABLED, + DEFAULT_ZEROCONF_ENABLED); + if (!zeroconfEnabled) + return; + + if (listen_port <= 0) { + LogWarning(zeroconf_domain, + "No global port, disabling zeroconf"); + zeroconfEnabled = false; + return; + } + + serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); + +#ifdef HAVE_AVAHI + AvahiInit(loop, serviceName); +#endif + +#ifdef HAVE_BONJOUR + BonjourInit(loop, serviceName); +#endif +} + +void +ZeroconfDeinit() +{ + if (!zeroconfEnabled) + return; + +#ifdef HAVE_AVAHI + AvahiDeinit(); +#endif /* HAVE_AVAHI */ + +#ifdef HAVE_BONJOUR + BonjourDeinit(); +#endif +} diff --git a/src/zeroconf/ZeroconfGlue.hxx b/src/zeroconf/ZeroconfGlue.hxx new file mode 100644 index 000000000..5d2f29642 --- /dev/null +++ b/src/zeroconf/ZeroconfGlue.hxx @@ -0,0 +1,47 @@ +/* + * 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_ZEROCONF_GLUE_HXX +#define MPD_ZEROCONF_GLUE_HXX + +#include "check.h" + +class EventLoop; + +#ifdef HAVE_ZEROCONF + +void +ZeroconfInit(EventLoop &loop); + +void +ZeroconfDeinit(); + +#else /* ! HAVE_ZEROCONF */ + +static inline void +ZeroconfInit(EventLoop &) +{} + +static inline void +ZeroconfDeinit() +{} + +#endif /* ! HAVE_ZEROCONF */ + +#endif diff --git a/src/zeroconf/ZeroconfInternal.hxx b/src/zeroconf/ZeroconfInternal.hxx new file mode 100644 index 000000000..4d47d260a --- /dev/null +++ b/src/zeroconf/ZeroconfInternal.hxx @@ -0,0 +1,26 @@ +/* + * 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 ZEROCONF_INTERNAL_H +#define ZEROCONF_INTERNAL_H + +/* The dns-sd service type qualifier to publish */ +#define SERVICE_TYPE "_mpd._tcp" + +#endif |