playlist/pls: custom INI parser

Don't use GLib.
This commit is contained in:
Max Kellermann 2014-12-04 22:58:30 +01:00
parent 4b70f9d213
commit aa4c7055f8
3 changed files with 111 additions and 97 deletions

View File

@ -1397,6 +1397,8 @@ libplaylist_plugins_a_SOURCES = \
src/playlist/plugins/CuePlaylistPlugin.hxx \ src/playlist/plugins/CuePlaylistPlugin.hxx \
src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \ src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \
src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx \ src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx \
src/playlist/plugins/PlsPlaylistPlugin.cxx \
src/playlist/plugins/PlsPlaylistPlugin.hxx \
src/playlist/PlaylistRegistry.cxx src/playlist/PlaylistRegistry.hxx src/playlist/PlaylistRegistry.cxx src/playlist/PlaylistRegistry.hxx
libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(EXPAT_CFLAGS) \ $(EXPAT_CFLAGS) \
@ -1434,12 +1436,6 @@ libplaylist_plugins_a_SOURCES += \
src/playlist/plugins/RssPlaylistPlugin.hxx src/playlist/plugins/RssPlaylistPlugin.hxx
endif endif
if HAVE_GLIB
libplaylist_plugins_a_SOURCES += \
src/playlist/plugins/PlsPlaylistPlugin.cxx \
src/playlist/plugins/PlsPlaylistPlugin.hxx
endif
# #
# Filter plugins # Filter plugins
# #

View File

@ -45,10 +45,7 @@
const struct playlist_plugin *const playlist_plugins[] = { const struct playlist_plugin *const playlist_plugins[] = {
&extm3u_playlist_plugin, &extm3u_playlist_plugin,
&m3u_playlist_plugin, &m3u_playlist_plugin,
#ifdef HAVE_GLIB
// TODO: enable without GLib
&pls_playlist_plugin, &pls_playlist_plugin,
#endif
#ifdef ENABLE_EXPAT #ifdef ENABLE_EXPAT
&xspf_playlist_plugin, &xspf_playlist_plugin,
&asx_playlist_plugin, &asx_playlist_plugin,

View File

@ -21,121 +21,142 @@
#include "PlsPlaylistPlugin.hxx" #include "PlsPlaylistPlugin.hxx"
#include "../PlaylistPlugin.hxx" #include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx" #include "../MemorySongEnumerator.hxx"
#include "input/InputStream.hxx" #include "input/TextInputStream.hxx"
#include "DetachedSong.hxx" #include "DetachedSong.hxx"
#include "tag/TagBuilder.hxx" #include "tag/TagBuilder.hxx"
#include "util/ASCII.hxx"
#include "util/StringUtil.hxx"
#include "util/DivideString.hxx"
#include "util/Error.hxx" #include "util/Error.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx"
#include <glib.h>
#include <string> #include <string>
#include <stdio.h>
#include <stdio.h> #include <stdlib.h>
static constexpr Domain pls_domain("pls"); static constexpr Domain pls_domain("pls");
static void static bool
pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs) FindPlaylistSection(TextInputStream &is)
{ {
gchar *value; char *line;
GError *error = nullptr; while ((line = is.ReadLine()) != nullptr) {
int num_entries = g_key_file_get_integer(keyfile, "playlist", line = Strip(line);
"NumberOfEntries", &error); if (StringEqualsCaseASCII(line, "[playlist]"))
if (error) { return true;
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 */ return false;
num_entries = g_key_file_get_integer(keyfile, "playlist", }
"numberofentries", &error);
if (error) { static bool
g_error_free(error); ParsePls(TextInputStream &is, std::forward_list<DetachedSong> &songs)
error = nullptr; {
assert(songs.empty());
if (!FindPlaylistSection(is))
return false;
unsigned n_entries = 0;
struct Entry {
std::string file, title;
int length;
Entry():length(-1) {}
};
static constexpr unsigned MAX_ENTRIES = 65536;
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);
}
} }
} }
for (; num_entries > 0; --num_entries) { if (n_entries == 0)
char key[64]; /* no "NumberOfEntries" found */
sprintf(key, "File%u", num_entries); return false;
char *uri = g_key_file_get_string(keyfile, "playlist", key,
&error); auto i = songs.before_begin();
if(error) { for (const auto &entry : entries) {
FormatError(pls_domain, "Invalid PLS entry %s: '%s'", const char *uri = entry.file.c_str();
key, error->message);
g_error_free(error);
return;
}
TagBuilder tag; TagBuilder tag;
if (!entry.title.empty())
tag.AddItem(TAG_TITLE, entry.title.c_str());
sprintf(key, "Title%u", num_entries); if (entry.length > 0)
value = g_key_file_get_string(keyfile, "playlist", key, tag.SetDuration(SignedSongTime::FromS(entry.length));
nullptr);
if (value != nullptr)
tag.AddItem(TAG_TITLE, value);
g_free(value); i = songs.emplace_after(i, uri, tag.Commit());
sprintf(key, "Length%u", num_entries);
int length = g_key_file_get_integer(keyfile, "playlist", key,
nullptr);
if (length > 0)
tag.SetDuration(SignedSongTime::FromS(length));
songs.emplace_front(uri, tag.Commit());
g_free(uri);
} }
return true;
}
static bool
ParsePls(InputStream &is, std::forward_list<DetachedSong> &songs)
{
TextInputStream tis(is);
return ParsePls(tis, songs);
} }
static SongEnumerator * static SongEnumerator *
pls_open_stream(InputStream &is) pls_open_stream(InputStream &is)
{ {
GError *error = nullptr;
Error error2;
std::string kf_data;
do {
char buffer[1024];
size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
if (nbytes == 0) {
if (error2.IsDefined()) {
LogError(error2);
return nullptr;
}
break;
}
kf_data.append(buffer, nbytes);
/* Limit to 64k */
} while (kf_data.length() < 65536);
if (kf_data.empty()) {
LogWarning(pls_domain, "KeyFile parser failed: No Data");
return nullptr;
}
GKeyFile *keyfile = g_key_file_new();
if (!g_key_file_load_from_data(keyfile,
kf_data.data(), kf_data.length(),
G_KEY_FILE_NONE, &error)) {
FormatError(pls_domain,
"KeyFile parser failed: %s", error->message);
g_error_free(error);
g_key_file_free(keyfile);
return nullptr;
}
std::forward_list<DetachedSong> songs; std::forward_list<DetachedSong> songs;
pls_parser(keyfile, songs); if (!ParsePls(is, songs))
g_key_file_free(keyfile); return nullptr;
return new MemorySongEnumerator(std::move(songs)); return new MemorySongEnumerator(std::move(songs));
} }