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

« back to all changes in this revision

Viewing changes to src/playlist/asx_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/asx_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 "asx"
 
34
 
 
35
/**
 
36
 * This is the state object for the GLib XML parser.
 
37
 */
 
38
struct asx_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, ENTRY,
 
50
        } state;
 
51
 
 
52
        /**
 
53
         * The current tag within the "entry" element.  This is only
 
54
         * valid if state==ENTRY.  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
asx_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 asx_parser *parser = user_data;
 
85
 
 
86
        switch (parser->state) {
 
87
        case ROOT:
 
88
                if (g_ascii_strcasecmp(element_name, "entry") == 0) {
 
89
                        parser->state = ENTRY;
 
90
                        parser->song = song_remote_new("asx:");
 
91
                        parser->tag = TAG_NUM_OF_ITEM_TYPES;
 
92
                }
 
93
 
 
94
                break;
 
95
 
 
96
        case ENTRY:
 
97
                if (g_ascii_strcasecmp(element_name, "ref") == 0) {
 
98
                        const gchar *href = get_attribute(attribute_names,
 
99
                                                          attribute_values,
 
100
                                                          "href");
 
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, "author") == 0)
 
118
                        /* is that correct?  or should it be COMPOSER
 
119
                           or PERFORMER? */
 
120
                        parser->tag = TAG_ARTIST;
 
121
                else if (g_ascii_strcasecmp(element_name, "title") == 0)
 
122
                        parser->tag = TAG_TITLE;
 
123
 
 
124
                break;
 
125
        }
 
126
}
 
127
 
 
128
static void
 
129
asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
 
130
                const gchar *element_name,
 
131
                gpointer user_data, G_GNUC_UNUSED GError **error)
 
132
{
 
133
        struct asx_parser *parser = user_data;
 
134
 
 
135
        switch (parser->state) {
 
136
        case ROOT:
 
137
                break;
 
138
 
 
139
        case ENTRY:
 
140
                if (g_ascii_strcasecmp(element_name, "entry") == 0) {
 
141
                        if (strcmp(parser->song->uri, "asx:") != 0)
 
142
                                parser->songs = g_slist_prepend(parser->songs,
 
143
                                                                parser->song);
 
144
                        else
 
145
                                song_free(parser->song);
 
146
 
 
147
                        parser->state = ROOT;
 
148
                } else
 
149
                        parser->tag = TAG_NUM_OF_ITEM_TYPES;
 
150
 
 
151
                break;
 
152
        }
 
153
}
 
154
 
 
155
static void
 
156
asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
 
157
         const gchar *text, gsize text_len,
 
158
         gpointer user_data, G_GNUC_UNUSED GError **error)
 
159
{
 
160
        struct asx_parser *parser = user_data;
 
161
 
 
162
        switch (parser->state) {
 
163
        case ROOT:
 
164
                break;
 
165
 
 
166
        case ENTRY:
 
167
                if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
 
168
                        if (parser->song->tag == NULL)
 
169
                                parser->song->tag = tag_new();
 
170
                        tag_add_item_n(parser->song->tag, parser->tag,
 
171
                                       text, text_len);
 
172
                }
 
173
 
 
174
                break;
 
175
        }
 
176
}
 
177
 
 
178
static const GMarkupParser asx_parser = {
 
179
        .start_element = asx_start_element,
 
180
        .end_element = asx_end_element,
 
181
        .text = asx_text,
 
182
};
 
183
 
 
184
static void
 
185
song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
 
186
{
 
187
        struct song *song = data;
 
188
 
 
189
        song_free(song);
 
190
}
 
191
 
 
192
static void
 
193
asx_parser_destroy(gpointer data)
 
194
{
 
195
        struct asx_parser *parser = data;
 
196
 
 
197
        if (parser->state >= ENTRY)
 
198
                song_free(parser->song);
 
199
 
 
200
        g_slist_foreach(parser->songs, song_free_callback, NULL);
 
201
        g_slist_free(parser->songs);
 
202
}
 
