RssPlaylistPlugin.cxx 4.01 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * 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"
21
#include "RssPlaylistPlugin.hxx"
22 23
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
24
#include "tag/TagBuilder.hxx"
25
#include "util/ASCII.hxx"
26
#include "util/Error.hxx"
27
#include "lib/expat/ExpatParser.hxx"
28
#include "Log.hxx"
29 30 31 32

/**
 * This is the state object for the GLib XML parser.
 */
33
struct RssParser {
34 35 36 37
	/**
	 * The list of songs (in reverse order because that's faster
	 * while adding).
	 */
38
	std::forward_list<DetachedSong> songs;
39 40 41 42 43 44 45 46 47 48 49 50 51

	/**
	 * 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.
	 */
52
	TagType tag_type;
53 54

	/**
55
	 * The current song URI.  It is set by the "enclosure"
56 57
	 * element.
	 */
58
	std::string location;
59

60 61
	TagBuilder tag_builder;

62
	RssParser()
63
		:state(ROOT) {}
64 65
};

66 67 68
static void XMLCALL
rss_start_element(void *user_data, const XML_Char *element_name,
		  const XML_Char **atts)
69
{
70
	RssParser *parser = (RssParser *)user_data;
71 72

	switch (parser->state) {
73
	case RssParser::ROOT:
74
		if (StringEqualsCaseASCII(element_name, "item")) {
75
			parser->state = RssParser::ITEM;
76
			parser->location.clear();
77
			parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
78 79 80 81
		}

		break;

82
	case RssParser::ITEM:
83
		if (StringEqualsCaseASCII(element_name, "enclosure")) {
84 85
			const char *href =
				ExpatParser::GetAttributeCase(atts, "url");
86 87
			if (href != nullptr)
				parser->location = href;
88
		} else if (StringEqualsCaseASCII(element_name, "title"))
89
			parser->tag_type = TAG_TITLE;
90
		else if (StringEqualsCaseASCII(element_name, "itunes:author"))
91
			parser->tag_type = TAG_ARTIST;
92 93 94 95 96

		break;
	}
}

97 98
static void XMLCALL
rss_end_element(void *user_data, const XML_Char *element_name)
99
{
100
	RssParser *parser = (RssParser *)user_data;
101 102

	switch (parser->state) {
103
	case RssParser::ROOT:
104 105
		break;

106
	case RssParser::ITEM:
107
		if (StringEqualsCaseASCII(element_name, "item")) {
108 109 110
			if (!parser->location.empty())
				parser->songs.emplace_front(std::move(parser->location),
							    parser->tag_builder.Commit());
111

112
			parser->state = RssParser::ROOT;
113
		} else
114
			parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
115 116 117 118 119

		break;
	}
}

120 121
static void XMLCALL
rss_char_data(void *user_data, const XML_Char *s, int len)
122
{
123
	RssParser *parser = (RssParser *)user_data;
124 125

	switch (parser->state) {
126
	case RssParser::ROOT:
127 128
		break;

129
	case RssParser::ITEM:
130
		if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
131
			parser->tag_builder.AddItem(parser->tag_type, s, len);
132 133 134 135 136 137 138 139 140 141

		break;
	}
}

/*
 * The playlist object
 *
 */

142
static SongEnumerator *
143
rss_open_stream(InputStream &is)
144
{
145
	RssParser parser;
146

147 148 149 150
	{
		ExpatParser expat(&parser);
		expat.SetElementHandler(rss_start_element, rss_end_element);
		expat.SetCharacterDataHandler(rss_char_data);
151

152 153 154
		Error error;
		if (!expat.Parse(is, error)) {
			LogError(error);
155
			return nullptr;
156 157 158
		}
	}

159
	parser.songs.reverse();
160
	return new MemorySongEnumerator(std::move(parser.songs));
161 162 163 164
}

static const char *const rss_suffixes[] = {
	"rss",
165
	nullptr
166 167 168 169 170
};

static const char *const rss_mime_types[] = {
	"application/rss+xml",
	"text/xml",
171
	nullptr
172 173 174
};

const struct playlist_plugin rss_playlist_plugin = {
175
	"rss",
176

177 178 179 180
	nullptr,
	nullptr,
	nullptr,
	rss_open_stream,
181

182 183 184
	nullptr,
	rss_suffixes,
	rss_mime_types,
185
};