~ubuntu-branches/ubuntu/trusty/mpd/trusty

« back to all changes in this revision

Viewing changes to src/playlist/rss_playlist_plugin.c

  • Committer: Bazaar Package Importer
  • Author(s): Angel Abad
  • Date: 2011-02-02 12:26:30 UTC
  • mfrom: (1.5.11 upstream)
  • Revision ID: james.westby@ubuntu.com-20110202122630-bdyx8w4k94doz4fs
Tags: 0.16.1-1ubuntu1
* Merge from debian unstable. Remaining changes:
  - debian/control:
    + Don't build-depend on libmikmod2-dev (Debian bug #510675).
    + Move avahi-daemon from Suggests field to Recommends field.
  - debian/mpd.init.d:
    + Read mpd user from mpd.conf.
  - debian/control, debian/rules:
    + Add libmp3lame-dev to the build dependencies and enable lame.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2003-2010 The Music Player Daemon Project
 
3
 * http://www.musicpd.org
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License as published by
 
7
 * the Free Software Foundation; either version 2 of the License, or
 
8
 * (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License along
 
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
 
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
18
 */
 
19
 
 
20
#include "config.h"
 
21
#include "playlist/rss_playlist_plugin.h"
 
22
#include "playlist_plugin.h"
 
23
#include "input_stream.h"
 
24
#include "song.h"
 
25
#include "tag.h"
 
26
 
 
27
#include <glib.h>
 
28
 
 
29
#include <assert.h>
 
30
#include <string.h>
 
31
 
 
32
#undef G_LOG_DOMAIN
 
33
#define G_LOG_DOMAIN "rss"
 
34
 
 
35
/**
 
36
 * This is the state object for the GLib XML parser.
 
37
 */
 
38
struct rss_parser {
 
39
        /**
 
40
         * The list of songs (in reverse order because that's faster
 
41
         * while adding).
 
42
         */
 
43
        GSList *songs;
 
44
 
 
45
        /**
 
46
         * The current position in the XML file.
 
47
         */
 
48
        enum {
 
49
                ROOT, ITEM,
 
50
        } state;
 
51
 
 
52
        /**
 
53
         * The current tag within the "entry" element.  This is only
 
54
         * valid if state==ITEM.  TAG_NUM_OF_ITEM_TYPES means there
 
55
         * is no (known) tag.
 
56
         */
 
57
        enum tag_type tag;
 
58
 
 
59
        /**
 
60
         * The current song.  It is allocated after the "location"
 
61
         * element.
 
62
         */
 
63
        struct song *song;
 
64
};
 
65
 
 
66
static const gchar *
 
67
get_attribute(const gchar **attribute_names, const gchar **attribute_values,
 
68
              const gchar *name)
 
69
{
 
70
        for (unsigned i = 0; attribute_names[i] != NULL; ++i)
 
71
                if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
 
72
                        return attribute_values[i];
 
73
 
 
74
        return NULL;
 
75
}
 
76
 
 
77
static void
 
78
rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
 
79
                  const gchar *element_name,
 
80
                  const gchar **attribute_names,
 
81
                  const gchar **attribute_values,
 
82
                  gpointer user_data, G_GNUC_UNUSED GError **error)
 
83
{
 
84
        struct rss_parser *parser = user_data;
 
85
 
 
86
        switch (parser->state) {
 
87
        case ROOT:
 
88
                if (g_ascii_strcasecmp(element_name, "item") == 0) {
 
89
                        parser->state = ITEM;
 
90
                        parser->song = song_remote_new("rss:");
 
91
                        parser->tag = TAG_NUM_OF_ITEM_TYPES;
 
92
                }
 
93
 
 
94
                break;
 
95
 
 
96
        case ITEM:
 
97
                if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
 
98
                        const gchar *href = get_attribute(attribute_names,
 
99
                                                          attribute_values,
 
100
                                                          "url");
 
101
                        if (href != NULL) {
 
102
                                /* create new song object, and copy
 
103
                                   the existing tag over; we cannot
 
104
                                   replace the existing song's URI,
 
105
                                   because that attribute is
 
106
                                   immutable */
 
107
                                struct song *song = song_remote_new(href);
 
108
 
 
109
                                if (parser->song != NULL) {
 
110
                                        song->tag = parser->song->tag;
 
111
                                        parser->song->tag = NULL;
 
112
                                        song_free(parser->song);
 
113
                                }
 
114
 
 
115
                                parser->song = song;
 
116
                        }
 
117
                } else if (g_ascii_strcasecmp(element_name, "title") == 0)
 
118
                        parser->tag = TAG_TITLE;
 
119
                else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
 
120
                        parser->tag = TAG_ARTIST;
 
121
 
 
122
                break;
 
123
        }
 
124
}
 
125
 
 
126
static void
 
127
rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
 
128
                const gchar *element_name,
 
129
                gpointer user_data, G_GNUC_UNUSED GError **error)
 
130
{
 
131
        struct rss_parser *parser = user_data;
 
132
 
 
133
        switch (parser->state) {
 
134
        case ROOT:
 
135
                break;
 
136
 
 
137
        case ITEM:
 
138
                if (g_ascii_strcasecmp(element_name, "item") == 0) {
 
139
                        if (strcmp(parser->song->uri, "rss:") != 0)
 
140
                                parser->songs = g_slist_prepend(parser->songs,
 
141
                                                                parser->song);
 
142
                        else
 
143
                                song_free(parser->song);
 
144
 
 
145
                        parser->state = ROOT;
 
146
                } else
 
147
                        parser->tag = TAG_NUM_OF_ITEM_TYPES;
 
148
 
 
149
                break;
 
150
        }
 
151
}
 
152
 
 
153
static void
 
154
rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
 
155
         const gchar *text, gsize text_len,
 
156
         gpointer user_data, G_GNUC_UNUSED GError **error)
 
157
{
 
158
        struct rss_parser *parser = user_data;
 
159
 
 
160
        switch (parser->state) {
 
161
        case ROOT:
 
162
                break;
 
163
 
 
164
        case ITEM:
 
165
                if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
 
166
                        if (parser->song->tag == NULL)
 
167
                                parser->song->tag = tag_new();
 
168
                        tag_add_item_n(parser->song->tag, parser->tag,
 
169
                                       text, text_len);
 
170
                }
 
171
 
 
172
                break;
 
173
        }
 
174
}
 
175
 
 
176
static const GMarkupParser rss_parser = {
 
177
        .start_element = rss_start_element,
 
178
        .end_element = rss_end_element,
 
179
        .text = rss_text,
 
180
};
 
181
 
 
182
static void
 
183
song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
 
184
{
 
185
        struct song *song = data;
 
186
 
 
187
        song_free(song);
 
188
}
 
189
 
 
190
static void
 
191
rss_parser_destroy(gpointer data)
 
192
{
 
193
        struct rss_parser *parser = data;
 
194
 
 
195
        if (parser->state >= ITEM)
 
196
                song_free(parser->song);
 
197
 
 
198
        g_slist_foreach(parser->songs, song_free_callback, NULL);
 
199
        g_slist_free(parser->songs);
 
200
}
 
201
 
 
202
/*
 
203
 * The playlist object
 
204
 *
 
205
 */
 
206
 
 
207
struct rss_playlist {
 
208
        struct playlist_provider base;
 
209
 
 
210
        GSList *songs;
 
211
};
 
212
 
 
213
static struct playlist_provider *
 
214
rss_open_stream(struct input_stream *is)
 
215
{
 
216
        struct rss_parser parser = {
 
217
                .songs = NULL,
 
218
                .state = ROOT,
 
219
        };
 
220
        struct rss_playlist *playlist;
 
221
        GMarkupParseContext *context;
 
222
        char buffer[1024];
 
223
        size_t nbytes;
 
224
        bool success;
 
225
        GError *error = NULL;
 
226
 
 
227
        /* parse the RSS XML file */
 
228
 
 
229
        context = g_markup_parse_context_new(&rss_parser,
 
230
                                             G_MARKUP_TREAT_CDATA_AS_TEXT,
 
231
                                             &parser, rss_parser_destroy);
 
232
 
 
233
        while (true) {
 
234
                nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
 
235
                if (nbytes == 0) {
 
236
                        if (error != NULL) {
 
237
                                g_markup_parse_context_free(context);
 
238
                                g_warning("%s", error->message);
 
239
                                g_error_free(error);
 
240
                                return NULL;
 
241
                        }
 
242
 
 
243
                        break;
 
244
                }
 
245
 
 
246
                success = g_markup_parse_context_parse(context, buffer, nbytes,
 
247
                                                       &error);
 
248
                if (!success) {
 
249
                        g_warning("XML parser failed: %s", error->message);
 
250
                        g_error_free(error);
 
251
                        g_markup_parse_context_free(context);
 
252
                        return NULL;
 
253
                }
 
254
        }
 
255
 
 
256
        success = g_markup_parse_context_end_parse(context, &error);
 
257
        if (!success) {
 
258
                g_warning("XML parser failed: %s", error->message);
 
259
                g_error_free(error);
 
260
                g_markup_parse_context_free(context);
 
261
                return NULL;
 
262
        }
 
263
 
 
264
        /* create a #rss_playlist object from the parsed song list */
 
265
 
 
266
        playlist = g_new(struct rss_playlist, 1);
 
267
        playlist_provider_init(&playlist->base, &rss_playlist_plugin);
 
268
        playlist->songs = g_slist_reverse(parser.songs);
 
269
        parser.songs = NULL;
 
270
 
 
271
        g_markup_parse_context_free(context);
 
272
 
 
273
        return &playlist->base;
 
274
}
 
275
 
 
276
static void
 
277
rss_close(struct playlist_provider *_playlist)
 
278
{
 
279
        struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
 
280
 
 
281
        g_slist_foreach(playlist->songs, song_free_callback, NULL);
 
282
        g_slist_free(playlist->songs);
 
283
        g_free(playlist);
 
284
}
 
285
 
 
286
static struct song *
 
287
rss_read(struct playlist_provider *_playlist)
 
288
{
 
289
        struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
 
290
        struct song *song;
 
291
 
 
292
        if (playlist->songs == NULL)
 
293
                return NULL;
 
294
 
 
295
        song = playlist->songs->data;
 
296
        playlist->songs = g_slist_remove(playlist->songs, song);
 
297
 
 
298
        return song;
 
299
}
 
300
 
 
301
static const char *const rss_suffixes[] = {
 
302
        "rss",
 
303
        NULL
 
304
};
 
305
 
 
306
static const char *const rss_mime_types[] = {
 
307
        "application/rss+xml",
 
308
        "text/xml",
 
309
        NULL
 
310
};
 
311
 
 
312
const struct playlist_plugin rss_playlist_plugin = {
 
313
        .name = "rss",
 
314
 
 
315
        .open_stream = rss_open_stream,
 
316
        .close = rss_close,
 
317
        .read = rss_read,
 
318
 
 
319
        .suffixes = rss_suffixes,
 
320
        .mime_types = rss_mime_types,
 
321
};