diff options
Diffstat (limited to 'src/playlist')
-rw-r--r-- | src/playlist/AsxPlaylistPlugin.cxx | 286 | ||||
-rw-r--r-- | src/playlist/MemorySongEnumerator.cxx | 32 | ||||
-rw-r--r-- | src/playlist/MemorySongEnumerator.hxx | 38 | ||||
-rw-r--r-- | src/playlist/PlaylistAny.cxx | 69 | ||||
-rw-r--r-- | src/playlist/PlaylistAny.hxx | 41 | ||||
-rw-r--r-- | src/playlist/PlaylistMapper.cxx | 102 | ||||
-rw-r--r-- | src/playlist/PlaylistMapper.hxx | 40 | ||||
-rw-r--r-- | src/playlist/PlaylistPlugin.hxx | 109 | ||||
-rw-r--r-- | src/playlist/PlaylistQueue.cxx | 90 | ||||
-rw-r--r-- | src/playlist/PlaylistQueue.hxx | 59 | ||||
-rw-r--r-- | src/playlist/PlaylistRegistry.cxx | 309 | ||||
-rw-r--r-- | src/playlist/PlaylistRegistry.hxx | 83 | ||||
-rw-r--r-- | src/playlist/PlaylistSong.cxx | 131 | ||||
-rw-r--r-- | src/playlist/PlaylistSong.hxx | 37 | ||||
-rw-r--r-- | src/playlist/Print.cxx | 71 | ||||
-rw-r--r-- | src/playlist/Print.hxx | 36 | ||||
-rw-r--r-- | src/playlist/RssPlaylistPlugin.cxx | 284 | ||||
-rw-r--r-- | src/playlist/SongEnumerator.hxx | 41 | ||||
-rw-r--r-- | src/playlist/plugins/AsxPlaylistPlugin.cxx | 186 | ||||
-rw-r--r-- | src/playlist/plugins/AsxPlaylistPlugin.hxx (renamed from src/playlist/AsxPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/CuePlaylistPlugin.cxx (renamed from src/playlist/CuePlaylistPlugin.cxx) | 19 | ||||
-rw-r--r-- | src/playlist/plugins/CuePlaylistPlugin.hxx (renamed from src/playlist/CuePlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/DespotifyPlaylistPlugin.cxx (renamed from src/playlist/DespotifyPlaylistPlugin.cxx) | 32 | ||||
-rw-r--r-- | src/playlist/plugins/DespotifyPlaylistPlugin.hxx (renamed from src/playlist/DespotifyPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx (renamed from src/playlist/EmbeddedCuePlaylistPlugin.cxx) | 30 | ||||
-rw-r--r-- | src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx (renamed from src/playlist/EmbeddedCuePlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/ExtM3uPlaylistPlugin.cxx (renamed from src/playlist/ExtM3uPlaylistPlugin.cxx) | 48 | ||||
-rw-r--r-- | src/playlist/plugins/ExtM3uPlaylistPlugin.hxx (renamed from src/playlist/ExtM3uPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/M3uPlaylistPlugin.cxx (renamed from src/playlist/M3uPlaylistPlugin.cxx) | 16 | ||||
-rw-r--r-- | src/playlist/plugins/M3uPlaylistPlugin.hxx (renamed from src/playlist/M3uPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/PlsPlaylistPlugin.cxx (renamed from src/playlist/PlsPlaylistPlugin.cxx) | 78 | ||||
-rw-r--r-- | src/playlist/plugins/PlsPlaylistPlugin.hxx (renamed from src/playlist/PlsPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/RssPlaylistPlugin.cxx | 185 | ||||
-rw-r--r-- | src/playlist/plugins/RssPlaylistPlugin.hxx (renamed from src/playlist/RssPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/SoundCloudPlaylistPlugin.cxx (renamed from src/playlist/SoundCloudPlaylistPlugin.cxx) | 168 | ||||
-rw-r--r-- | src/playlist/plugins/SoundCloudPlaylistPlugin.hxx (renamed from src/playlist/SoundCloudPlaylistPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/playlist/plugins/XspfPlaylistPlugin.cxx (renamed from src/playlist/XspfPlaylistPlugin.cxx) | 155 | ||||
-rw-r--r-- | src/playlist/plugins/XspfPlaylistPlugin.hxx (renamed from src/playlist/XspfPlaylistPlugin.hxx) | 2 |
38 files changed, 1882 insertions, 913 deletions
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/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..04efbb569 --- /dev/null +++ b/src/playlist/PlaylistAny.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 "PlaylistAny.hxx" +#include "PlaylistMapper.hxx" +#include "PlaylistRegistry.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "input/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::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) { + 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/playlist/PlaylistAny.hxx b/src/playlist/PlaylistAny.hxx new file mode 100644 index 000000000..c472afb31 --- /dev/null +++ b/src/playlist/PlaylistAny.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_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/playlist/PlaylistMapper.cxx b/src/playlist/PlaylistMapper.cxx new file mode 100644 index 000000000..0df0bc61f --- /dev/null +++ b/src/playlist/PlaylistMapper.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 "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/playlist/PlaylistMapper.hxx b/src/playlist/PlaylistMapper.hxx new file mode 100644 index 000000000..a460cb124 --- /dev/null +++ b/src/playlist/PlaylistMapper.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_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/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx new file mode 100644 index 000000000..d3c44f1f4 --- /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; +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/playlist/PlaylistQueue.cxx b/src/playlist/PlaylistQueue.cxx new file mode 100644 index 000000000..564c94d0a --- /dev/null +++ b/src/playlist/PlaylistQueue.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 "PlaylistQueue.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" +#include "input/InputStream.hxx" +#include "SongEnumerator.hxx" +#include "DetachedSong.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 + ? 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(), + secure)) { + delete song; + continue; + } + + PlaylistResult result = dest.AppendSong(pc, std::move(*song)); + delete song; + 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/playlist/PlaylistQueue.hxx b/src/playlist/PlaylistQueue.hxx new file mode 100644 index 000000000..075191ced --- /dev/null +++ b/src/playlist/PlaylistQueue.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. + */ + +/*! \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/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx new file mode 100644 index 000000000..55064849b --- /dev/null +++ b/src/playlist/PlaylistRegistry.cxx @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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, + &pls_playlist_plugin, +#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()); +} + +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) +{ + assert(is.ready); + + 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; +} + +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::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) + *is_r = is; + else + is->Close(); + + return playlist; +} diff --git a/src/playlist/PlaylistRegistry.hxx b/src/playlist/PlaylistRegistry.hxx new file mode 100644 index 000000000..0079fa68e --- /dev/null +++ b/src/playlist/PlaylistRegistry.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_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/playlist/PlaylistSong.cxx b/src/playlist/PlaylistSong.cxx new file mode 100644 index 000000000..69f8762ab --- /dev/null +++ b/src/playlist/PlaylistSong.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 "PlaylistSong.hxx" +#include "Mapper.hxx" +#include "db/DatabaseSong.hxx" +#include "ls.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "fs/AllocatedPath.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) +{ + { + TagBuilder builder(add.GetTag()); + builder.Complement(base.GetTag()); + add.SetTag(builder.Commit()); + } + + add.SetLastModified(base.GetLastModified()); +} + +static void +apply_song_metadata(DetachedSong &dest, const DetachedSong &src) +{ + if (!src.GetTag().IsDefined() && + src.GetStartMS() == 0 && src.GetEndMS() == 0) + return; + + merge_song_metadata(dest, src); + + if (dest.GetTag().IsDefined() && dest.GetTag().time > 0 && + src.GetStartMS() > 0 && src.GetEndMS() == 0 && + src.GetStartMS() / 1000 < (unsigned)dest.GetTag().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 */ + dest.WritableTag().time = + dest.GetTag().time - src.GetStartMS() / 1000; +} + +static bool +playlist_check_load_song(DetachedSong &song) +{ + const char *const uri = song.GetURI(); + + if (uri_has_scheme(uri)) { + return true; + } else if (PathTraitsUTF8::IsAbsolute(uri)) { + DetachedSong tmp(uri); + if (!tmp.Update()) + return false; + + apply_song_metadata(song, tmp); + return true; + } else { + DetachedSong *tmp = DatabaseDetachSong(uri, IgnoreError()); + if (tmp == nullptr) + return false; + + apply_song_metadata(song, *tmp); + delete tmp; + return true; + } +} + +bool +playlist_check_translate_song(DetachedSong &song, const char *base_uri, + bool secure) +{ + const char *const uri = song.GetURI(); + + if (uri_has_scheme(uri)) + /* valid remote song? */ + return uri_supported_scheme(uri); + + 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; + + if (PathTraitsUTF8::IsAbsolute(uri)) { + /* XXX fs_charset vs utf8? */ + const char *suffix = map_to_relative_path(uri); + assert(suffix != nullptr); + + if (suffix != uri) + song.SetURI(std::string(suffix)); + else if (!secure) + /* local files must be relative to the music + directory when "secure" is enabled */ + return false; + + base_uri = nullptr; + } + + if (base_uri != nullptr) { + song.SetURI(PathTraitsUTF8::Build(base_uri, uri)); + /* repeat the above checks */ + return playlist_check_translate_song(song, nullptr, secure); + } + + return playlist_check_load_song(song); +} diff --git a/src/playlist/PlaylistSong.hxx b/src/playlist/PlaylistSong.hxx new file mode 100644 index 000000000..2a47b28db --- /dev/null +++ b/src/playlist/PlaylistSong.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_PLAYLIST_SONG_HXX +#define MPD_PLAYLIST_SONG_HXX + +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. + * + * @param secure if true, then local files are only allowed if they + * are relative to base_uri + * @return true on success, false if the song should not be used + */ +bool +playlist_check_translate_song(DetachedSong &song, const char *base_uri, + bool secure); + +#endif diff --git a/src/playlist/Print.cxx b/src/playlist/Print.cxx new file mode 100644 index 000000000..dc5aa252c --- /dev/null +++ b/src/playlist/Print.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 "Print.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "SongEnumerator.hxx" +#include "SongPrint.hxx" +#include "input/InputStream.hxx" +#include "DetachedSong.hxx" +#include "fs/Traits.hxx" +#include "thread/Cond.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("."); + + DetachedSong *song; + while ((song = e.NextSong()) != nullptr) { + if (playlist_check_translate_song(*song, base_uri.c_str(), + false)) { + 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; + + 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/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/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/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/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx index 240c1824a..63371be0f 100644 --- a/src/playlist/AsxPlaylistPlugin.hxx +++ b/src/playlist/plugins/AsxPlaylistPlugin.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/playlist/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx index 42a43bbad..d6be4eb83 100644 --- a/src/playlist/CuePlaylistPlugin.cxx +++ b/src/playlist/plugins/CuePlaylistPlugin.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,15 +19,12 @@ #include "config.h" #include "CuePlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "tag/Tag.hxx" -#include "Song.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" #include "cue/CueParser.hxx" -#include "TextInputStream.hxx" +#include "input/TextInputStream.hxx" -#include <assert.h> -#include <string.h> +#include <string> class CuePlaylist final : public SongEnumerator { InputStream &is; @@ -39,7 +36,7 @@ class CuePlaylist final : public SongEnumerator { :is(_is), tis(is) { } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -48,10 +45,10 @@ cue_playlist_open_stream(InputStream &is) return new CuePlaylist(is); } -Song * +DetachedSong * CuePlaylist::NextSong() { - Song *song = parser.Get(); + DetachedSong *song = parser.Get(); if (song != nullptr) return song; diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx index cf5e3a8f0..4d833bfc2 100644 --- a/src/playlist/CuePlaylistPlugin.hxx +++ b/src/playlist/plugins/CuePlaylistPlugin.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/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx index a1a865c08..0d18e8b25 100644 --- a/src/playlist/DespotifyPlaylistPlugin.cxx +++ b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,11 @@ #include "config.h" #include "DespotifyPlaylistPlugin.hxx" -#include "DespotifyUtils.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" +#include "lib/despotify/DespotifyUtils.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" #include "tag/Tag.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "Log.hxx" extern "C" { @@ -34,10 +34,9 @@ extern "C" { #include <stdlib.h> static void -add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) +add_song(std::forward_list<DetachedSong> &songs, ds_track &track) { const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; - Song *song; char uri[128]; char *ds_uri; @@ -45,35 +44,32 @@ add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) snprintf(uri, sizeof(uri), "%s://", dsp_scheme); ds_uri = uri + strlen(dsp_scheme) + 3; - if (despotify_track_to_uri(track, ds_uri) != ds_uri) { + 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); + "Can't add track %s", track.title); return; } - song = Song::NewRemote(uri); - song->tag = mpd_despotify_tag_from_track(track); - - songs.emplace_front(song); + songs.emplace_front(uri, mpd_despotify_tag_from_track(track)); } static bool parse_track(struct despotify_session *session, - std::forward_list<SongPointer> &songs, + 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); + add_song(songs, *track); return true; } static bool parse_playlist(struct despotify_session *session, - std::forward_list<SongPointer> &songs, + std::forward_list<DetachedSong> &songs, struct ds_link *link) { ds_playlist *playlist = despotify_link_get_playlist(session, link); @@ -82,7 +78,7 @@ parse_playlist(struct despotify_session *session, for (ds_track *track = playlist->tracks; track != nullptr; track = track->next) - add_song(songs, track); + add_song(songs, *track); return true; } @@ -103,7 +99,7 @@ despotify_playlist_open_uri(const char *url, return nullptr; } - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; bool parse_result; switch (link->type) { diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx index c1e5b7f39..6acfd40f4 100644 --- a/src/playlist/DespotifyPlaylistPlugin.hxx +++ b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx index d758650eb..53f3feda0 100644 --- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.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,20 +25,18 @@ #include "config.h" #include "EmbeddedCuePlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "tag/Tag.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" #include "tag/TagHandler.hxx" #include "tag/TagId3.hxx" #include "tag/ApeTag.hxx" -#include "Song.hxx" +#include "DetachedSong.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 { @@ -71,7 +69,7 @@ public: delete parser; } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static void @@ -95,7 +93,7 @@ embcue_playlist_open_uri(const char *uri, gcc_unused Mutex &mutex, gcc_unused Cond &cond) { - if (!PathTraits::IsAbsoluteUTF8(uri)) + if (!PathTraitsUTF8::IsAbsolute(uri)) /* only local files supported */ return nullptr; @@ -105,7 +103,7 @@ embcue_playlist_open_uri(const char *uri, const auto playlist = new EmbeddedCuePlaylist(); - tag_file_scan(path_fs, &embcue_tag_handler, playlist); + 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()) @@ -118,7 +116,7 @@ embcue_playlist_open_uri(const char *uri, return nullptr; } - playlist->filename = PathTraits::GetBaseUTF8(uri); + playlist->filename = PathTraitsUTF8::GetBase(uri); playlist->next = &playlist->cuesheet[0]; playlist->parser = new CueParser(); @@ -126,10 +124,10 @@ embcue_playlist_open_uri(const char *uri, return playlist; } -Song * +DetachedSong * EmbeddedCuePlaylist::NextSong() { - Song *song = parser->Get(); + DetachedSong *song = parser->Get(); if (song != nullptr) return song; @@ -147,14 +145,16 @@ EmbeddedCuePlaylist::NextSong() parser->Feed(line); song = parser->Get(); - if (song != nullptr) - return song->ReplaceURI(filename.c_str()); + if (song != nullptr) { + song->SetURI(filename); + return song; + } } parser->Finish(); song = parser->Get(); if (song != nullptr) - song = song->ReplaceURI(filename.c_str()); + song->SetURI(filename); return song; } diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx index e306730f4..5eedf3f13 100644 --- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.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/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx index 8d260fec7..a337bce4c 100644 --- a/src/playlist/ExtM3uPlaylistPlugin.cxx +++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.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 "ExtM3uPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "Song.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/StringUtil.hxx" -#include "TextInputStream.hxx" - -#include <glib.h> +#include "input/TextInputStream.hxx" #include <string.h> #include <stdlib.h> @@ -45,7 +44,7 @@ public: strcmp(line.c_str(), "#EXTM3U") == 0; } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -68,18 +67,17 @@ extm3u_open_stream(InputStream &is) * * @param line the rest of the input line after the colon */ -static Tag * +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; + return Tag(); if (duration < 0) /* 0 means unknown duration */ @@ -89,38 +87,34 @@ extm3u_parse_tag(const char *line) if (*name == 0 && duration == 0) /* no information available; don't allocate a tag object */ - return NULL; + return Tag(); - tag = new Tag(); - tag->time = duration; + 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); + tag.AddItem(TAG_NAME, name); - return tag; + return tag.Commit(); } -Song * +DetachedSong * ExtM3uPlaylist::NextSong() { - Tag *tag = NULL; + Tag tag; std::string line; const char *line_s; - Song *song; do { - if (!tis.ReadLine(line)) { - delete tag; + if (!tis.ReadLine(line)) return NULL; - } - + line_s = line.c_str(); - if (g_str_has_prefix(line_s, "#EXTINF:")) { - delete tag; + if (StringStartsWith(line_s, "#EXTINF:")) { tag = extm3u_parse_tag(line_s + 8); continue; } @@ -128,9 +122,7 @@ ExtM3uPlaylist::NextSong() line_s = strchug_fast(line_s); } while (line_s[0] == '#' || *line_s == 0); - song = Song::NewRemote(line_s); - song->tag = tag; - return song; + return new DetachedSong(line_s, std::move(tag)); } static const char *const extm3u_suffixes[] = { diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx index 844fba15c..5743ded43 100644 --- a/src/playlist/ExtM3uPlaylistPlugin.hxx +++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.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/playlist/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx index 3f99bdfdf..f892f2010 100644 --- a/src/playlist/M3uPlaylistPlugin.cxx +++ b/src/playlist/plugins/M3uPlaylistPlugin.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,11 @@ #include "config.h" #include "M3uPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "SongEnumerator.hxx" -#include "Song.hxx" +#include "../PlaylistPlugin.hxx" +#include "../SongEnumerator.hxx" +#include "DetachedSong.hxx" #include "util/StringUtil.hxx" -#include "TextInputStream.hxx" +#include "input/TextInputStream.hxx" class M3uPlaylist final : public SongEnumerator { TextInputStream tis; @@ -33,7 +33,7 @@ public: :tis(is) { } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -42,7 +42,7 @@ m3u_open_stream(InputStream &is) return new M3uPlaylist(is); } -Song * +DetachedSong * M3uPlaylist::NextSong() { std::string line; @@ -56,7 +56,7 @@ M3uPlaylist::NextSong() line_s = strchug_fast(line_s); } while (line_s[0] == '#' || *line_s == 0); - return Song::NewRemote(line_s); + return new DetachedSong(line_s); } static const char *const m3u_suffixes[] = { diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx index a2058bb29..f1ad14069 100644 --- a/src/playlist/M3uPlaylistPlugin.hxx +++ b/src/playlist/plugins/M3uPlaylistPlugin.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/playlist/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx index 99be3ad35..979f59a96 100644 --- a/src/playlist/PlsPlaylistPlugin.cxx +++ b/src/playlist/plugins/PlsPlaylistPlugin.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,11 @@ #include "config.h" #include "PlsPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.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" @@ -32,13 +32,14 @@ #include <string> +#include <stdio.h> + static constexpr Domain pls_domain("pls"); static void -pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) +pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs) { gchar *value; - int length; GError *error = nullptr; int num_entries = g_key_file_get_integer(keyfile, "playlist", "NumberOfEntries", &error); @@ -57,13 +58,11 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) } } - while (num_entries > 0) { - Song *song; - + for (; num_entries > 0; --num_entries) { char key[64]; sprintf(key, "File%u", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); + char *uri = g_key_file_get_string(keyfile, "playlist", key, + &error); if(error) { FormatError(pls_domain, "Invalid PLS entry %s: '%s'", key, error->message); @@ -71,36 +70,24 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) return; } - song = Song::NewRemote(value); - g_free(value); + TagBuilder tag; 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; + nullptr); + if (value != nullptr) + tag.AddItem(TAG_TITLE, value); + 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; + int length = g_key_file_get_integer(keyfile, "playlist", key, + nullptr); + if (length > 0) + tag.SetTime(length); - songs.emplace_front(song); - num_entries--; + songs.emplace_front(uri, tag.Commit()); + g_free(uri); } } @@ -110,15 +97,12 @@ 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); + char buffer[1024]; + size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2); if (nbytes == 0) { if (error2.IsDefined()) { LogError(error2); @@ -137,12 +121,10 @@ pls_open_stream(InputStream &is) 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) { + 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); @@ -150,7 +132,7 @@ pls_open_stream(InputStream &is) return nullptr; } - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; pls_parser(keyfile, songs); g_key_file_free(keyfile); diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx index 3fafd36d0..1a3f33873 100644 --- a/src/playlist/PlsPlaylistPlugin.hxx +++ b/src/playlist/plugins/PlsPlaylistPlugin.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/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/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx index f49f7e9cf..a00a5a898 100644 --- a/src/playlist/RssPlaylistPlugin.hxx +++ b/src/playlist/plugins/RssPlaylistPlugin.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/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx index f6797b14d..8f378ac9d 100644 --- a/src/playlist/SoundCloudPlaylistPlugin.cxx +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.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,12 @@ #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 "../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" @@ -45,7 +45,8 @@ static constexpr Domain soundcloud_domain("soundcloud"); static bool soundcloud_init(const config_param ¶m) { - soundcloud_config.apikey = param.GetBlockValue("apikey", ""); + // 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 " @@ -62,19 +63,20 @@ soundcloud_init(const config_param ¶m) * @return Constructed URL. Must be freed with g_free. */ static char * -soundcloud_resolve(const char* uri) { +soundcloud_resolve(const char* uri) +{ char *u, *ru; - if (g_str_has_prefix(uri, "http://")) { + if (StringStartsWith(uri, "https://")) { u = g_strdup(uri); - } else if (g_str_has_prefix(uri, "soundcloud.com")) { - u = g_strconcat("http://", uri, nullptr); + } 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("http://soundcloud.com/", uri, nullptr); + u = g_strconcat("https://soundcloud.com/", uri, nullptr); } - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", + ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=", u, "&client_id=", soundcloud_config.apikey.c_str(), nullptr); g_free(u); @@ -105,15 +107,16 @@ struct parse_data { char* title; int got_url; /* nesting level of last stream_url */ - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; }; -static int handle_integer(void *ctx, - long +static int +handle_integer(void *ctx, + long #ifndef HAVE_YAJL1 - long + long #endif - intval) + intval) { struct parse_data *data = (struct parse_data *) ctx; @@ -128,26 +131,25 @@ static int handle_integer(void *ctx, return 1; } -static int handle_string(void *ctx, const unsigned char* stringval, +static int +handle_string(void *ctx, const unsigned char* stringval, #ifdef HAVE_YAJL1 - unsigned int + unsigned int #else - size_t + size_t #endif - stringlen) + 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); + g_free(data->title); data->title = g_strndup(s, stringlen); break; case Stream_URL: - if (data->stream_url != nullptr) - g_free(data->stream_url); + g_free(data->stream_url); data->stream_url = g_strndup(s, stringlen); data->got_url = 1; break; @@ -158,13 +160,14 @@ static int handle_string(void *ctx, const unsigned char* stringval, return 1; } -static int handle_mapkey(void *ctx, const unsigned char* stringval, +static int +handle_mapkey(void *ctx, const unsigned char* stringval, #ifdef HAVE_YAJL1 - unsigned int + unsigned int #else - size_t + size_t #endif - stringlen) + stringlen) { struct parse_data *data = (struct parse_data *) ctx; @@ -181,7 +184,8 @@ static int handle_mapkey(void *ctx, const unsigned char* stringval, return 1; } -static int handle_start_map(void *ctx) +static int +handle_start_map(void *ctx) { struct parse_data *data = (struct parse_data *) ctx; @@ -191,7 +195,8 @@ static int handle_start_map(void *ctx) return 1; } -static int handle_end_map(void *ctx) +static int +handle_end_map(void *ctx) { struct parse_data *data = (struct parse_data *) ctx; @@ -206,21 +211,16 @@ static int handle_end_map(void *ctx) /* got_url == 1, track finished, make it into a song */ data->got_url = 0; - Song *s; - char *u; + char *u = g_strconcat(data->stream_url, "?client_id=", + soundcloud_config.apikey.c_str(), nullptr); - 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; + TagBuilder tag; + tag.SetTime(data->duration / 1000); if (data->title != nullptr) - t->AddItem(TAG_NAME, data->title); - s->tag = t; + tag.AddItem(TAG_NAME, data->title); - data->songs.emplace_front(s); + data->songs.emplace_front(u, tag.Commit()); + g_free(u); return 1; } @@ -249,12 +249,9 @@ 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); + InputStream *input_stream = InputStream::OpenReady(url, mutex, cond, + error); if (input_stream == nullptr) { if (error.IsDefined()) LogError(error); @@ -262,12 +259,13 @@ soundcloud_parse_json(const char *url, yajl_handle hand, } mutex.lock(); - input_stream->WaitReady(); 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) { @@ -318,80 +316,62 @@ soundcloud_parse_json(const char *url, yajl_handle hand, * 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; - } + assert(memcmp(uri, "soundcloud://", 13) == 0); + uri += 13; char *u = nullptr; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", + 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 (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", + } 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 (strcmp(arg, "url") == 0) { + } 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); } - 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); + yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, + &data); #else - hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data); + 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); - if (data.title != nullptr) - g_free(data.title); - if (data.stream_url != nullptr) - g_free(data.stream_url); + g_free(data.title); + g_free(data.stream_url); if (ret == -1) return nullptr; diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx index 7c121328c..b355b477a 100644 --- a/src/playlist/SoundCloudPlaylistPlugin.hxx +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.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/playlist/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx index dcfab5a80..5b6010b53 100644 --- a/src/playlist/XspfPlaylistPlugin.cxx +++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,17 +19,16 @@ #include "config.h" #include "XspfPlaylistPlugin.hxx" -#include "PlaylistPlugin.hxx" -#include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" -#include "tag/Tag.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 <glib.h> - -#include <assert.h> #include <string.h> static constexpr Domain xspf_domain("xspf"); @@ -42,7 +41,7 @@ struct XspfParser { * The list of songs (in reverse order because that's faster * while adding). */ - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; /** * The current position in the XML file. @@ -57,24 +56,22 @@ struct XspfParser { * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there * is no (known) tag. */ - TagType tag; + TagType tag_type; /** - * The current song. It is allocated after the "location" - * element. + * The current song URI. It is set by the "location" element. */ - Song *song; + std::string location; + + TagBuilder tag_builder; 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) +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; @@ -94,8 +91,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context, case XspfParser::TRACKLIST: if (strcmp(element_name, "track") == 0) { parser->state = XspfParser::TRACK; - parser->song = nullptr; - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; } break; @@ -104,17 +101,17 @@ xspf_start_element(gcc_unused GMarkupParseContext *context, if (strcmp(element_name, "location") == 0) parser->state = XspfParser::LOCATION; else if (strcmp(element_name, "title") == 0) - parser->tag = TAG_TITLE; + 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 = TAG_ARTIST; + parser->tag_type = TAG_ARTIST; else if (strcmp(element_name, "annotation") == 0) - parser->tag = TAG_COMMENT; + parser->tag_type = TAG_COMMENT; else if (strcmp(element_name, "album") == 0) - parser->tag = TAG_ALBUM; + parser->tag_type = TAG_ALBUM; else if (strcmp(element_name, "trackNum") == 0) - parser->tag = TAG_TRACK; + parser->tag_type = TAG_TRACK; break; @@ -123,10 +120,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context, } } -static void -xspf_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +xspf_end_element(void *user_data, const XML_Char *element_name) { XspfParser *parser = (XspfParser *)user_data; @@ -148,12 +143,13 @@ xspf_end_element(gcc_unused GMarkupParseContext *context, case XspfParser::TRACK: if (strcmp(element_name, "track") == 0) { - if (parser->song != nullptr) - parser->songs.emplace_front(parser->song); + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); parser->state = XspfParser::TRACKLIST; } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; break; @@ -163,10 +159,8 @@ xspf_end_element(gcc_unused GMarkupParseContext *context, } } -static void -xspf_text(gcc_unused GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +xspf_char_data(void *user_data, const XML_Char *s, int len) { XspfParser *parser = (XspfParser *)user_data; @@ -177,43 +171,19 @@ xspf_text(gcc_unused GMarkupParseContext *context, 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); - } + 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: - if (parser->song == nullptr) { - char *uri = g_strndup(text, text_len); - parser->song = Song::NewRemote(uri); - g_free(uri); - } + parser->location.assign(s, len); 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 * @@ -223,58 +193,21 @@ 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); + { + 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; } } - 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; + return new MemorySongEnumerator(std::move(parser.songs)); } static const char *const xspf_suffixes[] = { diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx index fc9bbd2c6..6b08a6be6 100644 --- a/src/playlist/XspfPlaylistPlugin.hxx +++ b/src/playlist/plugins/XspfPlaylistPlugin.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 |