From aa4c7055f8e0746bd725ffc0cb140373fabcc405 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 4 Dec 2014 22:58:30 +0100 Subject: playlist/pls: custom INI parser Don't use GLib. --- Makefile.am | 8 +- src/playlist/PlaylistRegistry.cxx | 3 - src/playlist/plugins/PlsPlaylistPlugin.cxx | 193 ++++++++++++++++------------- 3 files changed, 109 insertions(+), 95 deletions(-) diff --git a/Makefile.am b/Makefile.am index 4aa1061e5..7111014c2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1397,6 +1397,8 @@ libplaylist_plugins_a_SOURCES = \ src/playlist/plugins/CuePlaylistPlugin.hxx \ src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \ src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx \ + src/playlist/plugins/PlsPlaylistPlugin.cxx \ + src/playlist/plugins/PlsPlaylistPlugin.hxx \ src/playlist/PlaylistRegistry.cxx src/playlist/PlaylistRegistry.hxx libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(EXPAT_CFLAGS) \ @@ -1434,12 +1436,6 @@ libplaylist_plugins_a_SOURCES += \ src/playlist/plugins/RssPlaylistPlugin.hxx endif -if HAVE_GLIB -libplaylist_plugins_a_SOURCES += \ - src/playlist/plugins/PlsPlaylistPlugin.cxx \ - src/playlist/plugins/PlsPlaylistPlugin.hxx -endif - # # Filter plugins # diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx index aed8e1567..d7bc3600a 100644 --- a/src/playlist/PlaylistRegistry.cxx +++ b/src/playlist/PlaylistRegistry.cxx @@ -45,10 +45,7 @@ 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 ENABLE_EXPAT &xspf_playlist_plugin, &asx_playlist_plugin, 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 #include -#include -#include +#include static constexpr Domain pls_domain("pls"); -static void -pls_parser(GKeyFile *keyfile, std::forward_list &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 &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 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 &songs) +{ + TextInputStream tis(is); + return ParsePls(tis, songs); +} + +static SongEnumerator * +pls_open_stream(InputStream &is) +{ std::forward_list songs; - pls_parser(keyfile, songs); - g_key_file_free(keyfile); + if (!ParsePls(is, songs)) + return nullptr; return new MemorySongEnumerator(std::move(songs)); } -- cgit v1.2.3