diff options
Diffstat (limited to 'src/playlist')
-rw-r--r-- | src/playlist/AsxPlaylistPlugin.cxx | 177 | ||||
-rw-r--r-- | src/playlist/AsxPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/CuePlaylistPlugin.cxx | 13 | ||||
-rw-r--r-- | src/playlist/CuePlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/DespotifyPlaylistPlugin.cxx | 26 | ||||
-rw-r--r-- | src/playlist/DespotifyPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/EmbeddedCuePlaylistPlugin.cxx | 26 | ||||
-rw-r--r-- | src/playlist/EmbeddedCuePlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/ExtM3uPlaylistPlugin.cxx | 42 | ||||
-rw-r--r-- | src/playlist/ExtM3uPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/M3uPlaylistPlugin.cxx | 10 | ||||
-rw-r--r-- | src/playlist/M3uPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/PlsPlaylistPlugin.cxx | 72 | ||||
-rw-r--r-- | src/playlist/PlsPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/RssPlaylistPlugin.cxx | 174 | ||||
-rw-r--r-- | src/playlist/RssPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/SoundCloudPlaylistPlugin.cxx | 159 | ||||
-rw-r--r-- | src/playlist/SoundCloudPlaylistPlugin.hxx | 2 | ||||
-rw-r--r-- | src/playlist/XspfPlaylistPlugin.cxx | 149 | ||||
-rw-r--r-- | src/playlist/XspfPlaylistPlugin.hxx | 2 |
20 files changed, 276 insertions, 592 deletions
diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx index 94198b8c3..7c988a539 100644 --- a/src/playlist/AsxPlaylistPlugin.cxx +++ b/src/playlist/AsxPlaylistPlugin.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 @@ -21,21 +21,13 @@ #include "AsxPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" #include "Song.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/ASCII.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" +#include "Expat.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. */ @@ -44,7 +36,7 @@ struct AsxParser { * 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. @@ -58,36 +50,23 @@ struct AsxParser { * valid if state==ENTRY. 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 "ref" element. */ - Song *song; + std::string location; + + TagBuilder tag_builder; 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) +static void XMLCALL +asx_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) { AsxParser *parser = (AsxParser *)user_data; @@ -95,48 +74,31 @@ asx_start_element(gcc_unused GMarkupParseContext *context, case AsxParser::ROOT: if (StringEqualsCaseASCII(element_name, "entry")) { parser->state = AsxParser::ENTRY; - parser->song = Song::NewRemote("asx:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->location.clear(); + parser->tag_type = 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; - } + 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 = TAG_ARTIST; + parser->tag_type = TAG_ARTIST; else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; + parser->tag_type = TAG_TITLE; break; } } -static void -asx_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +asx_end_element(void *user_data, const XML_Char *element_name) { AsxParser *parser = (AsxParser *)user_data; @@ -146,23 +108,20 @@ asx_end_element(gcc_unused GMarkupParseContext *context, 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(); + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); parser->state = AsxParser::ROOT; } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->tag_type = 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) +static void XMLCALL +asx_char_data(void *user_data, const XML_Char *s, int len) { AsxParser *parser = (AsxParser *)user_data; @@ -171,34 +130,13 @@ asx_text(gcc_unused GMarkupParseContext *context, 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); - } + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, 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 * @@ -208,58 +146,21 @@ 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); + { + 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; } } - 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; + return new MemorySongEnumerator(std::move(parser.songs)); } static const char *const asx_suffixes[] = { diff --git a/src/playlist/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx index 240c1824a..63371be0f 100644 --- a/src/playlist/AsxPlaylistPlugin.hxx +++ b/src/playlist/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/CuePlaylistPlugin.cxx index 42a43bbad..505c0c5d8 100644 --- a/src/playlist/CuePlaylistPlugin.cxx +++ b/src/playlist/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 @@ -21,13 +21,10 @@ #include "CuePlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "SongEnumerator.hxx" -#include "tag/Tag.hxx" -#include "Song.hxx" #include "cue/CueParser.hxx" #include "TextInputStream.hxx" -#include <assert.h> -#include <string.h> +#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,10 +45,10 @@ 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; diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx index cf5e3a8f0..4d833bfc2 100644 --- a/src/playlist/CuePlaylistPlugin.hxx +++ b/src/playlist/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/DespotifyPlaylistPlugin.cxx index a1a865c08..7d73a64bc 100644 --- a/src/playlist/DespotifyPlaylistPlugin.cxx +++ b/src/playlist/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 @@ -23,7 +23,7 @@ #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" #include "tag/Tag.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "Log.hxx" extern "C" { @@ -34,10 +34,9 @@ extern "C" { #include <stdlib.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 +44,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 +78,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 +99,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/DespotifyPlaylistPlugin.hxx index c1e5b7f39..6acfd40f4 100644 --- a/src/playlist/DespotifyPlaylistPlugin.hxx +++ b/src/playlist/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/EmbeddedCuePlaylistPlugin.cxx index d758650eb..4d9eb4b9b 100644 --- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx +++ b/src/playlist/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 @@ -27,18 +27,16 @@ #include "EmbeddedCuePlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "SongEnumerator.hxx" -#include "tag/Tag.hxx" #include "tag/TagHandler.hxx" #include "tag/TagId3.hxx" #include "tag/ApeTag.hxx" -#include "Song.hxx" +#include "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/EmbeddedCuePlaylistPlugin.hxx index e306730f4..5eedf3f13 100644 --- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx +++ b/src/playlist/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/ExtM3uPlaylistPlugin.cxx index 8d260fec7..1a975c081 100644 --- a/src/playlist/ExtM3uPlaylistPlugin.cxx +++ b/src/playlist/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 @@ -21,13 +21,12 @@ #include "ExtM3uPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "SongEnumerator.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/StringUtil.hxx" #include "TextInputStream.hxx" -#include <glib.h> - #include <string.h> #include <stdlib.h> @@ -45,7 +44,7 @@ public: strcmp(line.c_str(), "#EXTM3U") == 0; } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -68,18 +67,17 @@ 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 */ @@ -89,38 +87,34 @@ extm3u_parse_tag(const char *line) 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.SetTime(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; + Tag tag; std::string line; const char *line_s; - Song *song; do { - if (!tis.ReadLine(line)) { - delete tag; + if (!tis.ReadLine(line)) return NULL; - } - + line_s = line.c_str(); - if (g_str_has_prefix(line_s, "#EXTINF:")) { - delete tag; + if (StringStartsWith(line_s, "#EXTINF:")) { tag = extm3u_parse_tag(line_s + 8); continue; } @@ -128,9 +122,7 @@ ExtM3uPlaylist::NextSong() line_s = strchug_fast(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[] = { diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx index 844fba15c..5743ded43 100644 --- a/src/playlist/ExtM3uPlaylistPlugin.hxx +++ b/src/playlist/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/M3uPlaylistPlugin.cxx index 3f99bdfdf..fe7d8a17f 100644 --- a/src/playlist/M3uPlaylistPlugin.cxx +++ b/src/playlist/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 @@ -21,7 +21,7 @@ #include "M3uPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "SongEnumerator.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "util/StringUtil.hxx" #include "TextInputStream.hxx" @@ -33,7 +33,7 @@ public: :tis(is) { } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -42,7 +42,7 @@ m3u_open_stream(InputStream &is) return new M3uPlaylist(is); } -Song * +DetachedSong * M3uPlaylist::NextSong() { std::string line; @@ -56,7 +56,7 @@ M3uPlaylist::NextSong() line_s = strchug_fast(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/M3uPlaylistPlugin.hxx index a2058bb29..f1ad14069 100644 --- a/src/playlist/M3uPlaylistPlugin.hxx +++ b/src/playlist/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/PlsPlaylistPlugin.cxx index 99be3ad35..839098a73 100644 --- a/src/playlist/PlsPlaylistPlugin.cxx +++ b/src/playlist/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 @@ -22,8 +22,8 @@ #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" #include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" +#include "DetachedSong.hxx" +#include "tag/TagBuilder.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -32,13 +32,14 @@ #include <string> +#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); @@ -57,13 +58,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); @@ -71,36 +70,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.SetTime(length); - songs.emplace_front(song); - num_entries--; + songs.emplace_front(uri, tag.Commit()); + g_free(uri); } } @@ -110,15 +97,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); @@ -137,12 +121,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); @@ -150,7 +132,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/PlsPlaylistPlugin.hxx index 3fafd36d0..1a3f33873 100644 --- a/src/playlist/PlsPlaylistPlugin.hxx +++ b/src/playlist/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/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx index e2a44bfd3..253ff7ad2 100644 --- a/src/playlist/RssPlaylistPlugin.cxx +++ b/src/playlist/RssPlaylistPlugin.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 @@ -21,21 +21,13 @@ #include "RssPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" #include "Song.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/ASCII.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" +#include "Expat.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. */ @@ -44,7 +36,7 @@ struct RssParser { * 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. @@ -58,35 +50,23 @@ struct RssParser { * valid if state==ITEM. 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" + * The current song URI. It is set by the "enclosure" * element. */ - Song *song; + std::string location; + + TagBuilder tag_builder; 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) +static void XMLCALL +rss_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) { RssParser *parser = (RssParser *)user_data; @@ -94,46 +74,29 @@ rss_start_element(gcc_unused GMarkupParseContext *context, case RssParser::ROOT: if (StringEqualsCaseASCII(element_name, "item")) { parser->state = RssParser::ITEM; - parser->song = Song::NewRemote("rss:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->location.clear(); + parser->tag_type = 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; - } + const char *href = + ExpatParser::GetAttributeCase(atts, "url"); + if (href != nullptr) + parser->location = href; } else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; + parser->tag_type = TAG_TITLE; else if (StringEqualsCaseASCII(element_name, "itunes:author")) - parser->tag = TAG_ARTIST; + parser->tag_type = TAG_ARTIST; break; } } -static void -rss_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +rss_end_element(void *user_data, const XML_Char *element_name) { RssParser *parser = (RssParser *)user_data; @@ -143,23 +106,20 @@ rss_end_element(gcc_unused GMarkupParseContext *context, 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(); + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); parser->state = RssParser::ROOT; } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->tag_type = 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) +static void XMLCALL +rss_char_data(void *user_data, const XML_Char *s, int len) { RssParser *parser = (RssParser *)user_data; @@ -168,34 +128,13 @@ rss_text(gcc_unused GMarkupParseContext *context, 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); - } + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, 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 * @@ -205,58 +144,21 @@ 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; - } + { + ExpatParser expat(&parser); + expat.SetElementHandler(rss_start_element, rss_end_element); + expat.SetCharacterDataHandler(rss_char_data); - 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); + Error error; + if (!expat.Parse(is, error)) { + LogError(error); 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; + return new MemorySongEnumerator(std::move(parser.songs)); } static const char *const rss_suffixes[] = { diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx index f49f7e9cf..a00a5a898 100644 --- a/src/playlist/RssPlaylistPlugin.hxx +++ b/src/playlist/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/SoundCloudPlaylistPlugin.cxx index f6797b14d..b0282b5da 100644 --- a/src/playlist/SoundCloudPlaylistPlugin.cxx +++ b/src/playlist/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 @@ -24,7 +24,8 @@ #include "ConfigData.hxx" #include "InputStream.hxx" #include "Song.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -45,7 +46,8 @@ static constexpr Domain soundcloud_domain("soundcloud"); static bool soundcloud_init(const config_param ¶m) { - 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 +64,20 @@ soundcloud_init(const config_param ¶m) * @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 +108,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 +132,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 +161,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 +185,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 +196,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 +212,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.SetTime(data->duration / 1000); 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 +250,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 +260,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) { @@ -318,80 +317,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/SoundCloudPlaylistPlugin.hxx index 7c121328c..b355b477a 100644 --- a/src/playlist/SoundCloudPlaylistPlugin.hxx +++ b/src/playlist/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/XspfPlaylistPlugin.cxx index dcfab5a80..e726ad338 100644 --- a/src/playlist/XspfPlaylistPlugin.cxx +++ b/src/playlist/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 @@ -21,15 +21,14 @@ #include "XspfPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" +#include "DetachedSong.hxx" #include "InputStream.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Expat.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/XspfPlaylistPlugin.hxx index fc9bbd2c6..6b08a6be6 100644 --- a/src/playlist/XspfPlaylistPlugin.hxx +++ b/src/playlist/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 |