203
 
 
204
/*
 
205
 * The playlist object
 
206
 *
 
207
 */
 
208
 
 
209
struct asx_playlist {
 
210
        struct playlist_provider base;
 
211
 
 
212
        GSList *songs;
 
213
};
 
214
 
 
215
static struct playlist_provider *
 
216
asx_open_stream(struct input_stream *is)
 
217
{
 
218
        struct asx_parser parser = {
 
219
                .songs = NULL,
 
220
                .state = ROOT,
 
221
        };
 
222
        struct asx_playlist *playlist;
 
223
        GMarkupParseContext *context;
 
224
        char buffer[1024];
 
225
        size_t nbytes;
 
226
        bool success;
 
227
        GError *error = NULL;
 
228
 
 
229
        /* parse the ASX XML file */
 
230
 
 
231
        context = g_markup_parse_context_new(&asx_parser,
 
232
                                             G_MARKUP_TREAT_CDATA_AS_TEXT,
 
233
                                             &parser, asx_parser_destroy);
 
234
 
 
235
        while (true) {
 
236
                nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
 
237
                if (nbytes == 0) {
 
238
                        if (error != NULL) {
 
239
                                g_markup_parse_context_free(context);
 
240
                                g_warning("%s", error->message);
 
241
                                g_error_free(error);
 
242
                                return NULL;
 
243
                        }
 
244
 
 
245
                        break;
 
246
                }
 
247
 
 
248
                success = g_markup_parse_context_parse(context, buffer, nbytes,
 
249
                                                       &error);
 
250
                if (!success) {
 
251
                        g_warning("XML parser failed: %s", error->message);
 
252
                        g_error_free(error);
 
253
                        g_markup_parse_context_free(context);
 
254
                        return NULL;
 
255
                }
 
256
        }
 
257
 
 
258
        success = g_markup_parse_context_end_parse(context, &error);
 
259
        if (!success) {
 
260
                g_warning("XML parser failed: %s", error->message);
 
261
                g_error_free(error);
 
262
                g_markup_parse_context_free(context);
 
263
                return NULL;
 
264
        }
 
265
 
 
266
        /* create a #asx_playlist object from the parsed song list */
 
267
 
 
268
        playlist = g_new(struct asx_playlist, 1);
 
269
        playlist_provider_init(&playlist->base, &asx_playlist_plugin);
 
270
        playlist->songs = g_slist_reverse(parser.songs);
 
271
        parser.songs = NULL;
 
272
 
 
273
        g_markup_parse_context_free(context);
 
274
 
 
275
        return &playlist->base;
 
276
}
 
277
 
 
278
static void
 
279
asx_close(struct playlist_provider *_playlist)
 
280
{
 
281
        struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
 
282
 
 
283
        g_slist_foreach(playlist->songs, song_free_callback, NULL);
 
284
        g_slist_free(playlist->songs);
 
285
        g_free(playlist);
 
286
}
 
287
 
 
288
static struct song *
 
289
asx_read(struct playlist_provider *_playlist)
 
290
{
 
291
        struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
 
292
        struct song *song;
 
293
 
 
294
        if (playlist->songs == NULL)
 
295
                return NULL;
 
296
 
 
297
        song = playlist->songs->data;
 
298
        playlist->songs = g_slist_remove(playlist->songs, song);
 
299
 
 
300
        return song;
 
301
}
 
302
 
 
303
static const char *const asx_suffixes[] = {
 
304
        "asx",
 
305
        NULL
 
306
};
 
307
 
 
308
static const char *const asx_mime_types[] = {
 
309
        "video/x-ms-asf",
 
310
        NULL
 
311
};
 
312
 
 
313
const struct playlist_plugin asx_playlist_plugin = {
 
314
        .name = "asx",
 
315
 
 
316
        .open_stream = asx_open_stream,
 
317
        .close = asx_close,
 
318
        .read = asx_read,
 
319
 
 
320
        .suffixes = asx_suffixes,
 
321
        .mime_types = asx_mime_types,
 
322
};