aboutsummaryrefslogtreecommitdiffstats
path: root/src/playlist
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/playlist/AsxPlaylistPlugin.cxx286
-rw-r--r--src/playlist/AsxPlaylistPlugin.hxx25
-rw-r--r--src/playlist/CloseSongEnumerator.cxx34
-rw-r--r--src/playlist/CloseSongEnumerator.hxx47
-rw-r--r--src/playlist/CuePlaylistPlugin.cxx91
-rw-r--r--src/playlist/CuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.cxx145
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.hxx25
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.cxx184
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.cxx158
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/M3uPlaylistPlugin.cxx84
-rw-r--r--src/playlist/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/MemorySongEnumerator.cxx32
-rw-r--r--src/playlist/MemorySongEnumerator.hxx39
-rw-r--r--src/playlist/PlaylistAny.cxx40
-rw-r--r--src/playlist/PlaylistAny.hxx40
-rw-r--r--src/playlist/PlaylistMapper.cxx97
-rw-r--r--src/playlist/PlaylistMapper.hxx41
-rw-r--r--src/playlist/PlaylistPlugin.hxx109
-rw-r--r--src/playlist/PlaylistQueue.cxx99
-rw-r--r--src/playlist/PlaylistQueue.hxx62
-rw-r--r--src/playlist/PlaylistRegistry.cxx285
-rw-r--r--src/playlist/PlaylistRegistry.hxx74
-rw-r--r--src/playlist/PlaylistSong.cxx78
-rw-r--r--src/playlist/PlaylistSong.hxx36
-rw-r--r--src/playlist/PlaylistStream.cxx98
-rw-r--r--src/playlist/PlaylistStream.hxx46
-rw-r--r--src/playlist/PlsPlaylistPlugin.cxx182
-rw-r--r--src/playlist/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/Print.cxx75
-rw-r--r--src/playlist/Print.hxx36
-rw-r--r--src/playlist/RssPlaylistPlugin.cxx284
-rw-r--r--src/playlist/RssPlaylistPlugin.hxx25
-rw-r--r--src/playlist/SongEnumerator.hxx41
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.cxx421
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.hxx25
-rw-r--r--src/playlist/XspfPlaylistPlugin.cxx301
-rw-r--r--src/playlist/XspfPlaylistPlugin.hxx25
-rw-r--r--src/playlist/cue/CueParser.cxx316
-rw-r--r--src/playlist/cue/CueParser.hxx144
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.cxx186
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.cxx88
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/DespotifyPlaylistPlugin.cxx142
-rw-r--r--src/playlist/plugins/DespotifyPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx184
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.cxx153
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.cxx83
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.cxx164
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx185
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.cxx401
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.cxx234
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.hxx25
62 files changed, 3939 insertions, 2386 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/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx
deleted file mode 100644
index 240c1824a..000000000
--- a/src/playlist/AsxPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX
-#define MPD_ASX_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin asx_playlist_plugin;
-
-#endif
diff --git a/src/playlist/CloseSongEnumerator.cxx b/src/playlist/CloseSongEnumerator.cxx
new file mode 100644
index 000000000..2dddef823
--- /dev/null
+++ b/src/playlist/CloseSongEnumerator.cxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CloseSongEnumerator.hxx"
+#include "input/InputStream.hxx"
+
+CloseSongEnumerator::~CloseSongEnumerator()
+{
+ delete other;
+ delete is;
+}
+
+DetachedSong *
+CloseSongEnumerator::NextSong()
+{
+ return other->NextSong();
+}
diff --git a/src/playlist/CloseSongEnumerator.hxx b/src/playlist/CloseSongEnumerator.hxx
new file mode 100644
index 000000000..17f015394
--- /dev/null
+++ b/src/playlist/CloseSongEnumerator.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLOSE_SONG_ENUMERATOR_HXX
+#define MPD_CLOSE_SONG_ENUMERATOR_HXX
+
+#include "SongEnumerator.hxx"
+#include "Compiler.h"
+
+class InputStream;
+
+/**
+ * A #SongEnumerator wrapper that closes an #InputStream automatically
+ * after deleting the #SongEnumerator
+ */
+class CloseSongEnumerator final : public SongEnumerator {
+ SongEnumerator *const other;
+
+ InputStream *const is;
+
+public:
+ gcc_nonnull_all
+ CloseSongEnumerator(SongEnumerator *_other, InputStream *const _is)
+ :other(_other), is(_is) {}
+
+ virtual ~CloseSongEnumerator();
+
+ virtual DetachedSong *NextSong() override;
+};
+
+#endif
diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx
deleted file mode 100644
index 42a43bbad..000000000
--- a/src/playlist/CuePlaylistPlugin.cxx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "CuePlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "Song.hxx"
-#include "cue/CueParser.hxx"
-#include "TextInputStream.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-class CuePlaylist final : public SongEnumerator {
- InputStream &is;
- TextInputStream tis;
- CueParser parser;
-
- public:
- CuePlaylist(InputStream &_is)
- :is(_is), tis(is) {
- }
-
- virtual Song *NextSong() override;
-};
-
-static SongEnumerator *
-cue_playlist_open_stream(InputStream &is)
-{
- return new CuePlaylist(is);
-}
-
-Song *
-CuePlaylist::NextSong()
-{
- Song *song = parser.Get();
- if (song != nullptr)
- return song;
-
- std::string line;
- while (tis.ReadLine(line)) {
- parser.Feed(line.c_str());
- song = parser.Get();
- if (song != nullptr)
- return song;
- }
-
- parser.Finish();
- return parser.Get();
-}
-
-static const char *const cue_playlist_suffixes[] = {
- "cue",
- nullptr
-};
-
-static const char *const cue_playlist_mime_types[] = {
- "application/x-cue",
- nullptr
-};
-
-const struct playlist_plugin cue_playlist_plugin = {
- "cue",
-
- nullptr,
- nullptr,
- nullptr,
- cue_playlist_open_stream,
-
- nullptr,
- cue_playlist_suffixes,
- cue_playlist_mime_types,
-};
diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx
deleted file mode 100644
index cf5e3a8f0..000000000
--- a/src/playlist/CuePlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX
-#define MPD_CUE_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin cue_playlist_plugin;
-
-#endif
diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx
deleted file mode 100644
index a1a865c08..000000000
--- a/src/playlist/DespotifyPlaylistPlugin.cxx
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2011-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DespotifyPlaylistPlugin.hxx"
-#include "DespotifyUtils.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "Song.hxx"
-#include "Log.hxx"
-
-extern "C" {
-#include <despotify.h>
-}
-
-#include <string.h>
-#include <stdlib.h>
-
-static void
-add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
-{
- const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
- Song *song;
- char uri[128];
- char *ds_uri;
-
- /* Create a spt://... URI for MPD */
- snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
- ds_uri = uri + strlen(dsp_scheme) + 3;
-
- if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
- /* Should never really fail, but let's be sure */
- FormatDebug(despotify_domain,
- "Can't add track %s", track->title);
- return;
- }
-
- song = Song::NewRemote(uri);
- song->tag = mpd_despotify_tag_from_track(track);
-
- songs.emplace_front(song);
-}
-
-static bool
-parse_track(struct despotify_session *session,
- std::forward_list<SongPointer> &songs,
- struct ds_link *link)
-{
- struct ds_track *track = despotify_link_get_track(session, link);
- if (track == nullptr)
- return false;
-
- add_song(songs, track);
- return true;
-}
-
-static bool
-parse_playlist(struct despotify_session *session,
- std::forward_list<SongPointer> &songs,
- struct ds_link *link)
-{
- ds_playlist *playlist = despotify_link_get_playlist(session, link);
- if (playlist == nullptr)
- return false;
-
- for (ds_track *track = playlist->tracks; track != nullptr;
- track = track->next)
- add_song(songs, track);
-
- return true;
-}
-
-static SongEnumerator *
-despotify_playlist_open_uri(const char *url,
- gcc_unused Mutex &mutex, gcc_unused Cond &cond)
-{
- despotify_session *session = mpd_despotify_get_session();
- if (session == nullptr)
- return nullptr;
-
- /* Get link without spt:// */
- ds_link *link =
- despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
- if (link == nullptr) {
- FormatDebug(despotify_domain, "Can't find %s\n", url);
- return nullptr;
- }
-
- std::forward_list<SongPointer> songs;
-
- bool parse_result;
- switch (link->type) {
- case LINK_TYPE_TRACK:
- parse_result = parse_track(session, songs, link);
- break;
- case LINK_TYPE_PLAYLIST:
- parse_result = parse_playlist(session, songs, link);
- break;
- default:
- parse_result = false;
- break;
- }
-
- despotify_free_link(link);
- if (!parse_result)
- return nullptr;
-
- songs.reverse();
- return new MemorySongEnumerator(std::move(songs));
-}
-
-static const char *const despotify_schemes[] = {
- "spt",
- nullptr
-};
-
-const struct playlist_plugin despotify_playlist_plugin = {
- "despotify",
-
- nullptr,
- nullptr,
- despotify_playlist_open_uri,
- nullptr,
-
- despotify_schemes,
- nullptr,
- nullptr,
-};
diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/DespotifyPlaylistPlugin.hxx
deleted file mode 100644
index c1e5b7f39..000000000
--- a/src/playlist/DespotifyPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
-#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin despotify_playlist_plugin;
-
-#endif
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
deleted file mode 100644
index 9ad71b8a8..000000000
--- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Playlist plugin that reads embedded cue sheets from the "CUESHEET"
- * tag of a music file.
- */
-
-#include "config.h"
-#include "EmbeddedCuePlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/TagId3.hxx"
-#include "tag/ApeTag.hxx"
-#include "Song.hxx"
-#include "TagFile.hxx"
-#include "cue/CueParser.hxx"
-#include "fs/Traits.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "util/ASCII.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-class EmbeddedCuePlaylist final : public SongEnumerator {
-public:
- /**
- * This is an override for the CUE's "FILE". An embedded CUE
- * sheet must always point to the song file it is contained
- * in.
- */
- std::string filename;
-
- /**
- * The value of the file's "CUESHEET" tag.
- */
- std::string cuesheet;
-
- /**
- * The offset of the next line within "cuesheet".
- */
- char *next;
-
- CueParser *parser;
-
-public:
- EmbeddedCuePlaylist()
- :parser(nullptr) {
- }
-
- virtual ~EmbeddedCuePlaylist() {
- delete parser;
- }
-
- virtual Song *NextSong() override;
-};
-
-static void
-embcue_tag_pair(const char *name, const char *value, void *ctx)
-{
- EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx;
-
- if (playlist->cuesheet.empty() &&
- StringEqualsCaseASCII(name, "cuesheet"))
- playlist->cuesheet = value;
-}
-
-static const struct tag_handler embcue_tag_handler = {
- nullptr,
- nullptr,
- embcue_tag_pair,
-};
-
-static SongEnumerator *
-embcue_playlist_open_uri(const char *uri,
- gcc_unused Mutex &mutex,
- gcc_unused Cond &cond)
-{
- if (!PathTraits::IsAbsoluteUTF8(uri))
- /* only local files supported */
- return nullptr;
-
- const auto path_fs = AllocatedPath::FromUTF8(uri);
- if (path_fs.IsNull())
- return nullptr;
-
- const auto playlist = new EmbeddedCuePlaylist();
-
- tag_file_scan(path_fs, &embcue_tag_handler, playlist);
- if (playlist->cuesheet.empty()) {
- tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
- if (playlist->cuesheet.empty())
- tag_id3_scan(path_fs, &embcue_tag_handler, playlist);
- }
-
- if (playlist->cuesheet.empty()) {
- /* no "CUESHEET" tag found */
- delete playlist;
- return nullptr;
- }
-
- playlist->filename = PathTraits::GetBaseUTF8(uri);
-
- playlist->next = &playlist->cuesheet[0];
- playlist->parser = new CueParser();
-
- return playlist;
-}
-
-Song *
-EmbeddedCuePlaylist::NextSong()
-{
- Song *song = parser->Get();
- if (song != nullptr)
- return song;
-
- while (*next != 0) {
- const char *line = next;
- char *eol = strpbrk(next, "\r\n");
- if (eol != nullptr) {
- /* null-terminate the line */
- *eol = 0;
- next = eol + 1;
- } else
- /* last line; put the "next" pointer to the
- end of the buffer */
- next += strlen(line);
-
- parser->Feed(line);
- song = parser->Get();
- if (song != nullptr)
- return song->ReplaceURI(filename.c_str());
- }
-
- parser->Finish();
- song = parser->Get();
- if (song != nullptr)
- song = song->ReplaceURI(filename.c_str());
- return song;
-}
-
-static const char *const embcue_playlist_suffixes[] = {
- /* a few codecs that are known to be supported; there are
- probably many more */
- "flac",
- "mp3", "mp2",
- "mp4", "mp4a", "m4b",
- "ape",
- "wv",
- "ogg", "oga",
- nullptr
-};
-
-const struct playlist_plugin embcue_playlist_plugin = {
- "embcue",
-
- nullptr,
- nullptr,
- embcue_playlist_open_uri,
- nullptr,
-
- nullptr,
- embcue_playlist_suffixes,
- nullptr,
-};
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx
deleted file mode 100644
index e306730f4..000000000
--- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
-#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin embcue_playlist_plugin;
-
-#endif
diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx
deleted file mode 100644
index 5ef010bda..000000000
--- a/src/playlist/ExtM3uPlaylistPlugin.cxx
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ExtM3uPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/StringUtil.hxx"
-#include "TextInputStream.hxx"
-
-#include <glib.h>
-
-#include <string.h>
-#include <stdlib.h>
-
-class ExtM3uPlaylist final : public SongEnumerator {
- TextInputStream tis;
-
-public:
- ExtM3uPlaylist(InputStream &is)
- :tis(is) {
- }
-
- bool CheckFirstLine() {
- std::string line;
- return tis.ReadLine(line) &&
- strcmp(line.c_str(), "#EXTM3U") == 0;
- }
-
- virtual Song *NextSong() override;
-};
-
-static SongEnumerator *
-extm3u_open_stream(InputStream &is)
-{
- ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is);
-
- if (!playlist->CheckFirstLine()) {
- /* no EXTM3U header: fall back to the plain m3u
- plugin */
- delete playlist;
- return NULL;
- }
-
- return playlist;
-}
-
-/**
- * Parse a EXTINF line.
- *
- * @param line the rest of the input line after the colon
- */
-static Tag *
-extm3u_parse_tag(const char *line)
-{
- long duration;
- char *endptr;
- const char *name;
- Tag *tag;
-
- duration = strtol(line, &endptr, 10);
- if (endptr[0] != ',')
- /* malformed line */
- return NULL;
-
- if (duration < 0)
- /* 0 means unknown duration */
- duration = 0;
-
- name = strchug_fast(endptr + 1);
- if (*name == 0 && duration == 0)
- /* no information available; don't allocate a tag
- object */
- return NULL;
-
- tag = new Tag();
- tag->time = duration;
-
- /* unfortunately, there is no real specification for the
- EXTM3U format, so we must assume that the string after the
- comma is opaque, and is just the song name*/
- if (*name != 0)
- tag->AddItem(TAG_NAME, name);
-
- return tag;
-}
-
-Song *
-ExtM3uPlaylist::NextSong()
-{
- Tag *tag = NULL;
- std::string line;
- const char *line_s;
- Song *song;
-
- do {
- if (!tis.ReadLine(line)) {
- delete tag;
- return NULL;
- }
-
- line_s = line.c_str();
-
- if (g_str_has_prefix(line_s, "#EXTINF:")) {
- delete tag;
- tag = extm3u_parse_tag(line_s + 8);
- continue;
- }
-
- line_s = strchug_fast(line_s);
- } while (line_s[0] == '#' || *line_s == 0);
-
- song = Song::NewRemote(line_s);
- song->tag = tag;
- return song;
-}
-
-static const char *const extm3u_suffixes[] = {
- "m3u",
- "m3u8",
- nullptr
-};
-
-static const char *const extm3u_mime_types[] = {
- "audio/x-mpegurl",
- NULL
-};
-
-const struct playlist_plugin extm3u_playlist_plugin = {
- "extm3u",
-
- nullptr,
- nullptr,
- nullptr,
- extm3u_open_stream,
-
- nullptr,
- extm3u_suffixes,
- extm3u_mime_types,
-};
diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx
deleted file mode 100644
index 844fba15c..000000000
--- a/src/playlist/ExtM3uPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
-#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin extm3u_playlist_plugin;
-
-#endif
diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx
deleted file mode 100644
index 8b6adc2b6..000000000
--- a/src/playlist/M3uPlaylistPlugin.cxx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "M3uPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
-#include "util/StringUtil.hxx"
-#include "TextInputStream.hxx"
-
-class M3uPlaylist final : public SongEnumerator {
- TextInputStream tis;
-
-public:
- M3uPlaylist(InputStream &is)
- :tis(is) {
- }
-
- virtual Song *NextSong() override;
-};
-
-static SongEnumerator *
-m3u_open_stream(InputStream &is)
-{
- return new M3uPlaylist(is);
-}
-
-Song *
-M3uPlaylist::NextSong()
-{
- std::string line;
- const char *line_s;
-
- do {
- if (!tis.ReadLine(line))
- return nullptr;
-
- line_s = line.c_str();
- line_s = strchug_fast(line_s);
- } while (line_s[0] == '#' || *line_s == 0);
-
- return Song::NewRemote(line_s);
-}
-
-static const char *const m3u_suffixes[] = {
- "m3u",
- "m3u8",
- nullptr
-};
-
-static const char *const m3u_mime_types[] = {
- "audio/x-mpegurl",
- nullptr
-};
-
-const struct playlist_plugin m3u_playlist_plugin = {
- "m3u",
-
- nullptr,
- nullptr,
- nullptr,
- m3u_open_stream,
-
- nullptr,
- m3u_suffixes,
- m3u_mime_types,
-};
diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/M3uPlaylistPlugin.hxx
deleted file mode 100644
index a2058bb29..000000000
--- a/src/playlist/M3uPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX
-#define MPD_M3U_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin m3u_playlist_plugin;
-
-#endif
diff --git a/src/playlist/MemorySongEnumerator.cxx b/src/playlist/MemorySongEnumerator.cxx
new file mode 100644
index 000000000..c3127c2bf
--- /dev/null
+++ b/src/playlist/MemorySongEnumerator.cxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MemorySongEnumerator.hxx"
+
+DetachedSong *
+MemorySongEnumerator::NextSong()
+{
+ if (songs.empty())
+ return nullptr;
+
+ auto result = new DetachedSong(std::move(songs.front()));
+ songs.pop_front();
+ return result;
+}
diff --git a/src/playlist/MemorySongEnumerator.hxx b/src/playlist/MemorySongEnumerator.hxx
new file mode 100644
index 000000000..d1259f011
--- /dev/null
+++ b/src/playlist/MemorySongEnumerator.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX
+#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
+
+#include "SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "Compiler.h"
+
+#include <forward_list>
+
+class MemorySongEnumerator final : public SongEnumerator {
+ std::forward_list<DetachedSong> songs;
+
+public:
+ MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs)
+ :songs(std::move(_songs)) {}
+
+ virtual DetachedSong *NextSong() override;
+};
+
+#endif
diff --git a/src/playlist/PlaylistAny.cxx b/src/playlist/PlaylistAny.cxx
new file mode 100644
index 000000000..7093fb99a
--- /dev/null
+++ b/src/playlist/PlaylistAny.cxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistAny.hxx"
+#include "PlaylistStream.hxx"
+#include "PlaylistMapper.hxx"
+#include "util/UriUtil.hxx"
+
+SongEnumerator *
+playlist_open_any(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond)
+{
+ return uri_has_scheme(uri)
+ ? playlist_open_remote(uri, mutex, cond)
+ : playlist_mapper_open(uri,
+#ifdef ENABLE_DATABASE
+ storage,
+#endif
+ mutex, cond);
+}
diff --git a/src/playlist/PlaylistAny.hxx b/src/playlist/PlaylistAny.hxx
new file mode 100644
index 000000000..23b0075b6
--- /dev/null
+++ b/src/playlist/PlaylistAny.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_ANY_HXX
+#define MPD_PLAYLIST_ANY_HXX
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class Storage;
+
+/**
+ * Opens a playlist from the specified URI, which can be either an
+ * absolute remote URI (with a scheme) or a relative path to the
+ * music orplaylist directory.
+ */
+SongEnumerator *
+playlist_open_any(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond);
+
+#endif
diff --git a/src/playlist/PlaylistMapper.cxx b/src/playlist/PlaylistMapper.cxx
new file mode 100644
index 000000000..042a39d34
--- /dev/null
+++ b/src/playlist/PlaylistMapper.cxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistMapper.hxx"
+#include "PlaylistFile.hxx"
+#include "PlaylistStream.hxx"
+#include "PlaylistRegistry.hxx"
+#include "Mapper.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "storage/StorageInterface.hxx"
+#include "util/UriUtil.hxx"
+
+#include <assert.h>
+
+/**
+ * Load a playlist from the configured playlist directory.
+ */
+static SongEnumerator *
+playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(spl_valid_name(uri));
+
+ const auto path_fs = map_spl_utf8_to_fs(uri);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ return playlist_open_path(path_fs, mutex, cond);
+}
+
+#ifdef ENABLE_DATABASE
+
+/**
+ * Load a playlist from the configured music directory.
+ */
+static SongEnumerator *
+playlist_open_in_storage(const char *uri, const Storage *storage,
+ Mutex &mutex, Cond &cond)
+{
+ assert(uri_safe_local(uri));
+
+ if (storage == nullptr)
+ return nullptr;
+
+ {
+ const auto path = storage->MapFS(uri);
+ if (!path.IsNull())
+ return playlist_open_path(path, mutex, cond);
+ }
+
+ const auto uri2 = storage->MapUTF8(uri);
+ return playlist_open_remote(uri2.c_str(), mutex, cond);
+}
+
+#endif
+
+SongEnumerator *
+playlist_mapper_open(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond)
+{
+ if (spl_valid_name(uri)) {
+ auto playlist = playlist_open_in_playlist_dir(uri,
+ mutex, cond);
+ if (playlist != nullptr)
+ return playlist;
+ }
+
+#ifdef ENABLE_DATABASE
+ if (uri_safe_local(uri)) {
+ auto playlist = playlist_open_in_storage(uri, storage,
+ mutex, cond);
+ if (playlist != nullptr)
+ return playlist;
+ }
+#endif
+
+ return nullptr;
+}
diff --git a/src/playlist/PlaylistMapper.hxx b/src/playlist/PlaylistMapper.hxx
new file mode 100644
index 000000000..29ce45083
--- /dev/null
+++ b/src/playlist/PlaylistMapper.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_MAPPER_HXX
+#define MPD_PLAYLIST_MAPPER_HXX
+
+#include "check.h"
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class Storage;
+
+/**
+ * Opens a playlist from an URI relative to the playlist or music
+ * directory.
+ */
+SongEnumerator *
+playlist_mapper_open(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond);
+
+#endif
diff --git a/src/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx
new file mode 100644
index 000000000..fd779ad8d
--- /dev/null
+++ b/src/playlist/PlaylistPlugin.hxx
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_PLUGIN_HXX
+#define MPD_PLAYLIST_PLUGIN_HXX
+
+struct config_param;
+class InputStream;
+struct Tag;
+class Mutex;
+class Cond;
+class SongEnumerator;
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or nullptr
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const config_param &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..b10a26172
--- /dev/null
+++ b/src/playlist/PlaylistQueue.cxx
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistQueue.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "queue/Playlist.hxx"
+#include "SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "fs/Traits.hxx"
+#include "util/Error.hxx"
+
+#ifdef ENABLE_DATABASE
+#include "SongLoader.hxx"
+#endif
+
+bool
+playlist_load_into_queue(const char *uri, SongEnumerator &e,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader,
+ Error &error)
+{
+ const std::string base_uri = uri != nullptr
+ ? PathTraitsUTF8::GetParent(uri)
+ : std::string(".");
+
+ DetachedSong *song;
+ for (unsigned i = 0;
+ i < end_index && (song = e.NextSong()) != nullptr;
+ ++i) {
+ if (i < start_index) {
+ /* skip songs before the start index */
+ delete song;
+ continue;
+ }
+
+ if (!playlist_check_translate_song(*song, base_uri.c_str(),
+ loader)) {
+ delete song;
+ continue;
+ }
+
+ unsigned id = dest.AppendSong(pc, std::move(*song), error);
+ delete song;
+ if (id == 0)
+ return false;
+ }
+
+ return true;
+}
+
+bool
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader,
+ Error &error)
+{
+ Mutex mutex;
+ Cond cond;
+
+ auto playlist = playlist_open_any(uri,
+#ifdef ENABLE_DATABASE
+ loader.GetStorage(),
+#endif
+ mutex, cond);
+ if (playlist == nullptr) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_LIST),
+ "No such playlist");
+ return false;
+ }
+
+ bool result =
+ playlist_load_into_queue(uri, *playlist,
+ start_index, end_index,
+ dest, pc, loader, error);
+ delete playlist;
+ return result;
+}
diff --git a/src/playlist/PlaylistQueue.hxx b/src/playlist/PlaylistQueue.hxx
new file mode 100644
index 000000000..28eb86fcc
--- /dev/null
+++ b/src/playlist/PlaylistQueue.hxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \file
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_HXX
+#define MPD_PLAYLIST_QUEUE_HXX
+
+#include "PlaylistError.hxx"
+
+class Error;
+class SongLoader;
+class SongEnumerator;
+struct playlist;
+struct PlayerControl;
+
+/**
+ * Loads the contents of a playlist and append it to the specified
+ * play queue.
+ *
+ * @param uri the URI of the playlist, used to resolve relative song
+ * URIs
+ * @param start_index the index of the first song
+ * @param end_index the index of the last song (excluding)
+ */
+bool
+playlist_load_into_queue(const char *uri, SongEnumerator &e,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader,
+ Error &error);
+
+/**
+ * Opens a playlist with a playlist plugin and append to the specified
+ * play queue.
+ */
+bool
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader, Error &error);
+
+#endif
+
diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx
new file mode 100644
index 000000000..4e9ef890e
--- /dev/null
+++ b/src/playlist/PlaylistRegistry.cxx
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistRegistry.hxx"
+#include "PlaylistPlugin.hxx"
+#include "plugins/ExtM3uPlaylistPlugin.hxx"
+#include "plugins/M3uPlaylistPlugin.hxx"
+#include "plugins/XspfPlaylistPlugin.hxx"
+#include "plugins/DespotifyPlaylistPlugin.hxx"
+#include "plugins/SoundCloudPlaylistPlugin.hxx"
+#include "plugins/PlsPlaylistPlugin.hxx"
+#include "plugins/AsxPlaylistPlugin.hxx"
+#include "plugins/RssPlaylistPlugin.hxx"
+#include "plugins/CuePlaylistPlugin.hxx"
+#include "plugins/EmbeddedCuePlaylistPlugin.hxx"
+#include "input/InputStream.hxx"
+#include "util/UriUtil.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Macros.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+#ifdef HAVE_GLIB
+ // TODO: enable without GLib
+ &pls_playlist_plugin,
+#endif
+#ifdef HAVE_EXPAT
+ &xspf_playlist_plugin,
+ &asx_playlist_plugin,
+ &rss_playlist_plugin,
+#endif
+#ifdef ENABLE_DESPOTIFY
+ &despotify_playlist_plugin,
+#endif
+#ifdef ENABLE_SOUNDCLOUD
+ &soundcloud_playlist_plugin,
+#endif
+ &cue_playlist_plugin,
+ &embcue_playlist_plugin,
+ nullptr
+};
+
+static constexpr unsigned n_playlist_plugins =
+ ARRAY_SIZE(playlist_plugins) - 1;
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[n_playlist_plugins];
+
+#define playlist_plugins_for_each_enabled(plugin) \
+ playlist_plugins_for_each(plugin) \
+ if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins])
+
+void
+playlist_list_global_init(void)
+{
+ const config_param empty;
+
+ for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ config_find_block(CONF_PLAYLIST_PLUGIN, "name",
+ plugin->name);
+ if (param == nullptr)
+ param = &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)
+{
+ SongEnumerator *playlist = nullptr;
+
+ assert(uri != nullptr);
+
+ UriSuffixBuffer suffix_buffer;
+ const char *const suffix = uri_get_suffix(uri, suffix_buffer);
+ if (suffix == nullptr)
+ return nullptr;
+
+ for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && !tried[i] &&
+ plugin->open_uri != nullptr && plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
+ if (playlist != nullptr)
+ break;
+ }
+ }
+
+ return playlist;
+}
+
+SongEnumerator *
+playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ /** this array tracks which plugins have already been tried by
+ playlist_list_open_uri_scheme() */
+ bool tried[n_playlist_plugins];
+
+ assert(uri != nullptr);
+
+ memset(tried, false, sizeof(tried));
+
+ auto playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried);
+ if (playlist == nullptr)
+ playlist = playlist_list_open_uri_suffix(uri, mutex, cond,
+ tried);
+
+ return playlist;
+}
+
+static SongEnumerator *
+playlist_list_open_stream_mime2(InputStream &is, const char *mime)
+{
+ assert(mime != nullptr);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->open_stream != nullptr &&
+ plugin->mime_types != nullptr &&
+ string_array_contains(plugin->mime_types, mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ is.Rewind(IgnoreError());
+
+ auto playlist = playlist_plugin_open_stream(plugin,
+ is);
+ if (playlist != nullptr)
+ return playlist;
+ }
+ }
+
+ return nullptr;
+}
+
+static SongEnumerator *
+playlist_list_open_stream_mime(InputStream &is, const char *full_mime)
+{
+ assert(full_mime != nullptr);
+
+ const char *semicolon = strchr(full_mime, ';');
+ if (semicolon == nullptr)
+ return playlist_list_open_stream_mime2(is, full_mime);
+
+ if (semicolon == full_mime)
+ return nullptr;
+
+ /* probe only the portion before the semicolon*/
+ const std::string mime(full_mime, semicolon);
+ return playlist_list_open_stream_mime2(is, mime.c_str());
+}
+
+SongEnumerator *
+playlist_list_open_stream_suffix(InputStream &is, const char *suffix)
+{
+ assert(suffix != nullptr);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->open_stream != nullptr &&
+ plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ is.Rewind(IgnoreError());
+
+ auto playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != nullptr)
+ return playlist;
+ }
+ }
+
+ return nullptr;
+}
+
+SongEnumerator *
+playlist_list_open_stream(InputStream &is, const char *uri)
+{
+ assert(is.IsReady());
+
+ const char *const mime = is.GetMimeType();
+ if (mime != nullptr) {
+ auto playlist = playlist_list_open_stream_mime(is, mime);
+ if (playlist != nullptr)
+ return playlist;
+ }
+
+ UriSuffixBuffer suffix_buffer;
+ const char *suffix = uri != nullptr
+ ? uri_get_suffix(uri, suffix_buffer)
+ : nullptr;
+ if (suffix != nullptr) {
+ auto playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != nullptr)
+ return playlist;
+ }
+
+ return nullptr;
+}
+
+bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != nullptr);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/playlist/PlaylistRegistry.hxx b/src/playlist/PlaylistRegistry.hxx
new file mode 100644
index 000000000..7ce559baa
--- /dev/null
+++ b/src/playlist/PlaylistRegistry.hxx
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_REGISTRY_HXX
+#define MPD_PLAYLIST_REGISTRY_HXX
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class InputStream;
+
+extern const struct playlist_plugin *const playlist_plugins[];
+
+#define playlist_plugins_for_each(plugin) \
+ for (const struct playlist_plugin *plugin, \
+ *const*playlist_plugin_iterator = &playlist_plugins[0]; \
+ (plugin = *playlist_plugin_iterator) != nullptr; \
+ ++playlist_plugin_iterator)
+
+/**
+ * Initializes all playlist plugins.
+ */
+void
+playlist_list_global_init(void);
+
+/**
+ * Deinitializes all playlist plugins.
+ */
+void
+playlist_list_global_finish(void);
+
+/**
+ * Opens a playlist by its URI.
+ */
+SongEnumerator *
+playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond);
+
+SongEnumerator *
+playlist_list_open_stream_suffix(InputStream &is, const char *suffix);
+
+/**
+ * Opens a playlist from an input stream.
+ *
+ * @param is an #input_stream object which is open and ready
+ * @param uri optional URI which was used to open the stream; may be
+ * used to select the appropriate playlist plugin
+ */
+SongEnumerator *
+playlist_list_open_stream(InputStream &is, const char *uri);
+
+/**
+ * Determines if there is a playlist plugin which can handle the
+ * specified file name suffix.
+ */
+bool
+playlist_suffix_supported(const char *suffix);
+
+#endif
diff --git a/src/playlist/PlaylistSong.cxx b/src/playlist/PlaylistSong.cxx
new file mode 100644
index 000000000..3603c1add
--- /dev/null
+++ b/src/playlist/PlaylistSong.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistSong.hxx"
+#include "SongLoader.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "fs/Traits.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "DetachedSong.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+static void
+merge_song_metadata(DetachedSong &add, const DetachedSong &base)
+{
+ if (base.GetTag().IsDefined()) {
+ TagBuilder builder(add.GetTag());
+ builder.Complement(base.GetTag());
+ add.SetTag(builder.Commit());
+ }
+
+ add.SetLastModified(base.GetLastModified());
+}
+
+static bool
+playlist_check_load_song(DetachedSong &song, const SongLoader &loader)
+{
+ DetachedSong *tmp = loader.LoadSong(song.GetURI(), IgnoreError());
+ if (tmp == nullptr)
+ return false;
+
+ song.SetURI(tmp->GetURI());
+ if (!song.HasRealURI() && tmp->HasRealURI())
+ song.SetRealURI(tmp->GetRealURI());
+
+ merge_song_metadata(song, *tmp);
+ delete tmp;
+ return true;
+}
+
+bool
+playlist_check_translate_song(DetachedSong &song, const char *base_uri,
+ const SongLoader &loader)
+{
+ if (base_uri != nullptr && strcmp(base_uri, ".") == 0)
+ /* PathTraitsUTF8::GetParent() returns "." when there
+ is no directory name in the given path; clear that
+ now, because it would break the database lookup
+ functions */
+ base_uri = nullptr;
+
+ const char *uri = song.GetURI();
+ if (base_uri != nullptr && !uri_has_scheme(uri) &&
+ !PathTraitsUTF8::IsAbsolute(uri))
+ song.SetURI(PathTraitsUTF8::Build(base_uri, uri));
+
+ return playlist_check_load_song(song, loader);
+}
diff --git a/src/playlist/PlaylistSong.hxx b/src/playlist/PlaylistSong.hxx
new file mode 100644
index 000000000..278df46a8
--- /dev/null
+++ b/src/playlist/PlaylistSong.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_SONG_HXX
+#define MPD_PLAYLIST_SONG_HXX
+
+class SongLoader;
+class DetachedSong;
+
+/**
+ * Verifies the song, returns false if it is unsafe. Translate the
+ * song to a song within the database, if it is a local file.
+ *
+ * @return true on success, false if the song should not be used
+ */
+bool
+playlist_check_translate_song(DetachedSong &song, const char *base_uri,
+ const SongLoader &loader);
+
+#endif
diff --git a/src/playlist/PlaylistStream.cxx b/src/playlist/PlaylistStream.cxx
new file mode 100644
index 000000000..074f39d66
--- /dev/null
+++ b/src/playlist/PlaylistStream.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistStream.hxx"
+#include "PlaylistRegistry.hxx"
+#include "CloseSongEnumerator.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "input/InputStream.hxx"
+#include "input/LocalOpen.hxx"
+#include "fs/Path.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+static SongEnumerator *
+playlist_open_path_suffix(Path path, Mutex &mutex, Cond &cond)
+{
+ assert(!path.IsNull());
+
+ const char *suffix = uri_get_suffix(path.c_str());
+ if (suffix == nullptr || !playlist_suffix_supported(suffix))
+ return nullptr;
+
+ Error error;
+ InputStream *is = OpenLocalInputStream(path, mutex, cond, error);
+ if (is == nullptr) {
+ LogError(error);
+ return nullptr;
+ }
+
+ auto playlist = playlist_list_open_stream_suffix(*is, suffix);
+ if (playlist != nullptr)
+ playlist = new CloseSongEnumerator(playlist, is);
+ else
+ delete is;
+
+ return playlist;
+}
+
+SongEnumerator *
+playlist_open_path(Path path, Mutex &mutex, Cond &cond)
+{
+ assert(!path.IsNull());
+
+ const std::string uri_utf8 = path.ToUTF8();
+ auto playlist = !uri_utf8.empty()
+ ? playlist_list_open_uri(uri_utf8.c_str(), mutex, cond)
+ : nullptr;
+ if (playlist == nullptr)
+ playlist = playlist_open_path_suffix(path, mutex, cond);
+
+ return playlist;
+}
+
+SongEnumerator *
+playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(uri_has_scheme(uri));
+
+ SongEnumerator *playlist = playlist_list_open_uri(uri, mutex, cond);
+ if (playlist != nullptr)
+ return playlist;
+
+ Error error;
+ InputStream *is = InputStream::OpenReady(uri, mutex, cond, error);
+ if (is == nullptr) {
+ if (error.IsDefined())
+ FormatError(error, "Failed to open %s", uri);
+
+ return nullptr;
+ }
+
+ playlist = playlist_list_open_stream(*is, uri);
+ if (playlist == nullptr) {
+ delete is;
+ return nullptr;
+ }
+
+ return new CloseSongEnumerator(playlist, is);
+}
diff --git a/src/playlist/PlaylistStream.hxx b/src/playlist/PlaylistStream.hxx
new file mode 100644
index 000000000..c07ae0b09
--- /dev/null
+++ b/src/playlist/PlaylistStream.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_STREAM_HXX
+#define MPD_PLAYLIST_STREAM_HXX
+
+#include "Compiler.h"
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class Path;
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param path the path of the playlist file
+ * @param is_r on success, an input_stream object is returned here,
+ * which must be closed after the playlist_provider object is freed
+ * @return a playlist, or nullptr on error
+ */
+gcc_nonnull_all
+SongEnumerator *
+playlist_open_path(Path path, Mutex &mutex, Cond &cond);
+
+gcc_nonnull_all
+SongEnumerator *
+playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond);
+
+#endif
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx
deleted file mode 100644
index 7b5c8824c..000000000
--- a/src/playlist/PlsPlaylistPlugin.cxx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlsPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <string>
-#include <stdio.h>
-
-static constexpr Domain pls_domain("pls");
-
-static void
-pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
-{
- gchar *value;
- int length;
- GError *error = nullptr;
- int num_entries = g_key_file_get_integer(keyfile, "playlist",
- "NumberOfEntries", &error);
- if (error) {
- FormatError(pls_domain,
- "Invalid PLS file: '%s'", error->message);
- g_error_free(error);
- error = nullptr;
-
- /* Hack to work around shoutcast failure to comform to spec */
- num_entries = g_key_file_get_integer(keyfile, "playlist",
- "numberofentries", &error);
- if (error) {
- g_error_free(error);
- error = nullptr;
- }
- }
-
- while (num_entries > 0) {
- Song *song;
-
- char key[64];
- sprintf(key, "File%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error) {
- FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
- key, error->message);
- g_error_free(error);
- return;
- }
-
- song = Song::NewRemote(value);
- g_free(value);
-
- sprintf(key, "Title%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error == nullptr && value){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->AddItem(TAG_TITLE, value);
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
- g_free(value);
-
- sprintf(key, "Length%u", num_entries);
- length = g_key_file_get_integer(keyfile, "playlist", key,
- &error);
- if(error == nullptr && length > 0){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->time = length;
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
-
- songs.emplace_front(song);
- num_entries--;
- }
-
-}
-
-static SongEnumerator *
-pls_open_stream(InputStream &is)
-{
- GError *error = nullptr;
- Error error2;
- size_t nbytes;
- char buffer[1024];
- bool success;
- GKeyFile *keyfile;
-
- std::string kf_data;
-
- do {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- kf_data.append(buffer, nbytes);
- /* Limit to 64k */
- } while (kf_data.length() < 65536);
-
- if (kf_data.empty()) {
- LogWarning(pls_domain, "KeyFile parser failed: No Data");
- return nullptr;
- }
-
- keyfile = g_key_file_new();
- success = g_key_file_load_from_data(keyfile,
- kf_data.data(), kf_data.length(),
- G_KEY_FILE_NONE, &error);
-
- if (!success) {
- FormatError(pls_domain,
- "KeyFile parser failed: %s", error->message);
- g_error_free(error);
- g_key_file_free(keyfile);
- return nullptr;
- }
-
- std::forward_list<SongPointer> songs;
- pls_parser(keyfile, songs);
- g_key_file_free(keyfile);
-
- return new MemorySongEnumerator(std::move(songs));
-}
-
-static const char *const pls_suffixes[] = {
- "pls",
- nullptr
-};
-
-static const char *const pls_mime_types[] = {
- "audio/x-scpls",
- nullptr
-};
-
-const struct playlist_plugin pls_playlist_plugin = {
- "pls",
-
- nullptr,
- nullptr,
- nullptr,
- pls_open_stream,
-
- nullptr,
- pls_suffixes,
- pls_mime_types,
-};
diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/PlsPlaylistPlugin.hxx
deleted file mode 100644
index 3fafd36d0..000000000
--- a/src/playlist/PlsPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX
-#define MPD_PLS_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin pls_playlist_plugin;
-
-#endif
diff --git a/src/playlist/Print.cxx b/src/playlist/Print.cxx
new file mode 100644
index 000000000..8f743f56d
--- /dev/null
+++ b/src/playlist/Print.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Print.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "SongEnumerator.hxx"
+#include "SongPrint.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
+#include "fs/Traits.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "client/Client.hxx"
+
+static void
+playlist_provider_print(Client &client, const char *uri,
+ SongEnumerator &e, bool detail)
+{
+ const std::string base_uri = uri != nullptr
+ ? PathTraitsUTF8::GetParent(uri)
+ : std::string(".");
+
+ const SongLoader loader(client);
+
+ DetachedSong *song;
+ while ((song = e.NextSong()) != nullptr) {
+ if (playlist_check_translate_song(*song, base_uri.c_str(),
+ loader) &&
+ detail)
+ song_print_info(client, *song);
+ else
+ /* fallback if no detail was requested or no
+ detail was available */
+ song_print_uri(client, *song);
+
+ delete song;
+ }
+}
+
+bool
+playlist_file_print(Client &client, const char *uri, bool detail)
+{
+ Mutex mutex;
+ Cond cond;
+
+ SongEnumerator *playlist = playlist_open_any(uri,
+#ifdef ENABLE_DATABASE
+ client.GetStorage(),
+#endif
+ mutex, cond);
+ if (playlist == nullptr)
+ return false;
+
+ playlist_provider_print(client, uri, *playlist, detail);
+ delete playlist;
+ return true;
+}
diff --git a/src/playlist/Print.hxx b/src/playlist/Print.hxx
new file mode 100644
index 000000000..c2fff5475
--- /dev/null
+++ b/src/playlist/Print.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST__PRINT_HXX
+#define MPD_PLAYLIST__PRINT_HXX
+
+class Client;
+
+/**
+ * Send the playlist file to the client.
+ *
+ * @param client the client which requested the playlist
+ * @param uri the URI of the playlist file in UTF-8 encoding
+ * @param detail true if all details should be printed
+ * @return true on success, false if the playlist does not exist
+ */
+bool
+playlist_file_print(Client &client, const char *uri, bool detail);
+
+#endif
diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
deleted file mode 100644
index e2a44bfd3..000000000
--- a/src/playlist/RssPlaylistPlugin.cxx
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RssPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain rss_domain("rss");
-
-/**
- * This is the state object for the GLib XML parser.
- */
-struct RssParser {
- /**
- * The list of songs (in reverse order because that's faster
- * while adding).
- */
- std::forward_list<SongPointer> songs;
-
- /**
- * The current position in the XML file.
- */
- enum {
- ROOT, ITEM,
- } state;
-
- /**
- * The current tag within the "entry" element. This is only
- * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
- * is no (known) tag.
- */
- TagType tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- Song *song;
-
- RssParser()
- :state(ROOT) {}
-};
-
-static const gchar *
-get_attribute(const gchar **attribute_names, const gchar **attribute_values,
- const gchar *name)
-{
- for (unsigned i = 0; attribute_names[i] != nullptr; ++i)
- if (StringEqualsCaseASCII(attribute_names[i], name))
- return attribute_values[i];
-
- return nullptr;
-}
-
-static void
-rss_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- if (StringEqualsCaseASCII(element_name, "item")) {
- parser->state = RssParser::ITEM;
- parser->song = Song::NewRemote("rss:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case RssParser::ITEM:
- if (StringEqualsCaseASCII(element_name, "enclosure")) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "url");
- if (href != nullptr) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- Song *song = Song::NewRemote(href);
-
- if (parser->song != nullptr) {
- song->tag = parser->song->tag;
- parser->song->tag = nullptr;
- parser->song->Free();
- }
-
- parser->song = song;
- }
- } else if (StringEqualsCaseASCII(element_name, "title"))
- parser->tag = TAG_TITLE;
- else if (StringEqualsCaseASCII(element_name, "itunes:author"))
- parser->tag = TAG_ARTIST;
-
- break;
- }
-}
-
-static void
-rss_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- break;
-
- case RssParser::ITEM:
- if (StringEqualsCaseASCII(element_name, "item")) {
- if (strcmp(parser->song->uri, "rss:") != 0)
- parser->songs.emplace_front(parser->song);
- else
- parser->song->Free();
-
- parser->state = RssParser::ROOT;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
- }
-}
-
-static void
-rss_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- break;
-
- case RssParser::ITEM:
- if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag,
- text, text_len);
- }
-
- break;
- }
-}
-
-static const GMarkupParser rss_parser = {
- rss_start_element,
- rss_end_element,
- rss_text,
- nullptr,
- nullptr,
-};
-
-static void
-rss_parser_destroy(gpointer data)
-{
- RssParser *parser = (RssParser *)data;
-
- if (parser->state >= RssParser::ITEM)
- parser->song->Free();
-}
-
-/*
- * The playlist object
- *
- */
-
-static SongEnumerator *
-rss_open_stream(InputStream &is)
-{
- RssParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the RSS XML file */
-
- context = g_markup_parse_context_new(&rss_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, rss_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
- parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
-}
-
-static const char *const rss_suffixes[] = {
- "rss",
- nullptr
-};
-
-static const char *const rss_mime_types[] = {
- "application/rss+xml",
- "text/xml",
- nullptr
-};
-
-const struct playlist_plugin rss_playlist_plugin = {
- "rss",
-
- nullptr,
- nullptr,
- nullptr,
- rss_open_stream,
-
- nullptr,
- rss_suffixes,
- rss_mime_types,
-};
diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx
deleted file mode 100644
index f49f7e9cf..000000000
--- a/src/playlist/RssPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX
-#define MPD_RSS_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin rss_playlist_plugin;
-
-#endif
diff --git a/src/playlist/SongEnumerator.hxx b/src/playlist/SongEnumerator.hxx
new file mode 100644
index 000000000..75295add1
--- /dev/null
+++ b/src/playlist/SongEnumerator.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SONG_ENUMERATOR_HXX
+#define MPD_SONG_ENUMERATOR_HXX
+
+class DetachedSong;
+
+/**
+ * An object which provides serial access to a number of #Song
+ * objects. It is used to enumerate the contents of a playlist file.
+ */
+class SongEnumerator {
+public:
+ virtual ~SongEnumerator() {}
+
+ /**
+ * Obtain the next song. The caller is responsible for
+ * freeing the returned #Song object. Returns nullptr if
+ * there are no more songs.
+ */
+ virtual DetachedSong *NextSong() = 0;
+};
+
+#endif
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx
deleted file mode 100644
index f6797b14d..000000000
--- a/src/playlist/SoundCloudPlaylistPlugin.cxx
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SoundCloudPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "ConfigData.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <yajl/yajl_parse.h>
-
-#include <string>
-
-#include <string.h>
-
-static struct {
- std::string apikey;
-} soundcloud_config;
-
-static constexpr Domain soundcloud_domain("soundcloud");
-
-static bool
-soundcloud_init(const config_param &param)
-{
- soundcloud_config.apikey = param.GetBlockValue("apikey", "");
- if (soundcloud_config.apikey.empty()) {
- LogDebug(soundcloud_domain,
- "disabling the soundcloud playlist plugin "
- "because API key is not set");
- return false;
- }
-
- return true;
-}
-
-/**
- * Construct a full soundcloud resolver URL from the given fragment.
- * @param uri uri of a soundcloud page (or just the path)
- * @return Constructed URL. Must be freed with g_free.
- */
-static char *
-soundcloud_resolve(const char* uri) {
- char *u, *ru;
-
- if (g_str_has_prefix(uri, "http://")) {
- u = g_strdup(uri);
- } else if (g_str_has_prefix(uri, "soundcloud.com")) {
- u = g_strconcat("http://", uri, nullptr);
- } else {
- /* assume it's just a path on soundcloud.com */
- u = g_strconcat("http://soundcloud.com/", uri, nullptr);
- }
-
- ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
- u, "&client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- g_free(u);
-
- return ru;
-}
-
-/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */
-
-enum key {
- Duration,
- Title,
- Stream_URL,
- Other,
-};
-
-const char* key_str[] = {
- "duration",
- "title",
- "stream_url",
- nullptr,
-};
-
-struct parse_data {
- int key;
- char* stream_url;
- long duration;
- char* title;
- int got_url; /* nesting level of last stream_url */
-
- std::forward_list<SongPointer> songs;
-};
-
-static int handle_integer(void *ctx,
- long
-#ifndef HAVE_YAJL1
- long
-#endif
- intval)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- switch (data->key) {
- case Duration:
- data->duration = intval;
- break;
- default:
- break;
- }
-
- return 1;
-}
-
-static int handle_string(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
-{
- struct parse_data *data = (struct parse_data *) ctx;
- const char *s = (const char *) stringval;
-
- switch (data->key) {
- case Title:
- if (data->title != nullptr)
- g_free(data->title);
- data->title = g_strndup(s, stringlen);
- break;
- case Stream_URL:
- if (data->stream_url != nullptr)
- g_free(data->stream_url);
- data->stream_url = g_strndup(s, stringlen);
- data->got_url = 1;
- break;
- default:
- break;
- }
-
- return 1;
-}
-
-static int handle_mapkey(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- int i;
- data->key = Other;
-
- for (i = 0; i < Other; ++i) {
- if (memcmp((const char *)stringval, key_str[i], stringlen) == 0) {
- data->key = i;
- break;
- }
- }
-
- return 1;
-}
-
-static int handle_start_map(void *ctx)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- if (data->got_url > 0)
- data->got_url++;
-
- return 1;
-}
-
-static int handle_end_map(void *ctx)
-{
- struct parse_data *data = (struct parse_data *) ctx;
-
- if (data->got_url > 1) {
- data->got_url--;
- return 1;
- }
-
- if (data->got_url == 0)
- return 1;
-
- /* got_url == 1, track finished, make it into a song */
- data->got_url = 0;
-
- Song *s;
- char *u;
-
- u = g_strconcat(data->stream_url, "?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- s = Song::NewRemote(u);
- g_free(u);
-
- Tag *t = new Tag();
- t->time = data->duration / 1000;
- if (data->title != nullptr)
- t->AddItem(TAG_NAME, data->title);
- s->tag = t;
-
- data->songs.emplace_front(s);
-
- return 1;
-}
-
-static yajl_callbacks parse_callbacks = {
- nullptr,
- nullptr,
- handle_integer,
- nullptr,
- nullptr,
- handle_string,
- handle_start_map,
- handle_mapkey,
- handle_end_map,
- nullptr,
- nullptr,
-};
-
-/**
- * Read JSON data and parse it using the given YAJL parser.
- * @param url URL of the JSON data.
- * @param hand YAJL parser handle.
- * @return -1 on error, 0 on success.
- */
-static int
-soundcloud_parse_json(const char *url, yajl_handle hand,
- Mutex &mutex, Cond &cond)
-{
- char buffer[4096];
- unsigned char *ubuffer = (unsigned char *)buffer;
-
- Error error;
- InputStream *input_stream = InputStream::Open(url, mutex, cond,
- error);
- if (input_stream == nullptr) {
- if (error.IsDefined())
- LogError(error);
- return -1;
- }
-
- mutex.lock();
- input_stream->WaitReady();
-
- yajl_status stat;
- int done = 0;
-
- while (!done) {
- const size_t nbytes =
- input_stream->Read(buffer, sizeof(buffer), error);
- if (nbytes == 0) {
- if (error.IsDefined())
- LogError(error);
-
- if (input_stream->IsEOF()) {
- done = true;
- } else {
- mutex.unlock();
- input_stream->Close();
- return -1;
- }
- }
-
- if (done) {
-#ifdef HAVE_YAJL1
- stat = yajl_parse_complete(hand);
-#else
- stat = yajl_complete_parse(hand);
-#endif
- } else
- stat = yajl_parse(hand, ubuffer, nbytes);
-
- if (stat != yajl_status_ok
-#ifdef HAVE_YAJL1
- && stat != yajl_status_insufficient_data
-#endif
- )
- {
- unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
- LogError(soundcloud_domain, (const char *)str);
- yajl_free_error(hand, str);
- break;
- }
- }
-
- mutex.unlock();
- input_stream->Close();
-
- return 0;
-}
-
-/**
- * Parse a soundcloud:// URL and create a playlist.
- * @param uri A soundcloud URL. Accepted forms:
- * soundcloud://track/<track-id>
- * soundcloud://playlist/<playlist-id>
- * soundcloud://url/<url or path of soundcloud page>
- */
-
-static SongEnumerator *
-soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
-{
- char *s, *p;
- char *scheme, *arg, *rest;
- s = g_strdup(uri);
- scheme = s;
- for (p = s; *p; p++) {
- if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') {
- *p = 0;
- p += 3;
- break;
- }
- }
- arg = p;
- for (; *p; p++) {
- if (*p == '/') {
- *p = 0;
- p++;
- break;
- }
- }
- rest = p;
-
- if (strcmp(scheme, "soundcloud") != 0) {
- FormatWarning(soundcloud_domain,
- "incompatible scheme for soundcloud plugin: %s",
- scheme);
- g_free(s);
- return nullptr;
- }
-
- char *u = nullptr;
- if (strcmp(arg, "track") == 0) {
- u = g_strconcat("http://api.soundcloud.com/tracks/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- } else if (strcmp(arg, "playlist") == 0) {
- u = g_strconcat("http://api.soundcloud.com/playlists/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- } else if (strcmp(arg, "url") == 0) {
- /* Translate to soundcloud resolver call. libcurl will automatically
- follow the redirect to the right resource. */
- u = soundcloud_resolve(rest);
- }
- g_free(s);
-
- if (u == nullptr) {
- LogWarning(soundcloud_domain, "unknown soundcloud URI");
- return nullptr;
- }
-
- yajl_handle hand;
- struct parse_data data;
-
- data.got_url = 0;
- data.title = nullptr;
- data.stream_url = nullptr;
-#ifdef HAVE_YAJL1
- hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, (void *) &data);
-#else
- hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data);
-#endif
-
- int ret = soundcloud_parse_json(u, hand, mutex, cond);
-
- g_free(u);
- yajl_free(hand);
- if (data.title != nullptr)
- g_free(data.title);
- if (data.stream_url != nullptr)
- g_free(data.stream_url);
-
- if (ret == -1)
- return nullptr;
-
- data.songs.reverse();
- return new MemorySongEnumerator(std::move(data.songs));
-}
-
-static const char *const soundcloud_schemes[] = {
- "soundcloud",
- nullptr
-};
-
-const struct playlist_plugin soundcloud_playlist_plugin = {
- "soundcloud",
-
- soundcloud_init,
- nullptr,
- soundcloud_open_uri,
- nullptr,
-
- soundcloud_schemes,
- nullptr,
- nullptr,
-};
-
-
diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx
deleted file mode 100644
index 7c121328c..000000000
--- a/src/playlist/SoundCloudPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
-#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin soundcloud_playlist_plugin;
-
-#endif
diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx
deleted file mode 100644
index dcfab5a80..000000000
--- a/src/playlist/XspfPlaylistPlugin.cxx
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "XspfPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "tag/Tag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain xspf_domain("xspf");
-
-/**
- * This is the state object for the GLib XML parser.
- */
-struct XspfParser {
- /**
- * The list of songs (in reverse order because that's faster
- * while adding).
- */
- std::forward_list<SongPointer> songs;
-
- /**
- * The current position in the XML file.
- */
- enum {
- ROOT, PLAYLIST, TRACKLIST, TRACK,
- LOCATION,
- } state;
-
- /**
- * The current tag within the "track" element. This is only
- * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
- * is no (known) tag.
- */
- TagType tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- Song *song;
-
- XspfParser()
- :state(ROOT) {}
-};
-
-static void
-xspf_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gcc_unused const gchar **attribute_names,
- gcc_unused const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
-{
- XspfParser *parser = (XspfParser *)user_data;
-
- switch (parser->state) {
- case XspfParser::ROOT:
- if (strcmp(element_name, "playlist") == 0)
- parser->state = XspfParser::PLAYLIST;
-
- break;
-
- case XspfParser::PLAYLIST:
- if (strcmp(element_name, "trackList") == 0)
- parser->state = XspfParser::TRACKLIST;
-
- break;
-
- case XspfParser::TRACKLIST:
- if (strcmp(element_name, "track") == 0) {
- parser->state = XspfParser::TRACK;
- parser->song = nullptr;
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case XspfParser::TRACK:
- if (strcmp(element_name, "location") == 0)
- parser->state = XspfParser::LOCATION;
- else if (strcmp(element_name, "title") == 0)
- parser->tag = TAG_TITLE;
- else if (strcmp(element_name, "creator") == 0)
- /* TAG_COMPOSER would be more correct
- according to the XSPF spec */
- parser->tag = TAG_ARTIST;
- else if (strcmp(element_name, "annotation") == 0)
- parser->tag = TAG_COMMENT;
- else if (strcmp(element_name, "album") == 0)
- parser->tag = TAG_ALBUM;
- else if (strcmp(element_name, "trackNum") == 0)
- parser->tag = TAG_TRACK;
-
- break;
-
- case XspfParser::LOCATION:
- break;
- }
-}
-
-static void
-xspf_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
-{
- XspfParser *parser = (XspfParser *)user_data;
-
- switch (parser->state) {
- case XspfParser::ROOT:
- break;
-
- case XspfParser::PLAYLIST:
- if (strcmp(element_name, "playlist") == 0)
- parser->state = XspfParser::ROOT;
-
- break;
-
- case XspfParser::TRACKLIST:
- if (strcmp(element_name, "tracklist") == 0)
- parser->state = XspfParser::PLAYLIST;
-
- break;
-
- case XspfParser::TRACK:
- if (strcmp(element_name, "track") == 0) {
- if (parser->song != nullptr)
- parser->songs.emplace_front(parser->song);
-
- parser->state = XspfParser::TRACKLIST;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
-
- case XspfParser::LOCATION:
- parser->state = XspfParser::TRACK;
- break;
- }
-}
-
-static void
-xspf_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
-{
- XspfParser *parser = (XspfParser *)user_data;
-
- switch (parser->state) {
- case XspfParser::ROOT:
- case XspfParser::PLAYLIST:
- case XspfParser::TRACKLIST:
- break;
-
- case XspfParser::TRACK:
- if (parser->song != nullptr &&
- parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag, text, text_len);
- }
-
- break;
-
- case XspfParser::LOCATION:
- if (parser->song == nullptr) {
- char *uri = g_strndup(text, text_len);
- parser->song = Song::NewRemote(uri);
- g_free(uri);
- }
-
- break;
- }
-}
-
-static const GMarkupParser xspf_parser = {
- xspf_start_element,
- xspf_end_element,
- xspf_text,
- nullptr,
- nullptr,
-};
-
-static void
-xspf_parser_destroy(gpointer data)
-{
- XspfParser *parser = (XspfParser *)data;
-
- if (parser->state >= XspfParser::TRACK && parser->song != nullptr)
- parser->song->Free();
-}
-
-/*
- * The playlist object
- *
- */
-
-static SongEnumerator *
-xspf_open_stream(InputStream &is)
-{
- XspfParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the XSPF XML file */
-
- context = g_markup_parse_context_new(&xspf_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, xspf_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
- parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
-}
-
-static const char *const xspf_suffixes[] = {
- "xspf",
- nullptr
-};
-
-static const char *const xspf_mime_types[] = {
- "application/xspf+xml",
- nullptr
-};
-
-const struct playlist_plugin xspf_playlist_plugin = {
- "xspf",
-
- nullptr,
- nullptr,
- nullptr,
- xspf_open_stream,
-
- nullptr,
- xspf_suffixes,
- xspf_mime_types,
-};
diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/XspfPlaylistPlugin.hxx
deleted file mode 100644
index fc9bbd2c6..000000000
--- a/src/playlist/XspfPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX
-#define MPD_XSPF_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin xspf_playlist_plugin;
-
-#endif
diff --git a/src/playlist/cue/CueParser.cxx b/src/playlist/cue/CueParser.cxx
new file mode 100644
index 000000000..372c90b78
--- /dev/null
+++ b/src/playlist/cue/CueParser.cxx
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CueParser.hxx"
+#include "util/Alloc.hxx"
+#include "util/StringUtil.hxx"
+#include "util/CharUtil.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+CueParser::CueParser()
+ :state(HEADER),
+ current(nullptr),
+ previous(nullptr),
+ finished(nullptr),
+ end(false) {}
+
+CueParser::~CueParser()
+{
+ delete current;
+ delete previous;
+ delete finished;
+}
+
+static const char *
+cue_next_word(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(!IsWhitespaceNotNull(*p));
+
+ const char *word = p;
+ while (!IsWhitespaceOrNull(*p))
+ ++p;
+
+ *p = 0;
+ *pp = p + 1;
+ return word;
+}
+
+static const char *
+cue_next_quoted(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(p[-1] == '"');
+
+ char *end = strchr(p, '"');
+ if (end == nullptr) {
+ /* syntax error - ignore it silently */
+ *pp = p + strlen(p);
+ return p;
+ }
+
+ *end = 0;
+ *pp = end + 1;
+
+ return p;
+}
+
+static const char *
+cue_next_token(char **pp)
+{
+ char *p = StripLeft(*pp);
+ if (*p == 0)
+ return nullptr;
+
+ return cue_next_word(p, pp);
+}
+
+static const char *
+cue_next_value(char **pp)
+{
+ char *p = StripLeft(*pp);
+ if (*p == 0)
+ return nullptr;
+
+ if (*p == '"')
+ return cue_next_quoted(p + 1, pp);
+ else
+ return cue_next_word(p, pp);
+}
+
+static void
+cue_add_tag(TagBuilder &tag, TagType type, char *p)
+{
+ const char *value = cue_next_value(&p);
+ if (value != nullptr)
+ tag.AddItem(type, value);
+
+}
+
+static void
+cue_parse_rem(char *p, TagBuilder &tag)
+{
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ TagType type2 = tag_name_parse_i(type);
+ if (type2 != TAG_NUM_OF_ITEM_TYPES)
+ cue_add_tag(tag, type2, p);
+}
+
+TagBuilder *
+CueParser::GetCurrentTag()
+{
+ if (state == HEADER)
+ return &header_tag;
+ else if (state == TRACK)
+ return &song_tag;
+ else
+ return nullptr;
+}
+
+static int
+cue_parse_position(const char *p)
+{
+ char *endptr;
+ unsigned long minutes = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != ':')
+ return -1;
+
+ p = endptr + 1;
+ unsigned long seconds = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != ':')
+ return -1;
+
+ p = endptr + 1;
+ unsigned long frames = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != 0)
+ return -1;
+
+ return minutes * 60000 + seconds * 1000 + frames * 1000 / 75;
+}
+
+void
+CueParser::Commit()
+{
+ /* the caller of this library must call cue_parser_get() often
+ enough */
+ assert(finished == nullptr);
+ assert(!end);
+
+ if (current == nullptr)
+ return;
+
+ assert(!current->GetTag().IsDefined());
+ current->SetTag(song_tag.Commit());
+
+ finished = previous;
+ previous = current;
+ current = nullptr;
+}
+
+void
+CueParser::Feed2(char *p)
+{
+ assert(!end);
+ assert(p != nullptr);
+
+ const char *command = cue_next_token(&p);
+ if (command == nullptr)
+ return;
+
+ if (strcmp(command, "REM") == 0) {
+ TagBuilder *tag = GetCurrentTag();
+ if (tag != nullptr)
+ cue_parse_rem(p, *tag);
+ } else if (strcmp(command, "PERFORMER") == 0) {
+ /* MPD knows a "performer" tag, but it is not a good
+ match for this CUE tag; from the Hydrogenaudio
+ Knowledgebase: "At top-level this will specify the
+ CD artist, while at track-level it specifies the
+ track artist." */
+
+ TagType type = state == TRACK
+ ? TAG_ARTIST
+ : TAG_ALBUM_ARTIST;
+
+ TagBuilder *tag = GetCurrentTag();
+ if (tag != nullptr)
+ cue_add_tag(*tag, type, p);
+ } else if (strcmp(command, "TITLE") == 0) {
+ if (state == HEADER)
+ cue_add_tag(header_tag, TAG_ALBUM, p);
+ else if (state == TRACK)
+ cue_add_tag(song_tag, TAG_TITLE, p);
+ } else if (strcmp(command, "FILE") == 0) {
+ Commit();
+
+ const char *new_filename = cue_next_value(&p);
+ if (new_filename == nullptr)
+ return;
+
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ if (strcmp(type, "WAVE") != 0 &&
+ strcmp(type, "MP3") != 0 &&
+ strcmp(type, "AIFF") != 0) {
+ state = IGNORE_FILE;
+ return;
+ }
+
+ state = WAVE;
+ filename = new_filename;
+ } else if (state == IGNORE_FILE) {
+ return;
+ } else if (strcmp(command, "TRACK") == 0) {
+ Commit();
+
+ const char *nr = cue_next_token(&p);
+ if (nr == nullptr)
+ return;
+
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ if (strcmp(type, "AUDIO") != 0) {
+ state = IGNORE_TRACK;
+ return;
+ }
+
+ state = TRACK;
+ current = new DetachedSong(filename);
+ assert(!current->GetTag().IsDefined());
+
+ song_tag = header_tag;
+ song_tag.AddItem(TAG_TRACK, nr);
+
+ last_updated = false;
+ } else if (state == IGNORE_TRACK) {
+ return;
+ } else if (state == TRACK && strcmp(command, "INDEX") == 0) {
+ const char *nr = cue_next_token(&p);
+ if (nr == nullptr)
+ return;
+
+ const char *position = cue_next_token(&p);
+ if (position == nullptr)
+ return;
+
+ int position_ms = cue_parse_position(position);
+ if (position_ms < 0)
+ return;
+
+ if (!last_updated && previous != nullptr &&
+ previous->GetStartTime().ToMS() < (unsigned)position_ms) {
+ last_updated = true;
+ previous->SetEndTime(SongTime::FromMS(position_ms));
+ }
+
+ current->SetStartTime(SongTime::FromMS(position_ms));
+ }
+}
+
+void
+CueParser::Feed(const char *line)
+{
+ assert(!end);
+ assert(line != nullptr);
+
+ char *allocated = xstrdup(line);
+ Feed2(allocated);
+ free(allocated);
+}
+
+void
+CueParser::Finish()
+{
+ if (end)
+ /* has already been called, ignore */
+ return;
+
+ Commit();
+ end = true;
+}
+
+DetachedSong *
+CueParser::Get()
+{
+ if (finished == nullptr && end) {
+ /* cue_parser_finish() has been called already:
+ deliver all remaining (partial) results */
+ assert(current == nullptr);
+
+ finished = previous;
+ previous = nullptr;
+ }
+
+ DetachedSong *song = finished;
+ finished = nullptr;
+ return song;
+}
diff --git a/src/playlist/cue/CueParser.hxx b/src/playlist/cue/CueParser.hxx
new file mode 100644
index 000000000..7e040169b
--- /dev/null
+++ b/src/playlist/cue/CueParser.hxx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CUE_PARSER_HXX
+#define MPD_CUE_PARSER_HXX
+
+#include "check.h"
+#include "tag/TagBuilder.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+class DetachedSong;
+struct Tag;
+
+class CueParser {
+ enum {
+ /**
+ * Parsing the CUE header.
+ */
+ HEADER,
+
+ /**
+ * Parsing a "FILE ... WAVE".
+ */
+ WAVE,
+
+ /**
+ * Ignore everything until the next "FILE".
+ */
+ IGNORE_FILE,
+
+ /**
+ * Parsing a "TRACK ... AUDIO".
+ */
+ TRACK,
+
+ /**
+ * Ignore everything until the next "TRACK".
+ */
+ IGNORE_TRACK,
+ } state;
+
+ /**
+ * Tags read from the CUE header.
+ */
+ TagBuilder header_tag;
+
+ /**
+ * Tags read for the current song (attribute #current). When
+ * #current gets moved to #previous, TagBuilder::Commit() will
+ * be called.
+ */
+ TagBuilder song_tag;
+
+ std::string filename;
+
+ /**
+ * The song currently being edited.
+ */
+ DetachedSong *current;
+
+ /**
+ * The previous song. It is remembered because its end_time
+ * will be set to the current song's start time.
+ */
+ DetachedSong *previous;
+
+ /**
+ * A song that is completely finished and can be returned to
+ * the caller via cue_parser_get().
+ */
+ DetachedSong *finished;
+
+ /**
+ * Set to true after previous.end_time has been updated to the
+ * start time of the current song.
+ */
+ bool last_updated;
+
+ /**
+ * Tracks whether cue_parser_finish() has been called. If
+ * true, then all remaining (partial) results will be
+ * delivered by cue_parser_get().
+ */
+ bool end;
+
+public:
+ CueParser();
+ ~CueParser();
+
+ /**
+ * Feed a text line from the CUE file into the parser. Call
+ * cue_parser_get() after this to see if a song has been finished.
+ */
+ void Feed(const char *line);
+
+ /**
+ * Tell the parser that the end of the file has been reached. Call
+ * cue_parser_get() after this to see if a song has been finished.
+ * This procedure must be done twice!
+ */
+ void Finish();
+
+ /**
+ * Check if a song was finished by the last cue_parser_feed() or
+ * cue_parser_finish() call.
+ *
+ * @return a song object that must be freed by the caller, or NULL if
+ * no song was finished at this time
+ */
+ DetachedSong *Get();
+
+private:
+ gcc_pure
+ TagBuilder *GetCurrentTag();
+
+ /**
+ * Commit the current song. It will be moved to "previous",
+ * so the next song may soon edit its end time (using the next
+ * song's start time).
+ */
+ void Commit();
+
+ void Feed2(char *p);
+};
+
+#endif
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx
new file mode 100644
index 000000000..3185a8144
--- /dev/null
+++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AsxPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct AsxParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "ref" element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ AsxParser()
+ :state(ROOT) {}
+
+};
+
+static void XMLCALL
+asx_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "entry")) {
+ parser->state = AsxParser::ENTRY;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case AsxParser::ENTRY:
+ if (StringEqualsCaseASCII(element_name, "ref")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "href");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "author"))
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag_type = TAG_ARTIST;
+ else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void XMLCALL
+asx_end_element(void *user_data, const XML_Char *element_name)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (StringEqualsCaseASCII(element_name, "entry")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = AsxParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+asx_char_data(void *user_data, const XML_Char *s, int len)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+asx_open_stream(InputStream &is)
+{
+ AsxParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(asx_start_element, asx_end_element);
+ expat.SetCharacterDataHandler(asx_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ nullptr
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ nullptr
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ "asx",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ asx_open_stream,
+
+ nullptr,
+ asx_suffixes,
+ asx_mime_types,
+};
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx
new file mode 100644
index 000000000..63371be0f
--- /dev/null
+++ b/src/playlist/plugins/AsxPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX
+#define MPD_ASX_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx
new file mode 100644
index 000000000..b907d34d0
--- /dev/null
+++ b/src/playlist/plugins/CuePlaylistPlugin.cxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CuePlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "input/TextInputStream.hxx"
+
+#include <string>
+
+class CuePlaylist final : public SongEnumerator {
+ InputStream &is;
+ TextInputStream tis;
+ CueParser parser;
+
+ public:
+ CuePlaylist(InputStream &_is)
+ :is(_is), tis(is) {
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+cue_playlist_open_stream(InputStream &is)
+{
+ return new CuePlaylist(is);
+}
+
+DetachedSong *
+CuePlaylist::NextSong()
+{
+ DetachedSong *song = parser.Get();
+ if (song != nullptr)
+ return song;
+
+ const char *line;
+ while ((line = tis.ReadLine()) != nullptr) {
+ parser.Feed(line);
+ song = parser.Get();
+ if (song != nullptr)
+ return song;
+ }
+
+ parser.Finish();
+ return parser.Get();
+}
+
+static const char *const cue_playlist_suffixes[] = {
+ "cue",
+ nullptr
+};
+
+static const char *const cue_playlist_mime_types[] = {
+ "application/x-cue",
+ nullptr
+};
+
+const struct playlist_plugin cue_playlist_plugin = {
+ "cue",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ cue_playlist_open_stream,
+
+ nullptr,
+ cue_playlist_suffixes,
+ cue_playlist_mime_types,
+};
diff --git a/src/playlist/plugins/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx
new file mode 100644
index 000000000..4d833bfc2
--- /dev/null
+++ b/src/playlist/plugins/CuePlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX
+#define MPD_CUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin cue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/DespotifyPlaylistPlugin.cxx b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx
new file mode 100644
index 000000000..636f64bc6
--- /dev/null
+++ b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DespotifyPlaylistPlugin.hxx"
+#include "lib/despotify/DespotifyUtils.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/Tag.hxx"
+#include "DetachedSong.hxx"
+#include "Log.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <string.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void
+add_song(std::forward_list<DetachedSong> &songs, ds_track &track)
+{
+ const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
+ char uri[128];
+ char *ds_uri;
+
+ /* Create a spt://... URI for MPD */
+ snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
+ ds_uri = uri + strlen(dsp_scheme) + 3;
+
+ if (despotify_track_to_uri(&track, ds_uri) != ds_uri) {
+ /* Should never really fail, but let's be sure */
+ FormatDebug(despotify_domain,
+ "Can't add track %s", track.title);
+ return;
+ }
+
+ songs.emplace_front(uri, mpd_despotify_tag_from_track(track));
+}
+
+static bool
+parse_track(struct despotify_session *session,
+ std::forward_list<DetachedSong> &songs,
+ struct ds_link *link)
+{
+ struct ds_track *track = despotify_link_get_track(session, link);
+ if (track == nullptr)
+ return false;
+
+ add_song(songs, *track);
+ return true;
+}
+
+static bool
+parse_playlist(struct despotify_session *session,
+ std::forward_list<DetachedSong> &songs,
+ struct ds_link *link)
+{
+ ds_playlist *playlist = despotify_link_get_playlist(session, link);
+ if (playlist == nullptr)
+ return false;
+
+ for (ds_track *track = playlist->tracks; track != nullptr;
+ track = track->next)
+ add_song(songs, *track);
+
+ return true;
+}
+
+static SongEnumerator *
+despotify_playlist_open_uri(const char *url,
+ gcc_unused Mutex &mutex, gcc_unused Cond &cond)
+{
+ despotify_session *session = mpd_despotify_get_session();
+ if (session == nullptr)
+ return nullptr;
+
+ /* Get link without spt:// */
+ ds_link *link =
+ despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
+ if (link == nullptr) {
+ FormatDebug(despotify_domain, "Can't find %s\n", url);
+ return nullptr;
+ }
+
+ std::forward_list<DetachedSong> songs;
+
+ bool parse_result;
+ switch (link->type) {
+ case LINK_TYPE_TRACK:
+ parse_result = parse_track(session, songs, link);
+ break;
+ case LINK_TYPE_PLAYLIST:
+ parse_result = parse_playlist(session, songs, link);
+ break;
+ default:
+ parse_result = false;
+ break;
+ }
+
+ despotify_free_link(link);
+ if (!parse_result)
+ return nullptr;
+
+ songs.reverse();
+ return new MemorySongEnumerator(std::move(songs));
+}
+
+static const char *const despotify_schemes[] = {
+ "spt",
+ nullptr
+};
+
+const struct playlist_plugin despotify_playlist_plugin = {
+ "despotify",
+
+ nullptr,
+ nullptr,
+ despotify_playlist_open_uri,
+ nullptr,
+
+ despotify_schemes,
+ nullptr,
+ nullptr,
+};
diff --git a/src/playlist/plugins/DespotifyPlaylistPlugin.hxx b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx
new file mode 100644
index 000000000..6acfd40f4
--- /dev/null
+++ b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
+#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin despotify_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
new file mode 100644
index 000000000..8baa11c03
--- /dev/null
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Playlist plugin that reads embedded cue sheets from the "CUESHEET"
+ * tag of a music file.
+ */
+
+#include "config.h"
+#include "EmbeddedCuePlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagId3.hxx"
+#include "tag/ApeTag.hxx"
+#include "DetachedSong.hxx"
+#include "TagFile.hxx"
+#include "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/ASCII.hxx"
+
+#include <string.h>
+
+class EmbeddedCuePlaylist final : public SongEnumerator {
+public:
+ /**
+ * This is an override for the CUE's "FILE". An embedded CUE
+ * sheet must always point to the song file it is contained
+ * in.
+ */
+ std::string filename;
+
+ /**
+ * The value of the file's "CUESHEET" tag.
+ */
+ std::string cuesheet;
+
+ /**
+ * The offset of the next line within "cuesheet".
+ */
+ char *next;
+
+ CueParser *parser;
+
+public:
+ EmbeddedCuePlaylist()
+ :parser(nullptr) {
+ }
+
+ virtual ~EmbeddedCuePlaylist() {
+ delete parser;
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static void
+embcue_tag_pair(const char *name, const char *value, void *ctx)
+{
+ EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx;
+
+ if (playlist->cuesheet.empty() &&
+ StringEqualsCaseASCII(name, "cuesheet"))
+ playlist->cuesheet = value;
+}
+
+static const struct tag_handler embcue_tag_handler = {
+ nullptr,
+ nullptr,
+ embcue_tag_pair,
+};
+
+static SongEnumerator *
+embcue_playlist_open_uri(const char *uri,
+ gcc_unused Mutex &mutex,
+ gcc_unused Cond &cond)
+{
+ if (!PathTraitsUTF8::IsAbsolute(uri))
+ /* only local files supported */
+ return nullptr;
+
+ const auto path_fs = AllocatedPath::FromUTF8(uri);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ const auto playlist = new EmbeddedCuePlaylist();
+
+ tag_file_scan(path_fs, embcue_tag_handler, playlist);
+ if (playlist->cuesheet.empty()) {
+ tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet.empty())
+ tag_id3_scan(path_fs, &embcue_tag_handler, playlist);
+ }
+
+ if (playlist->cuesheet.empty()) {
+ /* no "CUESHEET" tag found */
+ delete playlist;
+ return nullptr;
+ }
+
+ playlist->filename = PathTraitsUTF8::GetBase(uri);
+
+ playlist->next = &playlist->cuesheet[0];
+ playlist->parser = new CueParser();
+
+ return playlist;
+}
+
+DetachedSong *
+EmbeddedCuePlaylist::NextSong()
+{
+ DetachedSong *song = parser->Get();
+ if (song != nullptr)
+ return song;
+
+ while (*next != 0) {
+ const char *line = next;
+ char *eol = strpbrk(next, "\r\n");
+ if (eol != nullptr) {
+ /* null-terminate the line */
+ *eol = 0;
+ next = eol + 1;
+ } else
+ /* last line; put the "next" pointer to the
+ end of the buffer */
+ next += strlen(line);
+
+ parser->Feed(line);
+ song = parser->Get();
+ if (song != nullptr) {
+ song->SetURI(filename);
+ return song;
+ }
+ }
+
+ parser->Finish();
+ song = parser->Get();
+ if (song != nullptr)
+ song->SetURI(filename);
+ return song;
+}
+
+static const char *const embcue_playlist_suffixes[] = {
+ /* a few codecs that are known to be supported; there are
+ probably many more */
+ "flac",
+ "mp3", "mp2",
+ "mp4", "mp4a", "m4b",
+ "ape",
+ "wv",
+ "ogg", "oga",
+ nullptr
+};
+
+const struct playlist_plugin embcue_playlist_plugin = {
+ "embcue",
+
+ nullptr,
+ nullptr,
+ embcue_playlist_open_uri,
+ nullptr,
+
+ nullptr,
+ embcue_playlist_suffixes,
+ nullptr,
+};
diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
new file mode 100644
index 000000000..5eedf3f13
--- /dev/null
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin embcue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..93316ca6c
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ExtM3uPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
+#include "input/TextInputStream.hxx"
+
+#include <string.h>
+#include <stdlib.h>
+
+class ExtM3uPlaylist final : public SongEnumerator {
+ TextInputStream tis;
+
+public:
+ ExtM3uPlaylist(InputStream &is)
+ :tis(is) {
+ }
+
+ bool CheckFirstLine() {
+ char *line = tis.ReadLine();
+ if (line == nullptr)
+ return false;
+
+ StripRight(line);
+ return strcmp(line, "#EXTM3U") == 0;
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+extm3u_open_stream(InputStream &is)
+{
+ ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is);
+
+ if (!playlist->CheckFirstLine()) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ delete playlist;
+ return nullptr;
+ }
+
+ return playlist;
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static Tag
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return Tag();
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = StripLeft(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return Tag();
+
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromS(unsigned(duration)));
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag.AddItem(TAG_NAME, name);
+
+ return tag.Commit();
+}
+
+DetachedSong *
+ExtM3uPlaylist::NextSong()
+{
+ Tag tag;
+ char *line_s;
+
+ do {
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
+
+ StripRight(line_s);
+
+ if (StringStartsWith(line_s, "#EXTINF:")) {
+ tag = extm3u_parse_tag(line_s + 8);
+ continue;
+ }
+
+ line_s = StripLeft(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s, std::move(tag));
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ "m3u8",
+ nullptr
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ nullptr
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ "extm3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ extm3u_open_stream,
+
+ nullptr,
+ extm3u_suffixes,
+ extm3u_mime_types,
+};
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..5743ded43
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..0428d291a
--- /dev/null
+++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "M3uPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "util/StringUtil.hxx"
+#include "input/TextInputStream.hxx"
+
+class M3uPlaylist final : public SongEnumerator {
+ TextInputStream tis;
+
+public:
+ M3uPlaylist(InputStream &is)
+ :tis(is) {
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+m3u_open_stream(InputStream &is)
+{
+ return new M3uPlaylist(is);
+}
+
+DetachedSong *
+M3uPlaylist::NextSong()
+{
+ char *line_s;
+
+ do {
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
+
+ line_s = Strip(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ "m3u8",
+ nullptr
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ nullptr
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ "m3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ m3u_open_stream,
+
+ nullptr,
+ m3u_suffixes,
+ m3u_mime_types,
+};
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..f1ad14069
--- /dev/null
+++ b/src/playlist/plugins/M3uPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX
+#define MPD_M3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx
new file mode 100644
index 000000000..f7724f522
--- /dev/null
+++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlsPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "input/InputStream.hxx"
+#include "DetachedSong.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <string>
+#include <stdio.h>
+
+#include <stdio.h>
+
+static constexpr Domain pls_domain("pls");
+
+static void
+pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
+{
+ gchar *value;
+ GError *error = nullptr;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ FormatError(pls_domain,
+ "Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = nullptr;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = nullptr;
+ }
+ }
+
+ for (; num_entries > 0; --num_entries) {
+ char key[64];
+ sprintf(key, "File%u", num_entries);
+ char *uri = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
+ key, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ TagBuilder tag;
+
+ sprintf(key, "Title%u", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ nullptr);
+ if (value != nullptr)
+ tag.AddItem(TAG_TITLE, value);
+
+ g_free(value);
+
+ sprintf(key, "Length%u", num_entries);
+ int length = g_key_file_get_integer(keyfile, "playlist", key,
+ nullptr);
+ if (length > 0)
+ tag.SetDuration(SignedSongTime::FromS(length));
+
+ songs.emplace_front(uri, tag.Commit());
+ g_free(uri);
+ }
+
+}
+
+static SongEnumerator *
+pls_open_stream(InputStream &is)
+{
+ GError *error = nullptr;
+ Error error2;
+
+ std::string kf_data;
+
+ do {
+ char buffer[1024];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
+ if (nbytes == 0) {
+ if (error2.IsDefined()) {
+ LogError(error2);
+ return nullptr;
+ }
+
+ break;
+ }
+
+ kf_data.append(buffer, nbytes);
+ /* Limit to 64k */
+ } while (kf_data.length() < 65536);
+
+ if (kf_data.empty()) {
+ LogWarning(pls_domain, "KeyFile parser failed: No Data");
+ return nullptr;
+ }
+
+ GKeyFile *keyfile = g_key_file_new();
+ if (!g_key_file_load_from_data(keyfile,
+ kf_data.data(), kf_data.length(),
+ G_KEY_FILE_NONE, &error)) {
+ FormatError(pls_domain,
+ "KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return nullptr;
+ }
+
+ std::forward_list<DetachedSong> songs;
+ pls_parser(keyfile, songs);
+ g_key_file_free(keyfile);
+
+ return new MemorySongEnumerator(std::move(songs));
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ nullptr
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ nullptr
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ "pls",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ pls_open_stream,
+
+ nullptr,
+ pls_suffixes,
+ pls_mime_types,
+};
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx
new file mode 100644
index 000000000..1a3f33873
--- /dev/null
+++ b/src/playlist/plugins/PlsPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX
+#define MPD_PLS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
new file mode 100644
index 000000000..6f9aad54b
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RssPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct RssParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ITEM,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "enclosure"
+ * element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ RssParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+rss_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ parser->state = RssParser::ITEM;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "enclosure")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "url");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+ else if (StringEqualsCaseASCII(element_name, "itunes:author"))
+ parser->tag_type = TAG_ARTIST;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_end_element(void *user_data, const XML_Char *element_name)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = RssParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_char_data(void *user_data, const XML_Char *s, int len)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+rss_open_stream(InputStream &is)
+{
+ RssParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(rss_start_element, rss_end_element);
+ expat.SetCharacterDataHandler(rss_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const rss_suffixes[] = {
+ "rss",
+ nullptr
+};
+
+static const char *const rss_mime_types[] = {
+ "application/rss+xml",
+ "text/xml",
+ nullptr
+};
+
+const struct playlist_plugin rss_playlist_plugin = {
+ "rss",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ rss_open_stream,
+
+ nullptr,
+ rss_suffixes,
+ rss_mime_types,
+};
diff --git a/src/playlist/plugins/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx
new file mode 100644
index 000000000..a00a5a898
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX
+#define MPD_RSS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin rss_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
new file mode 100644
index 000000000..ec4d240a5
--- /dev/null
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SoundCloudPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "config/ConfigData.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+#include <yajl/yajl_parse.h>
+
+#include <string>
+
+#include <string.h>
+
+static struct {
+ std::string apikey;
+} soundcloud_config;
+
+static constexpr Domain soundcloud_domain("soundcloud");
+
+static bool
+soundcloud_init(const config_param &param)
+{
+ // APIKEY for MPD application, registered under DarkFox' account.
+ soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8");
+ if (soundcloud_config.apikey.empty()) {
+ LogDebug(soundcloud_domain,
+ "disabling the soundcloud playlist plugin "
+ "because API key is not set");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Construct a full soundcloud resolver URL from the given fragment.
+ * @param uri uri of a soundcloud page (or just the path)
+ * @return Constructed URL. Must be freed with g_free.
+ */
+static char *
+soundcloud_resolve(const char* uri)
+{
+ char *u, *ru;
+
+ if (StringStartsWith(uri, "https://")) {
+ u = g_strdup(uri);
+ } else if (StringStartsWith(uri, "soundcloud.com")) {
+ u = g_strconcat("https://", uri, nullptr);
+ } else {
+ /* assume it's just a path on soundcloud.com */
+ u = g_strconcat("https://soundcloud.com/", uri, nullptr);
+ }
+
+ ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ g_free(u);
+
+ return ru;
+}
+
+/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */
+
+enum key {
+ Duration,
+ Title,
+ Stream_URL,
+ Other,
+};
+
+const char* key_str[] = {
+ "duration",
+ "title",
+ "stream_url",
+ nullptr,
+};
+
+struct parse_data {
+ int key;
+ char* stream_url;
+ long duration;
+ char* title;
+ int got_url; /* nesting level of last stream_url */
+
+ std::forward_list<DetachedSong> songs;
+};
+
+static int
+handle_integer(void *ctx,
+ long
+#ifndef HAVE_YAJL1
+ long
+#endif
+ intval)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ switch (data->key) {
+ case Duration:
+ data->duration = intval;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int
+handle_string(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+ const char *s = (const char *) stringval;
+
+ switch (data->key) {
+ case Title:
+ g_free(data->title);
+ data->title = g_strndup(s, stringlen);
+ break;
+ case Stream_URL:
+ g_free(data->stream_url);
+ data->stream_url = g_strndup(s, stringlen);
+ data->got_url = 1;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int
+handle_mapkey(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ int i;
+ data->key = Other;
+
+ for (i = 0; i < Other; ++i) {
+ if (memcmp((const char *)stringval, key_str[i], stringlen) == 0) {
+ data->key = i;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+static int
+handle_start_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 0)
+ data->got_url++;
+
+ return 1;
+}
+
+static int
+handle_end_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 1) {
+ data->got_url--;
+ return 1;
+ }
+
+ if (data->got_url == 0)
+ return 1;
+
+ /* got_url == 1, track finished, make it into a song */
+ data->got_url = 0;
+
+ char *u = g_strconcat(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromMS(data->duration));
+ if (data->title != nullptr)
+ tag.AddItem(TAG_NAME, data->title);
+
+ data->songs.emplace_front(u, tag.Commit());
+ g_free(u);
+
+ return 1;
+}
+
+static yajl_callbacks parse_callbacks = {
+ nullptr,
+ nullptr,
+ handle_integer,
+ nullptr,
+ nullptr,
+ handle_string,
+ handle_start_map,
+ handle_mapkey,
+ handle_end_map,
+ nullptr,
+ nullptr,
+};
+
+/**
+ * Read JSON data and parse it using the given YAJL parser.
+ * @param url URL of the JSON data.
+ * @param hand YAJL parser handle.
+ * @return -1 on error, 0 on success.
+ */
+static int
+soundcloud_parse_json(const char *url, yajl_handle hand,
+ Mutex &mutex, Cond &cond)
+{
+ Error error;
+ InputStream *input_stream = InputStream::OpenReady(url, mutex, cond,
+ error);
+ if (input_stream == nullptr) {
+ if (error.IsDefined())
+ LogError(error);
+ return -1;
+ }
+
+ mutex.lock();
+
+ yajl_status stat;
+ int done = 0;
+
+ while (!done) {
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
+ const size_t nbytes =
+ input_stream->Read(buffer, sizeof(buffer), error);
+ if (nbytes == 0) {
+ if (error.IsDefined())
+ LogError(error);
+
+ if (input_stream->IsEOF()) {
+ done = true;
+ } else {
+ mutex.unlock();
+ delete input_stream;
+ return -1;
+ }
+ }
+
+ if (done) {
+#ifdef HAVE_YAJL1
+ stat = yajl_parse_complete(hand);
+#else
+ stat = yajl_complete_parse(hand);
+#endif
+ } else
+ stat = yajl_parse(hand, ubuffer, nbytes);
+
+ if (stat != yajl_status_ok
+#ifdef HAVE_YAJL1
+ && stat != yajl_status_insufficient_data
+#endif
+ )
+ {
+ unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
+ LogError(soundcloud_domain, (const char *)str);
+ yajl_free_error(hand, str);
+ break;
+ }
+ }
+
+ mutex.unlock();
+ delete input_stream;
+
+ return 0;
+}
+
+/**
+ * Parse a soundcloud:// URL and create a playlist.
+ * @param uri A soundcloud URL. Accepted forms:
+ * soundcloud://track/<track-id>
+ * soundcloud://playlist/<playlist-id>
+ * soundcloud://url/<url or path of soundcloud page>
+ */
+static SongEnumerator *
+soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(memcmp(uri, "soundcloud://", 13) == 0);
+ uri += 13;
+
+ char *u = nullptr;
+ if (memcmp(uri, "track/", 6) == 0) {
+ const char *rest = uri + 6;
+ u = g_strconcat("https://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "playlist/", 9) == 0) {
+ const char *rest = uri + 9;
+ u = g_strconcat("https://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "user/", 5) == 0) {
+ const char *rest = uri + 5;
+ u = g_strconcat("https://api.soundcloud.com/users/",
+ rest, "/tracks.json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "search/", 7) == 0) {
+ const char *rest = uri + 7;
+ u = g_strconcat("https://api.soundcloud.com/tracks.json?q=",
+ rest, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "url/", 4) == 0) {
+ const char *rest = uri + 4;
+ /* Translate to soundcloud resolver call. libcurl will automatically
+ follow the redirect to the right resource. */
+ u = soundcloud_resolve(rest);
+ }
+
+ if (u == nullptr) {
+ LogWarning(soundcloud_domain, "unknown soundcloud URI");
+ return nullptr;
+ }
+
+ struct parse_data data;
+ data.got_url = 0;
+ data.title = nullptr;
+ data.stream_url = nullptr;
+#ifdef HAVE_YAJL1
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
+ &data);
+#else
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
+#endif
+
+ int ret = soundcloud_parse_json(u, hand, mutex, cond);
+
+ g_free(u);
+ yajl_free(hand);
+ g_free(data.title);
+ g_free(data.stream_url);
+
+ if (ret == -1)
+ return nullptr;
+
+ data.songs.reverse();
+ return new MemorySongEnumerator(std::move(data.songs));
+}
+
+static const char *const soundcloud_schemes[] = {
+ "soundcloud",
+ nullptr
+};
+
+const struct playlist_plugin soundcloud_playlist_plugin = {
+ "soundcloud",
+
+ soundcloud_init,
+ nullptr,
+ soundcloud_open_uri,
+ nullptr,
+
+ soundcloud_schemes,
+ nullptr,
+ nullptr,
+};
+
+
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
new file mode 100644
index 000000000..b355b477a
--- /dev/null
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin soundcloud_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
new file mode 100644
index 000000000..5b6010b53
--- /dev/null
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "XspfPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+static constexpr Domain xspf_domain("xspf");
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct XspfParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "location" element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ XspfParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+xspf_start_element(void *user_data, const XML_Char *element_name,
+ gcc_unused const XML_Char **atts)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = XspfParser::PLAYLIST;
+
+ break;
+
+ case XspfParser::PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = XspfParser::TRACKLIST;
+
+ break;
+
+ case XspfParser::TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = XspfParser::TRACK;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case XspfParser::TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = XspfParser::LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag_type = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag_type = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag_type = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag_type = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag_type = TAG_TRACK;
+
+ break;
+
+ case XspfParser::LOCATION:
+ break;
+ }
+}
+
+static void XMLCALL
+xspf_end_element(void *user_data, const XML_Char *element_name)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ break;
+
+ case XspfParser::PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = XspfParser::ROOT;
+
+ break;
+
+ case XspfParser::TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = XspfParser::PLAYLIST;
+
+ break;
+
+ case XspfParser::TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = XspfParser::TRACKLIST;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->state = XspfParser::TRACK;
+ break;
+ }
+}
+
+static void XMLCALL
+xspf_char_data(void *user_data, const XML_Char *s, int len)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ case XspfParser::PLAYLIST:
+ case XspfParser::TRACKLIST:
+ break;
+
+ case XspfParser::TRACK:
+ if (!parser->location.empty() &&
+ parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->location.assign(s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+xspf_open_stream(InputStream &is)
+{
+ XspfParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(xspf_start_element, xspf_end_element);
+ expat.SetCharacterDataHandler(xspf_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ nullptr
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ nullptr
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ "xspf",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ xspf_open_stream,
+
+ nullptr,
+ xspf_suffixes,
+ xspf_mime_types,
+};
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx
new file mode 100644
index 000000000..6b08a6be6
--- /dev/null
+++ b/src/playlist/plugins/XspfPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX
+#define MPD_XSPF_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif