aboutsummaryrefslogtreecommitdiffstats
path: root/src/playlist
diff options
context:
space:
mode:
Diffstat (limited to 'src/playlist')
-rw-r--r--src/playlist/AsxPlaylistPlugin.cxx286
-rw-r--r--src/playlist/CloseSongEnumerator.cxx34
-rw-r--r--src/playlist/CloseSongEnumerator.hxx47
-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/Print.cxx74
-rw-r--r--src/playlist/Print.hxx36
-rw-r--r--src/playlist/RssPlaylistPlugin.cxx284
-rw-r--r--src/playlist/SongEnumerator.hxx41
-rw-r--r--src/playlist/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.hxx (renamed from src/playlist/AsxPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.cxx (renamed from src/playlist/CuePlaylistPlugin.cxx)27
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.hxx (renamed from src/playlist/CuePlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/DespotifyPlaylistPlugin.cxx (renamed from src/playlist/DespotifyPlaylistPlugin.cxx)33
-rw-r--r--src/playlist/plugins/DespotifyPlaylistPlugin.hxx (renamed from src/playlist/DespotifyPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx (renamed from src/playlist/EmbeddedCuePlaylistPlugin.cxx)32
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx (renamed from src/playlist/EmbeddedCuePlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.cxx (renamed from src/playlist/ExtM3uPlaylistPlugin.cxx)73
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.hxx (renamed from src/playlist/ExtM3uPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.cxx (renamed from src/playlist/M3uPlaylistPlugin.cxx)25
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.hxx (renamed from src/playlist/M3uPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.cxx (renamed from src/playlist/PlsPlaylistPlugin.cxx)78
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.hxx (renamed from src/playlist/PlsPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx185
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.hxx (renamed from src/playlist/RssPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.cxx (renamed from src/playlist/SoundCloudPlaylistPlugin.cxx)172
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.hxx (renamed from src/playlist/SoundCloudPlaylistPlugin.hxx)2
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.cxx (renamed from src/playlist/XspfPlaylistPlugin.cxx)155
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.hxx (renamed from src/playlist/XspfPlaylistPlugin.hxx)2
44 files changed, 2488 insertions, 936 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/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/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/Print.cxx b/src/playlist/Print.cxx
new file mode 100644
index 000000000..0db2a4ab0
--- /dev/null
+++ b/src/playlist/Print.cxx
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Print.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "SongEnumerator.hxx"
+#include "SongPrint.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
+#include "fs/Traits.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "client/Client.hxx"
+
+static void
+playlist_provider_print(Client &client, const char *uri,
+ SongEnumerator &e, bool detail)
+{
+ const std::string base_uri = uri != nullptr
+ ? PathTraitsUTF8::GetParent(uri)
+ : std::string(".");
+
+ const SongLoader loader(client);
+
+ DetachedSong *song;
+ while ((song = e.NextSong()) != nullptr) {
+ if (playlist_check_translate_song(*song, base_uri.c_str(),
+ loader)) {
+ if (detail)
+ song_print_info(client, *song);
+ else
+ song_print_uri(client, *song);
+ }
+
+ delete song;
+ }
+}
+
+bool
+playlist_file_print(Client &client, const char *uri, bool detail)
+{
+ Mutex mutex;
+ Cond cond;
+
+ SongEnumerator *playlist = playlist_open_any(uri,
+#ifdef ENABLE_DATABASE
+ client.GetStorage(),
+#endif
+ mutex, cond);
+ if (playlist == nullptr)
+ return false;
+
+ playlist_provider_print(client, uri, *playlist, detail);
+ delete playlist;
+ return true;
+}
diff --git a/src/playlist/Print.hxx b/src/playlist/Print.hxx
new file mode 100644
index 000000000..c2fff5475
--- /dev/null
+++ b/src/playlist/Print.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST__PRINT_HXX
+#define MPD_PLAYLIST__PRINT_HXX
+
+class Client;
+
+/**
+ * Send the playlist file to the client.
+ *
+ * @param client the client which requested the playlist
+ * @param uri the URI of the playlist file in UTF-8 encoding
+ * @param detail true if all details should be printed
+ * @return true on success, false if the playlist does not exist
+ */
+bool
+playlist_file_print(Client &client, const char *uri, bool detail);
+
+#endif
diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
deleted file mode 100644
index e2a44bfd3..000000000
--- a/src/playlist/RssPlaylistPlugin.cxx
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RssPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain rss_domain("rss");
-
-/**
- * This is the state object for the GLib XML parser.
- */
-struct RssParser {
- /**
- * The list of songs (in reverse order because that's faster
- * while adding).
- */
- std::forward_list<SongPointer> songs;
-
- /**
- * The current position in the XML file.
- */
- enum {
- ROOT, ITEM,
- } state;
-
- /**
- * The current tag within the "entry" element. This is only
- * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
- * is no (known) tag.
- */
- TagType tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- Song *song;
-
- RssParser()
- :state(ROOT) {}
-};
-
-static const gchar *
-get_attribute(const gchar **attribute_names, const gchar **attribute_values,
- const gchar *name)
-{
- for (unsigned i = 0; attribute_names[i] != nullptr; ++i)
- if (StringEqualsCaseASCII(attribute_names[i], name))
- return attribute_values[i];
-
- return nullptr;
-}
-
-static void
-rss_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- if (StringEqualsCaseASCII(element_name, "item")) {
- parser->state = RssParser::ITEM;
- parser->song = Song::NewRemote("rss:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case RssParser::ITEM:
- if (StringEqualsCaseASCII(element_name, "enclosure")) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "url");
- if (href != nullptr) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- Song *song = Song::NewRemote(href);
-
- if (parser->song != nullptr) {
- song->tag = parser->song->tag;
- parser->song->tag = nullptr;
- parser->song->Free();
- }
-
- parser->song = song;
- }
- } else if (StringEqualsCaseASCII(element_name, "title"))
- parser->tag = TAG_TITLE;
- else if (StringEqualsCaseASCII(element_name, "itunes:author"))
- parser->tag = TAG_ARTIST;
-
- break;
- }
-}
-
-static void
-rss_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- break;
-
- case RssParser::ITEM:
- if (StringEqualsCaseASCII(element_name, "item")) {
- if (strcmp(parser->song->uri, "rss:") != 0)
- parser->songs.emplace_front(parser->song);
- else
- parser->song->Free();
-
- parser->state = RssParser::ROOT;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
- }
-}
-
-static void
-rss_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- break;
-
- case RssParser::ITEM:
- if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag,
- text, text_len);
- }
-
- break;
- }
-}
-
-static const GMarkupParser rss_parser = {
- rss_start_element,
- rss_end_element,
- rss_text,
- nullptr,
- nullptr,
-};
-
-static void
-rss_parser_destroy(gpointer data)
-{
- RssParser *parser = (RssParser *)data;
-
- if (parser->state >= RssParser::ITEM)
- parser->song->Free();
-}
-
-/*
- * The playlist object
- *
- */
-
-static SongEnumerator *
-rss_open_stream(InputStream &is)
-{
- RssParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the RSS XML file */
-
- context = g_markup_parse_context_new(&rss_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, rss_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
- parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
-}
-
-static const char *const rss_suffixes[] = {
- "rss",
- nullptr
-};
-
-static const char *const rss_mime_types[] = {
- "application/rss+xml",
- "text/xml",
- nullptr
-};
-
-const struct playlist_plugin rss_playlist_plugin = {
- "rss",
-
- nullptr,
- nullptr,
- nullptr,
- rss_open_stream,
-
- nullptr,
- rss_suffixes,
- rss_mime_types,
-};
diff --git a/src/playlist/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/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/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx
index 240c1824a..63371be0f 100644
--- a/src/playlist/AsxPlaylistPlugin.hxx
+++ b/src/playlist/plugins/AsxPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx
index 42a43bbad..b907d34d0 100644
--- a/src/playlist/CuePlaylistPlugin.cxx
+++ b/src/playlist/plugins/CuePlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,15 +19,12 @@
#include "config.h"
#include "CuePlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "Song.hxx"
-#include "cue/CueParser.hxx"
-#include "TextInputStream.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "input/TextInputStream.hxx"
-#include <assert.h>
-#include <string.h>
+#include <string>
class CuePlaylist final : public SongEnumerator {
InputStream &is;
@@ -39,7 +36,7 @@ class CuePlaylist final : public SongEnumerator {
:is(_is), tis(is) {
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
@@ -48,16 +45,16 @@ cue_playlist_open_stream(InputStream &is)
return new CuePlaylist(is);
}
-Song *
+DetachedSong *
CuePlaylist::NextSong()
{
- Song *song = parser.Get();
+ DetachedSong *song = parser.Get();
if (song != nullptr)
return song;
- std::string line;
- while (tis.ReadLine(line)) {
- parser.Feed(line.c_str());
+ const char *line;
+ while ((line = tis.ReadLine()) != nullptr) {
+ parser.Feed(line);
song = parser.Get();
if (song != nullptr)
return song;
diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx
index cf5e3a8f0..4d833bfc2 100644
--- a/src/playlist/CuePlaylistPlugin.hxx
+++ b/src/playlist/plugins/CuePlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx
index a1a865c08..636f64bc6 100644
--- a/src/playlist/DespotifyPlaylistPlugin.cxx
+++ b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,11 +19,11 @@
#include "config.h"
#include "DespotifyPlaylistPlugin.hxx"
-#include "DespotifyUtils.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
+#include "lib/despotify/DespotifyUtils.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
#include "tag/Tag.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "Log.hxx"
extern "C" {
@@ -32,12 +32,12 @@ extern "C" {
#include <string.h>
#include <stdlib.h>
+#include <string.h>
static void
-add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
+add_song(std::forward_list<DetachedSong> &songs, ds_track &track)
{
const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
- Song *song;
char uri[128];
char *ds_uri;
@@ -45,35 +45,32 @@ add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
ds_uri = uri + strlen(dsp_scheme) + 3;
- if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
+ if (despotify_track_to_uri(&track, ds_uri) != ds_uri) {
/* Should never really fail, but let's be sure */
FormatDebug(despotify_domain,
- "Can't add track %s", track->title);
+ "Can't add track %s", track.title);
return;
}
- song = Song::NewRemote(uri);
- song->tag = mpd_despotify_tag_from_track(track);
-
- songs.emplace_front(song);
+ songs.emplace_front(uri, mpd_despotify_tag_from_track(track));
}
static bool
parse_track(struct despotify_session *session,
- std::forward_list<SongPointer> &songs,
+ std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
struct ds_track *track = despotify_link_get_track(session, link);
if (track == nullptr)
return false;
- add_song(songs, track);
+ add_song(songs, *track);
return true;
}
static bool
parse_playlist(struct despotify_session *session,
- std::forward_list<SongPointer> &songs,
+ std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
ds_playlist *playlist = despotify_link_get_playlist(session, link);
@@ -82,7 +79,7 @@ parse_playlist(struct despotify_session *session,
for (ds_track *track = playlist->tracks; track != nullptr;
track = track->next)
- add_song(songs, track);
+ add_song(songs, *track);
return true;
}
@@ -103,7 +100,7 @@ despotify_playlist_open_uri(const char *url,
return nullptr;
}
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
bool parse_result;
switch (link->type) {
diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx
index c1e5b7f39..6acfd40f4 100644
--- a/src/playlist/DespotifyPlaylistPlugin.hxx
+++ b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
index 2734fa59e..9e8f91e05 100644
--- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,20 +25,18 @@
#include "config.h"
#include "EmbeddedCuePlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagId3.hxx"
#include "tag/ApeTag.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
#include "TagFile.hxx"
-#include "cue/CueParser.hxx"
#include "fs/Traits.hxx"
#include "fs/AllocatedPath.hxx"
#include "util/ASCII.hxx"
-#include <assert.h>
#include <string.h>
class EmbeddedCuePlaylist final : public SongEnumerator {
@@ -71,7 +69,7 @@ public:
delete parser;
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static void
@@ -95,7 +93,7 @@ embcue_playlist_open_uri(const char *uri,
gcc_unused Mutex &mutex,
gcc_unused Cond &cond)
{
- if (!PathTraits::IsAbsoluteUTF8(uri))
+ if (!PathTraitsUTF8::IsAbsolute(uri))
/* only local files supported */
return nullptr;
@@ -105,7 +103,7 @@ embcue_playlist_open_uri(const char *uri,
const auto playlist = new EmbeddedCuePlaylist();
- tag_file_scan(path_fs, &embcue_tag_handler, playlist);
+ tag_file_scan(path_fs, embcue_tag_handler, playlist);
if (playlist->cuesheet.empty()) {
tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
if (playlist->cuesheet.empty())
@@ -118,7 +116,7 @@ embcue_playlist_open_uri(const char *uri,
return nullptr;
}
- playlist->filename = PathTraits::GetBaseUTF8(uri);
+ playlist->filename = PathTraitsUTF8::GetBase(uri);
playlist->next = &playlist->cuesheet[0];
playlist->parser = new CueParser();
@@ -126,10 +124,10 @@ embcue_playlist_open_uri(const char *uri,
return playlist;
}
-Song *
+DetachedSong *
EmbeddedCuePlaylist::NextSong()
{
- Song *song = parser->Get();
+ DetachedSong *song = parser->Get();
if (song != nullptr)
return song;
@@ -147,14 +145,16 @@ EmbeddedCuePlaylist::NextSong()
parser->Feed(line);
song = parser->Get();
- if (song != nullptr)
- return song->ReplaceURI(filename.c_str());
+ if (song != nullptr) {
+ song->SetURI(filename);
+ return song;
+ }
}
parser->Finish();
song = parser->Get();
if (song != nullptr)
- song = song->ReplaceURI(filename.c_str());
+ song->SetURI(filename);
return song;
}
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
index e306730f4..5eedf3f13 100644
--- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
index 5ef010bda..93316ca6c 100644
--- a/src/playlist/ExtM3uPlaylistPlugin.cxx
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,14 +19,13 @@
#include "config.h"
#include "ExtM3uPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
-#include "TextInputStream.hxx"
-
-#include <glib.h>
+#include "input/TextInputStream.hxx"
#include <string.h>
#include <stdlib.h>
@@ -40,12 +39,15 @@ public:
}
bool CheckFirstLine() {
- std::string line;
- return tis.ReadLine(line) &&
- strcmp(line.c_str(), "#EXTM3U") == 0;
+ char *line = tis.ReadLine();
+ if (line == nullptr)
+ return false;
+
+ StripRight(line);
+ return strcmp(line, "#EXTM3U") == 0;
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
@@ -57,7 +59,7 @@ extm3u_open_stream(InputStream &is)
/* no EXTM3U header: fall back to the plain m3u
plugin */
delete playlist;
- return NULL;
+ return nullptr;
}
return playlist;
@@ -68,69 +70,62 @@ extm3u_open_stream(InputStream &is)
*
* @param line the rest of the input line after the colon
*/
-static Tag *
+static Tag
extm3u_parse_tag(const char *line)
{
long duration;
char *endptr;
const char *name;
- Tag *tag;
duration = strtol(line, &endptr, 10);
if (endptr[0] != ',')
/* malformed line */
- return NULL;
+ return Tag();
if (duration < 0)
/* 0 means unknown duration */
duration = 0;
- name = strchug_fast(endptr + 1);
+ name = StripLeft(endptr + 1);
if (*name == 0 && duration == 0)
/* no information available; don't allocate a tag
object */
- return NULL;
+ return Tag();
- tag = new Tag();
- tag->time = duration;
+ TagBuilder tag;
+ tag.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);
+ tag.AddItem(TAG_NAME, name);
- return tag;
+ return tag.Commit();
}
-Song *
+DetachedSong *
ExtM3uPlaylist::NextSong()
{
- Tag *tag = NULL;
- std::string line;
- const char *line_s;
- Song *song;
+ Tag tag;
+ char *line_s;
do {
- if (!tis.ReadLine(line)) {
- delete tag;
- return NULL;
- }
-
- line_s = line.c_str();
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
- if (g_str_has_prefix(line_s, "#EXTINF:")) {
- delete tag;
+ StripRight(line_s);
+
+ if (StringStartsWith(line_s, "#EXTINF:")) {
tag = extm3u_parse_tag(line_s + 8);
continue;
}
- line_s = strchug_fast(line_s);
+ line_s = StripLeft(line_s);
} while (line_s[0] == '#' || *line_s == 0);
- song = Song::NewRemote(line_s);
- song->tag = tag;
- return song;
+ return new DetachedSong(line_s, std::move(tag));
}
static const char *const extm3u_suffixes[] = {
@@ -141,7 +136,7 @@ static const char *const extm3u_suffixes[] = {
static const char *const extm3u_mime_types[] = {
"audio/x-mpegurl",
- NULL
+ nullptr
};
const struct playlist_plugin extm3u_playlist_plugin = {
diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
index 844fba15c..5743ded43 100644
--- a/src/playlist/ExtM3uPlaylistPlugin.hxx
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx
index 8b6adc2b6..0428d291a 100644
--- a/src/playlist/M3uPlaylistPlugin.cxx
+++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,11 +19,11 @@
#include "config.h"
#include "M3uPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
#include "util/StringUtil.hxx"
-#include "TextInputStream.hxx"
+#include "input/TextInputStream.hxx"
class M3uPlaylist final : public SongEnumerator {
TextInputStream tis;
@@ -33,7 +33,7 @@ public:
:tis(is) {
}
- virtual Song *NextSong() override;
+ virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
@@ -42,21 +42,20 @@ m3u_open_stream(InputStream &is)
return new M3uPlaylist(is);
}
-Song *
+DetachedSong *
M3uPlaylist::NextSong()
{
- std::string line;
- const char *line_s;
+ char *line_s;
do {
- if (!tis.ReadLine(line))
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
return nullptr;
- line_s = line.c_str();
- line_s = strchug_fast(line_s);
+ line_s = Strip(line_s);
} while (line_s[0] == '#' || *line_s == 0);
- return Song::NewRemote(line_s);
+ return new DetachedSong(line_s);
}
static const char *const m3u_suffixes[] = {
diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx
index a2058bb29..f1ad14069 100644
--- a/src/playlist/M3uPlaylistPlugin.hxx
+++ b/src/playlist/plugins/M3uPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx
index 7b5c8824c..f7724f522 100644
--- a/src/playlist/PlsPlaylistPlugin.cxx
+++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,11 +19,11 @@
#include "config.h"
#include "PlsPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "input/InputStream.hxx"
+#include "DetachedSong.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -33,13 +33,14 @@
#include <string>
#include <stdio.h>
+#include <stdio.h>
+
static constexpr Domain pls_domain("pls");
static void
-pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
+pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
{
gchar *value;
- int length;
GError *error = nullptr;
int num_entries = g_key_file_get_integer(keyfile, "playlist",
"NumberOfEntries", &error);
@@ -58,13 +59,11 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
}
}
- while (num_entries > 0) {
- Song *song;
-
+ for (; num_entries > 0; --num_entries) {
char key[64];
sprintf(key, "File%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
+ char *uri = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
if(error) {
FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
key, error->message);
@@ -72,36 +71,24 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
return;
}
- song = Song::NewRemote(value);
- g_free(value);
+ TagBuilder tag;
sprintf(key, "Title%u", num_entries);
value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error == nullptr && value){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->AddItem(TAG_TITLE, value);
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
+ nullptr);
+ if (value != nullptr)
+ tag.AddItem(TAG_TITLE, value);
+
g_free(value);
sprintf(key, "Length%u", num_entries);
- length = g_key_file_get_integer(keyfile, "playlist", key,
- &error);
- if(error == nullptr && length > 0){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->time = length;
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
+ int length = g_key_file_get_integer(keyfile, "playlist", key,
+ nullptr);
+ if (length > 0)
+ tag.SetDuration(SignedSongTime::FromS(length));
- songs.emplace_front(song);
- num_entries--;
+ songs.emplace_front(uri, tag.Commit());
+ g_free(uri);
}
}
@@ -111,15 +98,12 @@ pls_open_stream(InputStream &is)
{
GError *error = nullptr;
Error error2;
- size_t nbytes;
- char buffer[1024];
- bool success;
- GKeyFile *keyfile;
std::string kf_data;
do {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
+ char buffer[1024];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
if (nbytes == 0) {
if (error2.IsDefined()) {
LogError(error2);
@@ -138,12 +122,10 @@ pls_open_stream(InputStream &is)
return nullptr;
}
- keyfile = g_key_file_new();
- success = g_key_file_load_from_data(keyfile,
- kf_data.data(), kf_data.length(),
- G_KEY_FILE_NONE, &error);
-
- if (!success) {
+ GKeyFile *keyfile = g_key_file_new();
+ if (!g_key_file_load_from_data(keyfile,
+ kf_data.data(), kf_data.length(),
+ G_KEY_FILE_NONE, &error)) {
FormatError(pls_domain,
"KeyFile parser failed: %s", error->message);
g_error_free(error);
@@ -151,7 +133,7 @@ pls_open_stream(InputStream &is)
return nullptr;
}
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
pls_parser(keyfile, songs);
g_key_file_free(keyfile);
diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx
index 3fafd36d0..1a3f33873 100644
--- a/src/playlist/PlsPlaylistPlugin.hxx
+++ b/src/playlist/plugins/PlsPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
new file mode 100644
index 000000000..6f9aad54b
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RssPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct RssParser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ std::forward_list<DetachedSong> songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ITEM,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "enclosure"
+ * element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ RssParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+rss_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ parser->state = RssParser::ITEM;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "enclosure")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "url");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+ else if (StringEqualsCaseASCII(element_name, "itunes:author"))
+ parser->tag_type = TAG_ARTIST;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_end_element(void *user_data, const XML_Char *element_name)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = RssParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_char_data(void *user_data, const XML_Char *s, int len)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+rss_open_stream(InputStream &is)
+{
+ RssParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(rss_start_element, rss_end_element);
+ expat.SetCharacterDataHandler(rss_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const rss_suffixes[] = {
+ "rss",
+ nullptr
+};
+
+static const char *const rss_mime_types[] = {
+ "application/rss+xml",
+ "text/xml",
+ nullptr
+};
+
+const struct playlist_plugin rss_playlist_plugin = {
+ "rss",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ rss_open_stream,
+
+ nullptr,
+ rss_suffixes,
+ rss_mime_types,
+};
diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx
index f49f7e9cf..a00a5a898 100644
--- a/src/playlist/RssPlaylistPlugin.hxx
+++ b/src/playlist/plugins/RssPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
index f6797b14d..ec4d240a5 100644
--- a/src/playlist/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,12 @@
#include "config.h"
#include "SoundCloudPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "ConfigData.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "config/ConfigData.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -45,7 +45,8 @@ static constexpr Domain soundcloud_domain("soundcloud");
static bool
soundcloud_init(const config_param &param)
{
- soundcloud_config.apikey = param.GetBlockValue("apikey", "");
+ // APIKEY for MPD application, registered under DarkFox' account.
+ soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8");
if (soundcloud_config.apikey.empty()) {
LogDebug(soundcloud_domain,
"disabling the soundcloud playlist plugin "
@@ -62,19 +63,20 @@ soundcloud_init(const config_param &param)
* @return Constructed URL. Must be freed with g_free.
*/
static char *
-soundcloud_resolve(const char* uri) {
+soundcloud_resolve(const char* uri)
+{
char *u, *ru;
- if (g_str_has_prefix(uri, "http://")) {
+ if (StringStartsWith(uri, "https://")) {
u = g_strdup(uri);
- } else if (g_str_has_prefix(uri, "soundcloud.com")) {
- u = g_strconcat("http://", uri, nullptr);
+ } else if (StringStartsWith(uri, "soundcloud.com")) {
+ u = g_strconcat("https://", uri, nullptr);
} else {
/* assume it's just a path on soundcloud.com */
- u = g_strconcat("http://soundcloud.com/", uri, nullptr);
+ u = g_strconcat("https://soundcloud.com/", uri, nullptr);
}
- ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
+ ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
u, "&client_id=",
soundcloud_config.apikey.c_str(), nullptr);
g_free(u);
@@ -105,15 +107,16 @@ struct parse_data {
char* title;
int got_url; /* nesting level of last stream_url */
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
};
-static int handle_integer(void *ctx,
- long
+static int
+handle_integer(void *ctx,
+ long
#ifndef HAVE_YAJL1
- long
+ long
#endif
- intval)
+ intval)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -128,26 +131,25 @@ static int handle_integer(void *ctx,
return 1;
}
-static int handle_string(void *ctx, const unsigned char* stringval,
+static int
+handle_string(void *ctx, const unsigned char* stringval,
#ifdef HAVE_YAJL1
- unsigned int
+ unsigned int
#else
- size_t
+ size_t
#endif
- stringlen)
+ stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
const char *s = (const char *) stringval;
switch (data->key) {
case Title:
- if (data->title != nullptr)
- g_free(data->title);
+ g_free(data->title);
data->title = g_strndup(s, stringlen);
break;
case Stream_URL:
- if (data->stream_url != nullptr)
- g_free(data->stream_url);
+ g_free(data->stream_url);
data->stream_url = g_strndup(s, stringlen);
data->got_url = 1;
break;
@@ -158,13 +160,14 @@ static int handle_string(void *ctx, const unsigned char* stringval,
return 1;
}
-static int handle_mapkey(void *ctx, const unsigned char* stringval,
+static int
+handle_mapkey(void *ctx, const unsigned char* stringval,
#ifdef HAVE_YAJL1
- unsigned int
+ unsigned int
#else
- size_t
+ size_t
#endif
- stringlen)
+ stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -181,7 +184,8 @@ static int handle_mapkey(void *ctx, const unsigned char* stringval,
return 1;
}
-static int handle_start_map(void *ctx)
+static int
+handle_start_map(void *ctx)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -191,7 +195,8 @@ static int handle_start_map(void *ctx)
return 1;
}
-static int handle_end_map(void *ctx)
+static int
+handle_end_map(void *ctx)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -206,21 +211,16 @@ static int handle_end_map(void *ctx)
/* got_url == 1, track finished, make it into a song */
data->got_url = 0;
- Song *s;
- char *u;
+ char *u = g_strconcat(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
- u = g_strconcat(data->stream_url, "?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- s = Song::NewRemote(u);
- g_free(u);
-
- Tag *t = new Tag();
- t->time = data->duration / 1000;
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromMS(data->duration));
if (data->title != nullptr)
- t->AddItem(TAG_NAME, data->title);
- s->tag = t;
+ tag.AddItem(TAG_NAME, data->title);
- data->songs.emplace_front(s);
+ data->songs.emplace_front(u, tag.Commit());
+ g_free(u);
return 1;
}
@@ -249,12 +249,9 @@ static int
soundcloud_parse_json(const char *url, yajl_handle hand,
Mutex &mutex, Cond &cond)
{
- char buffer[4096];
- unsigned char *ubuffer = (unsigned char *)buffer;
-
Error error;
- InputStream *input_stream = InputStream::Open(url, mutex, cond,
- error);
+ InputStream *input_stream = InputStream::OpenReady(url, mutex, cond,
+ error);
if (input_stream == nullptr) {
if (error.IsDefined())
LogError(error);
@@ -262,12 +259,13 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
}
mutex.lock();
- input_stream->WaitReady();
yajl_status stat;
int done = 0;
while (!done) {
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
const size_t nbytes =
input_stream->Read(buffer, sizeof(buffer), error);
if (nbytes == 0) {
@@ -278,7 +276,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
done = true;
} else {
mutex.unlock();
- input_stream->Close();
+ delete input_stream;
return -1;
}
}
@@ -306,7 +304,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
}
mutex.unlock();
- input_stream->Close();
+ delete input_stream;
return 0;
}
@@ -318,80 +316,62 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
* soundcloud://playlist/<playlist-id>
* soundcloud://url/<url or path of soundcloud page>
*/
-
static SongEnumerator *
soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
{
- char *s, *p;
- char *scheme, *arg, *rest;
- s = g_strdup(uri);
- scheme = s;
- for (p = s; *p; p++) {
- if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') {
- *p = 0;
- p += 3;
- break;
- }
- }
- arg = p;
- for (; *p; p++) {
- if (*p == '/') {
- *p = 0;
- p++;
- break;
- }
- }
- rest = p;
-
- if (strcmp(scheme, "soundcloud") != 0) {
- FormatWarning(soundcloud_domain,
- "incompatible scheme for soundcloud plugin: %s",
- scheme);
- g_free(s);
- return nullptr;
- }
+ assert(memcmp(uri, "soundcloud://", 13) == 0);
+ uri += 13;
char *u = nullptr;
- if (strcmp(arg, "track") == 0) {
- u = g_strconcat("http://api.soundcloud.com/tracks/",
+ if (memcmp(uri, "track/", 6) == 0) {
+ const char *rest = uri + 6;
+ u = g_strconcat("https://api.soundcloud.com/tracks/",
rest, ".json?client_id=",
soundcloud_config.apikey.c_str(), nullptr);
- } else if (strcmp(arg, "playlist") == 0) {
- u = g_strconcat("http://api.soundcloud.com/playlists/",
+ } else if (memcmp(uri, "playlist/", 9) == 0) {
+ const char *rest = uri + 9;
+ u = g_strconcat("https://api.soundcloud.com/playlists/",
rest, ".json?client_id=",
soundcloud_config.apikey.c_str(), nullptr);
- } else if (strcmp(arg, "url") == 0) {
+ } else if (memcmp(uri, "user/", 5) == 0) {
+ const char *rest = uri + 5;
+ u = g_strconcat("https://api.soundcloud.com/users/",
+ rest, "/tracks.json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "search/", 7) == 0) {
+ const char *rest = uri + 7;
+ u = g_strconcat("https://api.soundcloud.com/tracks.json?q=",
+ rest, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "url/", 4) == 0) {
+ const char *rest = uri + 4;
/* Translate to soundcloud resolver call. libcurl will automatically
follow the redirect to the right resource. */
u = soundcloud_resolve(rest);
}
- g_free(s);
if (u == nullptr) {
LogWarning(soundcloud_domain, "unknown soundcloud URI");
return nullptr;
}
- yajl_handle hand;
struct parse_data data;
-
data.got_url = 0;
data.title = nullptr;
data.stream_url = nullptr;
#ifdef HAVE_YAJL1
- hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, (void *) &data);
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
+ &data);
#else
- hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data);
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
#endif
int ret = soundcloud_parse_json(u, hand, mutex, cond);
g_free(u);
yajl_free(hand);
- if (data.title != nullptr)
- g_free(data.title);
- if (data.stream_url != nullptr)
- g_free(data.stream_url);
+ g_free(data.title);
+ g_free(data.stream_url);
if (ret == -1)
return nullptr;
diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
index 7c121328c..b355b477a 100644
--- a/src/playlist/SoundCloudPlaylistPlugin.hxx
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
index dcfab5a80..5b6010b53 100644
--- a/src/playlist/XspfPlaylistPlugin.cxx
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,17 +19,16 @@
#include "config.h"
#include "XspfPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "tag/Tag.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "lib/expat/ExpatParser.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
#include <string.h>
static constexpr Domain xspf_domain("xspf");
@@ -42,7 +41,7 @@ struct XspfParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
- std::forward_list<SongPointer> songs;
+ std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
@@ -57,24 +56,22 @@ struct XspfParser {
* valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
* is no (known) tag.
*/
- TagType tag;
+ TagType tag_type;
/**
- * The current song. It is allocated after the "location"
- * element.
+ * The current song URI. It is set by the "location" element.
*/
- Song *song;
+ std::string location;
+
+ TagBuilder tag_builder;
XspfParser()
:state(ROOT) {}
};
-static void
-xspf_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gcc_unused const gchar **attribute_names,
- gcc_unused const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+xspf_start_element(void *user_data, const XML_Char *element_name,
+ gcc_unused const XML_Char **atts)
{
XspfParser *parser = (XspfParser *)user_data;
@@ -94,8 +91,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
case XspfParser::TRACKLIST:
if (strcmp(element_name, "track") == 0) {
parser->state = XspfParser::TRACK;
- parser->song = nullptr;
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
break;
@@ -104,17 +101,17 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
if (strcmp(element_name, "location") == 0)
parser->state = XspfParser::LOCATION;
else if (strcmp(element_name, "title") == 0)
- parser->tag = TAG_TITLE;
+ parser->tag_type = TAG_TITLE;
else if (strcmp(element_name, "creator") == 0)
/* TAG_COMPOSER would be more correct
according to the XSPF spec */
- parser->tag = TAG_ARTIST;
+ parser->tag_type = TAG_ARTIST;
else if (strcmp(element_name, "annotation") == 0)
- parser->tag = TAG_COMMENT;
+ parser->tag_type = TAG_COMMENT;
else if (strcmp(element_name, "album") == 0)
- parser->tag = TAG_ALBUM;
+ parser->tag_type = TAG_ALBUM;
else if (strcmp(element_name, "trackNum") == 0)
- parser->tag = TAG_TRACK;
+ parser->tag_type = TAG_TRACK;
break;
@@ -123,10 +120,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
}
}
-static void
-xspf_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+xspf_end_element(void *user_data, const XML_Char *element_name)
{
XspfParser *parser = (XspfParser *)user_data;
@@ -148,12 +143,13 @@ xspf_end_element(gcc_unused GMarkupParseContext *context,
case XspfParser::TRACK:
if (strcmp(element_name, "track") == 0) {
- if (parser->song != nullptr)
- parser->songs.emplace_front(parser->song);
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
parser->state = XspfParser::TRACKLIST;
} else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
break;
@@ -163,10 +159,8 @@ xspf_end_element(gcc_unused GMarkupParseContext *context,
}
}
-static void
-xspf_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
+static void XMLCALL
+xspf_char_data(void *user_data, const XML_Char *s, int len)
{
XspfParser *parser = (XspfParser *)user_data;
@@ -177,43 +171,19 @@ xspf_text(gcc_unused GMarkupParseContext *context,
break;
case XspfParser::TRACK:
- if (parser->song != nullptr &&
- parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag, text, text_len);
- }
+ if (!parser->location.empty() &&
+ parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
break;
case XspfParser::LOCATION:
- if (parser->song == nullptr) {
- char *uri = g_strndup(text, text_len);
- parser->song = Song::NewRemote(uri);
- g_free(uri);
- }
+ parser->location.assign(s, len);
break;
}
}
-static const GMarkupParser xspf_parser = {
- xspf_start_element,
- xspf_end_element,
- xspf_text,
- nullptr,
- nullptr,
-};
-
-static void
-xspf_parser_destroy(gpointer data)
-{
- XspfParser *parser = (XspfParser *)data;
-
- if (parser->state >= XspfParser::TRACK && parser->song != nullptr)
- parser->song->Free();
-}
-
/*
* The playlist object
*
@@ -223,58 +193,21 @@ static SongEnumerator *
xspf_open_stream(InputStream &is)
{
XspfParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the XSPF XML file */
-
- context = g_markup_parse_context_new(&xspf_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, xspf_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(xspf_start_element, xspf_end_element);
+ expat.SetCharacterDataHandler(xspf_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
return nullptr;
}
}
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
+ return new MemorySongEnumerator(std::move(parser.songs));
}
static const char *const xspf_suffixes[] = {
diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx
index fc9bbd2c6..6b08a6be6 100644
--- a/src/playlist/XspfPlaylistPlugin.hxx
+++ b/src/playlist/plugins/XspfPlaylistPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify