aboutsummaryrefslogtreecommitdiffstats
path: root/src/playlist
diff options
context:
space:
mode:
Diffstat (limited to 'src/playlist')
-rw-r--r--src/playlist/AsxPlaylistPlugin.cxx286
-rw-r--r--src/playlist/MemorySongEnumerator.cxx32
-rw-r--r--src/playlist/MemorySongEnumerator.hxx38
-rw-r--r--src/playlist/PlaylistAny.cxx69
-rw-r--r--src/playlist/PlaylistAny.hxx41
-rw-r--r--src/playlist/PlaylistMapper.cxx102
-rw-r--r--src/playlist/PlaylistMapper.hxx40
-rw-r--r--src/playlist/PlaylistPlugin.hxx109
-rw-r--r--src/playlist/PlaylistQueue.cxx90
-rw-r--r--src/playlist/PlaylistQueue.hxx59
-rw-r--r--src/playlist/PlaylistRegistry.cxx309
-rw-r--r--src/playlist/PlaylistRegistry.hxx83
-rw-r--r--src/playlist/PlaylistSong.cxx131
-rw-r--r--src/playlist/PlaylistSong.hxx37
-rw-r--r--src/playlist/Print.cxx71
-rw-r--r--src/playlist/Print.hxx36
-rw-r--r--src/playlist/RssPlaylistPlugin.cxx284
-rw-r--r--src/playlist/SongEnumerator.hxx41
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.cxx186
-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.cxx185
-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 &param);
+
+ /**
+ * 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 &param)
+{
+ 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 = &empty;
+ 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 &param)
{
- 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 &param)
* @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