aboutsummaryrefslogtreecommitdiffstats
path: root/src/playlist
diff options
context:
space:
mode:
Diffstat (limited to 'src/playlist')
-rw-r--r--src/playlist/PlaylistRegistry.cxx7
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.cxx2
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.cxx193
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx2
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.cxx102
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.cxx2
6 files changed, 149 insertions, 159 deletions
diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx
index 4e9ef890e..ad0afa628 100644
--- a/src/playlist/PlaylistRegistry.cxx
+++ b/src/playlist/PlaylistRegistry.cxx
@@ -45,11 +45,8 @@
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
+#ifdef ENABLE_EXPAT
&xspf_playlist_plugin,
&asx_playlist_plugin,
&rss_playlist_plugin,
@@ -60,8 +57,10 @@ const struct playlist_plugin *const playlist_plugins[] = {
#ifdef ENABLE_SOUNDCLOUD
&soundcloud_playlist_plugin,
#endif
+#ifdef ENABLE_CUE
&cue_playlist_plugin,
&embcue_playlist_plugin,
+#endif
nullptr
};
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx
index 3185a8144..59943681a 100644
--- a/src/playlist/plugins/AsxPlaylistPlugin.cxx
+++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx
@@ -28,7 +28,7 @@
#include "Log.hxx"
/**
- * This is the state object for the GLib XML parser.
+ * This is the state object for our XML parser.
*/
struct AsxParser {
/**
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx
index f7724f522..4f7c5a1ff 100644
--- a/src/playlist/plugins/PlsPlaylistPlugin.cxx
+++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx
@@ -21,121 +21,142 @@
#include "PlsPlaylistPlugin.hxx"
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
-#include "input/InputStream.hxx"
+#include "input/TextInputStream.hxx"
#include "DetachedSong.hxx"
#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/StringUtil.hxx"
+#include "util/DivideString.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
#include <string>
-#include <stdio.h>
-#include <stdio.h>
+#include <stdlib.h>
static constexpr Domain pls_domain("pls");
-static void
-pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
+static bool
+FindPlaylistSection(TextInputStream &is)
{
- gchar *value;
- GError *error = nullptr;
- int num_entries = g_key_file_get_integer(keyfile, "playlist",
- "NumberOfEntries", &error);
- if (error) {
- FormatError(pls_domain,
- "Invalid PLS file: '%s'", error->message);
- g_error_free(error);
- error = nullptr;
-
- /* Hack to work around shoutcast failure to comform to spec */
- num_entries = g_key_file_get_integer(keyfile, "playlist",
- "numberofentries", &error);
- if (error) {
- g_error_free(error);
- error = nullptr;
- }
+ char *line;
+ while ((line = is.ReadLine()) != nullptr) {
+ line = Strip(line);
+ if (StringEqualsCaseASCII(line, "[playlist]"))
+ return true;
}
- for (; num_entries > 0; --num_entries) {
- char key[64];
- sprintf(key, "File%u", num_entries);
- char *uri = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error) {
- FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
- key, error->message);
- g_error_free(error);
- return;
- }
+ return false;
+}
- TagBuilder tag;
+static bool
+ParsePls(TextInputStream &is, std::forward_list<DetachedSong> &songs)
+{
+ assert(songs.empty());
- sprintf(key, "Title%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- nullptr);
- if (value != nullptr)
- tag.AddItem(TAG_TITLE, value);
+ if (!FindPlaylistSection(is))
+ return false;
- g_free(value);
+ unsigned n_entries = 0;
- sprintf(key, "Length%u", num_entries);
- int length = g_key_file_get_integer(keyfile, "playlist", key,
- nullptr);
- if (length > 0)
- tag.SetDuration(SignedSongTime::FromS(length));
+ struct Entry {
+ std::string file, title;
+ int length;
- songs.emplace_front(uri, tag.Commit());
- g_free(uri);
- }
+ Entry():length(-1) {}
+ };
-}
+ static constexpr unsigned MAX_ENTRIES = 65536;
-static SongEnumerator *
-pls_open_stream(InputStream &is)
-{
- GError *error = nullptr;
- Error error2;
-
- std::string kf_data;
-
- do {
- char buffer[1024];
- size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- LogError(error2);
- return nullptr;
- }
+ std::vector<Entry> entries;
+
+ char *line;
+ while ((line = is.ReadLine()) != nullptr) {
+ line = Strip(line);
+ if (*line == 0 || *line == ';')
+ continue;
+
+ if (*line == '[')
+ /* another section starts; we only want
+ [Playlist], so stop here */
break;
+
+ const DivideString ds(line, '=', true);
+ if (!ds.IsDefined())
+ continue;
+
+ const char *const name = ds.GetFirst();
+ const char *const value = ds.GetSecond();
+
+ if (StringEqualsCaseASCII(name, "NumberOfEntries")) {
+ n_entries = strtoul(value, nullptr, 10);
+ if (n_entries == 0)
+ /* empty file - nothing remains to be
+ done */
+ return true;
+
+ if (n_entries > MAX_ENTRIES)
+ n_entries = MAX_ENTRIES;
+ entries.resize(n_entries);
+ } else if (StringEqualsCaseASCII(name, "File", 4)) {
+ unsigned i = strtoul(name + 4, nullptr, 10);
+ if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) {
+ if (entries.size() < i)
+ entries.resize(i);
+ entries[i - 1].file = value;
+ }
+ } else if (StringEqualsCaseASCII(name, "Title", 5)) {
+ unsigned i = strtoul(name + 5, nullptr, 10);
+ if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) {
+ if (entries.size() < i)
+ entries.resize(i);
+ entries[i - 1].title = value;
+ }
+ } else if (StringEqualsCaseASCII(name, "Length", 6)) {
+ unsigned i = strtoul(name + 6, nullptr, 10);
+ if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) {
+ if (entries.size() < i)
+ entries.resize(i);
+ entries[i - 1].length = atoi(value);
+ }
}
+ }
- kf_data.append(buffer, nbytes);
- /* Limit to 64k */
- } while (kf_data.length() < 65536);
+ if (n_entries == 0)
+ /* no "NumberOfEntries" found */
+ return false;
- if (kf_data.empty()) {
- LogWarning(pls_domain, "KeyFile parser failed: No Data");
- return nullptr;
- }
+ auto i = songs.before_begin();
+ for (const auto &entry : entries) {
+ const char *uri = entry.file.c_str();
- GKeyFile *keyfile = g_key_file_new();
- if (!g_key_file_load_from_data(keyfile,
- kf_data.data(), kf_data.length(),
- G_KEY_FILE_NONE, &error)) {
- FormatError(pls_domain,
- "KeyFile parser failed: %s", error->message);
- g_error_free(error);
- g_key_file_free(keyfile);
- return nullptr;
+ TagBuilder tag;
+ if (!entry.title.empty())
+ tag.AddItem(TAG_TITLE, entry.title.c_str());
+
+ if (entry.length > 0)
+ tag.SetDuration(SignedSongTime::FromS(entry.length));
+
+ i = songs.emplace_after(i, uri, tag.Commit());
}
+ return true;
+}
+
+static bool
+ParsePls(InputStream &is, std::forward_list<DetachedSong> &songs)
+{
+ TextInputStream tis(is);
+ return ParsePls(tis, songs);
+}
+
+static SongEnumerator *
+pls_open_stream(InputStream &is)
+{
std::forward_list<DetachedSong> songs;
- pls_parser(keyfile, songs);
- g_key_file_free(keyfile);
+ if (!ParsePls(is, songs))
+ return nullptr;
return new MemorySongEnumerator(std::move(songs));
}
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
index 6f9aad54b..3d32b29ca 100644
--- a/src/playlist/plugins/RssPlaylistPlugin.cxx
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -28,7 +28,7 @@
#include "Log.hxx"
/**
- * This is the state object for the GLib XML parser.
+ * This is the state object for the our XML parser.
*/
struct RssParser {
/**
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
index ec4d240a5..6be9c65ab 100644
--- a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -25,16 +25,17 @@
#include "input/InputStream.hxx"
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
+#include "util/Alloc.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <yajl/yajl_parse.h>
#include <string>
#include <string.h>
+#include <stdlib.h>
static struct {
std::string apikey;
@@ -60,7 +61,7 @@ soundcloud_init(const config_param &param)
/**
* Construct a full soundcloud resolver URL from the given fragment.
* @param uri uri of a soundcloud page (or just the path)
- * @return Constructed URL. Must be freed with g_free.
+ * @return Constructed URL. Must be freed with free().
*/
static char *
soundcloud_resolve(const char* uri)
@@ -68,18 +69,18 @@ soundcloud_resolve(const char* uri)
char *u, *ru;
if (StringStartsWith(uri, "https://")) {
- u = g_strdup(uri);
+ u = xstrdup(uri);
} else if (StringStartsWith(uri, "soundcloud.com")) {
- u = g_strconcat("https://", uri, nullptr);
+ u = xstrcatdup("https://", uri);
} else {
/* assume it's just a path on soundcloud.com */
- u = g_strconcat("https://soundcloud.com/", uri, nullptr);
+ u = xstrcatdup("https://soundcloud.com/", uri);
}
- ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
- u, "&client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- g_free(u);
+ ru = xstrcatdup("https://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=",
+ soundcloud_config.apikey.c_str());
+ free(u);
return ru;
}
@@ -111,12 +112,7 @@ struct parse_data {
};
static int
-handle_integer(void *ctx,
- long
-#ifndef HAVE_YAJL1
- long
-#endif
- intval)
+handle_integer(void *ctx, long long intval)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -132,25 +128,19 @@ handle_integer(void *ctx,
}
static int
-handle_string(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
+handle_string(void *ctx, const unsigned char *stringval, size_t stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
const char *s = (const char *) stringval;
switch (data->key) {
case Title:
- g_free(data->title);
- data->title = g_strndup(s, stringlen);
+ free(data->title);
+ data->title = xstrndup(s, stringlen);
break;
case Stream_URL:
- g_free(data->stream_url);
- data->stream_url = g_strndup(s, stringlen);
+ free(data->stream_url);
+ data->stream_url = xstrndup(s, stringlen);
data->got_url = 1;
break;
default:
@@ -161,13 +151,7 @@ handle_string(void *ctx, const unsigned char* stringval,
}
static int
-handle_mapkey(void *ctx, const unsigned char* stringval,
-#ifdef HAVE_YAJL1
- unsigned int
-#else
- size_t
-#endif
- stringlen)
+handle_mapkey(void *ctx, const unsigned char *stringval, size_t stringlen)
{
struct parse_data *data = (struct parse_data *) ctx;
@@ -211,8 +195,8 @@ handle_end_map(void *ctx)
/* got_url == 1, track finished, make it into a song */
data->got_url = 0;
- char *u = g_strconcat(data->stream_url, "?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ char *u = xstrcatdup(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str());
TagBuilder tag;
tag.SetDuration(SignedSongTime::FromMS(data->duration));
@@ -220,7 +204,7 @@ handle_end_map(void *ctx)
tag.AddItem(TAG_NAME, data->title);
data->songs.emplace_front(u, tag.Commit());
- g_free(u);
+ free(u);
return 1;
}
@@ -282,20 +266,11 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
}
if (done) {
-#ifdef HAVE_YAJL1
- stat = yajl_parse_complete(hand);
-#else
stat = yajl_complete_parse(hand);
-#endif
} else
stat = yajl_parse(hand, ubuffer, nbytes);
- if (stat != yajl_status_ok
-#ifdef HAVE_YAJL1
- && stat != yajl_status_insufficient_data
-#endif
- )
- {
+ if (stat != yajl_status_ok) {
unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
LogError(soundcloud_domain, (const char *)str);
yajl_free_error(hand, str);
@@ -325,24 +300,24 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
char *u = nullptr;
if (memcmp(uri, "track/", 6) == 0) {
const char *rest = uri + 6;
- u = g_strconcat("https://api.soundcloud.com/tracks/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
+ u = xstrcatdup("https://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str());
} 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);
+ u = xstrcatdup("https://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str());
} 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);
+ u = xstrcatdup("https://api.soundcloud.com/users/",
+ rest, "/tracks.json?client_id=",
+ soundcloud_config.apikey.c_str());
} 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);
+ u = xstrcatdup("https://api.soundcloud.com/tracks.json?q=",
+ rest, "&client_id=",
+ soundcloud_config.apikey.c_str());
} else if (memcmp(uri, "url/", 4) == 0) {
const char *rest = uri + 4;
/* Translate to soundcloud resolver call. libcurl will automatically
@@ -359,19 +334,14 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
data.got_url = 0;
data.title = nullptr;
data.stream_url = nullptr;
-#ifdef HAVE_YAJL1
- yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
- &data);
-#else
yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
-#endif
int ret = soundcloud_parse_json(u, hand, mutex, cond);
- g_free(u);
+ free(u);
yajl_free(hand);
- g_free(data.title);
- g_free(data.stream_url);
+ free(data.title);
+ free(data.stream_url);
if (ret == -1)
return nullptr;
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
index 5b6010b53..01a157ba8 100644
--- a/src/playlist/plugins/XspfPlaylistPlugin.cxx
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -34,7 +34,7 @@
static constexpr Domain xspf_domain("xspf");
/**
- * This is the state object for the GLib XML parser.
+ * This is the state object for our XML parser.
*/
struct XspfParser {
/**