1 /*
2 Copyright (C) 2002, 2003, 2004, 2005, 2006 Bastien Nocera
3 Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org>
4
5 The Gnome Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
9
10 The Gnome Library 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 GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with the Gnome Library; see the file COPYING.LIB. If not,
17 write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301 USA.
19
20 Authors: Bastien Nocera <hadess@hadess.net>
21 */
22
23 /**
24 * SECTION:totem-pl-parser
25 * @short_description: playlist parser
26 * @stability: Stable
27 * @include: totem-pl-parser.h
28 *
29 * #TotemPlParser is a general-purpose playlist parser and writer, with
30 * support for several different types of playlist. Note that totem-pl-parser requires a main loop
31 * to operate properly (e.g. for the #TotemPlParser::entry-parsed signal to be emitted).
32 *
33 * <example>
34 * <title>Reading a Playlist</title>
35 * <programlisting>
36 * TotemPlParser *pl = totem_pl_parser_new ();
37 * g_object_set (pl, "recurse", FALSE, "disable-unsafe", TRUE, NULL);
38 * g_signal_connect (G_OBJECT (pl), "playlist-started", G_CALLBACK (playlist_started), NULL);
39 * g_signal_connect (G_OBJECT (pl), "entry-parsed", G_CALLBACK (entry_parsed), NULL);
40 *
41 * if (totem_pl_parser_parse (pl, "http://example.com/playlist.pls", FALSE) != TOTEM_PL_PARSER_RESULT_SUCCESS)
42 * g_error ("Playlist parsing failed.");
43 *
44 * g_object_unref (pl);
45 * </programlisting>
46 * </example>
47 *
48 * <example>
49 * <title>Reading a Playlist Asynchronously</title>
50 * <programlisting>
51 * TotemPlParser *pl = totem_pl_parser_new ();
52 * g_object_set (pl, "recurse", FALSE, "disable-unsafe", TRUE, NULL);
53 * g_signal_connect (G_OBJECT (pl), "playlist-started", G_CALLBACK (playlist_started), NULL);
54 * g_signal_connect (G_OBJECT (pl), "entry-parsed", G_CALLBACK (entry_parsed), NULL);
55 *
56 * totem_pl_parser_parse_async (pl, "http://example.com/playlist.pls", FALSE, NULL, parse_cb, NULL);
57 * g_object_unref (pl);
58 *
59 * static void
60 * parse_cb (TotemPlParser *parser, GAsyncResult *result, gpointer user_data)
61 * {
62 * GError *error = NULL;
63 * if (totem_pl_parser_parse_finish (parser, result, &error) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
64 * g_error ("Playlist parsing failed: %s", error->message);
65 * g_error_free (error);
66 * }
67 * }
68 * </programlisting>
69 * </example>
70 *
71 * <example>
72 * <title>Getting Metadata from Entries</title>
73 * <programlisting>
74 * static void
75 * entry_parsed (TotemPlParser *parser, const gchar *uri, GHashTable *metadata, gpointer user_data)
76 * {
77 * gchar *title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
78 * if (title != NULL)
79 * g_message ("Entry title: %s", title);
80 * else
81 * g_message ("Entry (URI: %s) has no title.", uri);
82 * }
83 * </programlisting>
84 * </example>
85 *
86 *
87 * <example>
88 * <title>Writing a Playlist</title>
89 * <programlisting>
90 * {
91 * TotemPlParser *pl;
92 * TotemPlPlaylist *playlist;
93 * TotemPlPlaylistIter iter;
94 * GFile *file;
95 *
96 * pl = totem_pl_parser_new ();
97 * playlist = totem_pl_playlist_new ();
98 * file = g_file_new_for_path ("/tmp/playlist.pls");
99 *
100 * totem_pl_playlist_append (playlist, &iter);
101 * totem_pl_playlist_set (playlist, &iter,
102 * TOTEM_PL_PARSER_FIELD_URI, "file:///1.ogg",
103 * TOTEM_PL_PARSER_FIELD_TITLE, "1.ogg",
104 * NULL);
105 *
106 * totem_pl_playlist_append (playlist, &iter);
107 * totem_pl_playlist_set (playlist, &iter,
108 * TOTEM_PL_PARSER_FIELD_URI, "file:///2.ogg",
109 * NULL);
110 *
111 * if (totem_pl_parser_save (pl, playlist, file, "Title",
112 * TOTEM_PL_PARSER_PLS, NULL) != TRUE) {
113 * g_error ("Playlist writing failed.");
114 * }
115 *
116 * g_object_unref (playlist);
117 * g_object_unref (pl);
118 * g_object_unref (file);
119 * }
120 * </programlisting>
121 * </example>
122 **/
123
124 #include "config.h"
125
126 #include <string.h>
127 #include <fnmatch.h>
128 #include <glib.h>
129 #include <glib/gstdio.h>
130 #include <glib/gi18n-lib.h>
131 #include <gio/gio.h>
132
133 #ifndef TOTEM_PL_PARSER_MINI
134 #include <gobject/gvaluecollector.h>
135
136 #include "totem-pl-parser.h"
137 #include "totemplparser-marshal.h"
138 #include "totem-disc.h"
139 #endif /* !TOTEM_PL_PARSER_MINI */
140
141 #include "totem-pl-parser-mini.h"
142 #include "totem-pl-parser-decode-date.h"
143 #include "totem-pl-parser-wm.h"
144 #include "totem-pl-parser-qt.h"
145 #include "totem-pl-parser-pls.h"
146 #include "totem-pl-parser-xspf.h"
147 #include "totem-pl-parser-media.h"
148 #include "totem-pl-parser-smil.h"
149 #include "totem-pl-parser-pla.h"
150 #include "totem-pl-parser-podcast.h"
151 #include "totem-pl-parser-lines.h"
152 #include "totem-pl-parser-misc.h"
153 #include "totem-pl-parser-private.h"
154 #include "totem-pl-parser-videosite.h"
155 #include "totem-pl-parser-amz.h"
156
157 #define READ_CHUNK_SIZE 8192
158 #define RECURSE_LEVEL_MAX 4
159
160 #define D(x) if (debug) x
161
162 typedef const char * (*PlaylistIdenCallback) (const char *data, gsize len);
163
164 #ifndef TOTEM_PL_PARSER_MINI
165 typedef TotemPlParserResult (*PlaylistCallback) (TotemPlParser *parser, GFile *uri, GFile *base_file, TotemPlParseData *parse_data, gpointer data);
166 #endif
167
168 typedef struct {
169 const char *mimetype;
170 #ifndef TOTEM_PL_PARSER_MINI
171 PlaylistCallback func;
172 #endif
173 PlaylistIdenCallback iden;
174 #ifndef TOTEM_PL_PARSER_MINI
175 guint unsafe : 1;
176 #endif
177 } PlaylistTypes;
178
179 #ifndef TOTEM_PL_PARSER_MINI
180 #define PLAYLIST_TYPE(mime,cb,identcb,unsafe) { mime, cb, identcb, unsafe }
181 #define PLAYLIST_TYPE2(mime,cb,identcb) { mime, cb, identcb }
182 #define PLAYLIST_TYPE3(mime) { mime, NULL, NULL, FALSE }
183 #else
184 #define PLAYLIST_TYPE(mime,cb,identcb,unsafe) { mime }
185 #define PLAYLIST_TYPE2(mime,cb,identcb) { mime, identcb }
186 #define PLAYLIST_TYPE3(mime) { mime }
187 #endif
188
189 /* These ones need a special treatment, mostly parser formats */
190 static PlaylistTypes special_types[] = {
191 PLAYLIST_TYPE ("audio/x-mpegurl", totem_pl_parser_add_m3u, NULL, FALSE),
192 PLAYLIST_TYPE ("video/vnd.mpegurl", totem_pl_parser_add_m4u, NULL, FALSE),
193 PLAYLIST_TYPE ("audio/playlist", totem_pl_parser_add_m3u, NULL, FALSE),
194 PLAYLIST_TYPE ("audio/x-scpls", totem_pl_parser_add_pls, NULL, FALSE),
195 PLAYLIST_TYPE ("application/x-smil", totem_pl_parser_add_smil, NULL, FALSE),
196 PLAYLIST_TYPE ("application/smil", totem_pl_parser_add_smil, NULL, FALSE),
197 PLAYLIST_TYPE ("application/smil+xml", totem_pl_parser_add_smil, NULL, FALSE),
198 PLAYLIST_TYPE ("application/vnd.ms-wpl", totem_pl_parser_add_smil, NULL, FALSE),
199 PLAYLIST_TYPE ("video/x-ms-wvx", totem_pl_parser_add_asx, NULL, FALSE),
200 PLAYLIST_TYPE ("audio/x-ms-wax", totem_pl_parser_add_asx, NULL, FALSE),
201 PLAYLIST_TYPE ("application/xspf+xml", totem_pl_parser_add_xspf, NULL, FALSE),
202 PLAYLIST_TYPE ("text/uri-list", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list, FALSE),
203 PLAYLIST_TYPE ("text/x-google-video-pointer", totem_pl_parser_add_gvp, NULL, FALSE),
204 PLAYLIST_TYPE ("text/google-video-pointer", totem_pl_parser_add_gvp, NULL, FALSE),
205 PLAYLIST_TYPE ("audio/x-iriver-pla", totem_pl_parser_add_pla, NULL, FALSE),
206 PLAYLIST_TYPE ("application/atom+xml", totem_pl_parser_add_atom, NULL, FALSE),
207 PLAYLIST_TYPE ("application/rss+xml", totem_pl_parser_add_rss, totem_pl_parser_is_rss, FALSE),
208 PLAYLIST_TYPE ("text/x-opml+xml", totem_pl_parser_add_opml, NULL, FALSE),
209 PLAYLIST_TYPE ("audio/x-amzxml", totem_pl_parser_add_amz, NULL, FALSE),
210 #ifndef TOTEM_PL_PARSER_MINI
211 PLAYLIST_TYPE ("application/x-desktop", totem_pl_parser_add_desktop, NULL, TRUE),
212 PLAYLIST_TYPE ("application/x-gnome-app-info", totem_pl_parser_add_desktop, NULL, TRUE),
213 PLAYLIST_TYPE ("application/x-cd-image", totem_pl_parser_add_iso, NULL, TRUE),
214 PLAYLIST_TYPE ("application/x-extension-img", totem_pl_parser_add_iso, NULL, TRUE),
215 PLAYLIST_TYPE ("application/x-cue", totem_pl_parser_add_cue, NULL, TRUE),
216 PLAYLIST_TYPE (DIR_MIME_TYPE, totem_pl_parser_add_directory, NULL, TRUE),
217 PLAYLIST_TYPE (BLOCK_DEVICE_TYPE, totem_pl_parser_add_block, NULL, TRUE),
218 #endif
219 };
220
221 /* These ones are "dual" types, might be a video, might be a parser
222 * Please keep the same _is_ functions together */
223 static PlaylistTypes dual_types[] = {
224 PLAYLIST_TYPE2 ("audio/x-real-audio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
225 PLAYLIST_TYPE2 ("audio/x-pn-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
226 PLAYLIST_TYPE2 ("application/ram", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
227 PLAYLIST_TYPE2 ("application/vnd.rn-realmedia", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
228 PLAYLIST_TYPE2 ("audio/x-pn-realaudio-plugin", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
229 PLAYLIST_TYPE2 ("audio/vnd.rn-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
230 PLAYLIST_TYPE2 ("audio/x-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
231 PLAYLIST_TYPE2 ("text/plain", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
232 PLAYLIST_TYPE2 ("application/x-php", NULL, NULL),
233 PLAYLIST_TYPE2 ("audio/x-ms-asx", totem_pl_parser_add_asx, totem_pl_parser_is_asx),
234 PLAYLIST_TYPE2 ("video/x-ms-asf", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
235 PLAYLIST_TYPE2 ("application/vnd.ms-asf", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
236 PLAYLIST_TYPE2 ("video/x-ms-wmv", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
237 PLAYLIST_TYPE2 ("audio/x-ms-wma", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
238 PLAYLIST_TYPE2 ("video/quicktime", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
239 PLAYLIST_TYPE2 ("video/mp4", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
240 PLAYLIST_TYPE2 ("application/x-quicktime-media-link", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
241 PLAYLIST_TYPE2 ("application/x-quicktimeplayer", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
242 PLAYLIST_TYPE2 ("application/xml", totem_pl_parser_add_xml_feed, totem_pl_parser_is_xml_feed),
243 };
244
245 static char *totem_pl_parser_mime_type_from_data (gconstpointer data, int len);
246
247 #ifndef TOTEM_PL_PARSER_MINI
248
249 static void totem_pl_parser_set_property (GObject *object,
250 guint prop_id,
251 const GValue *value,
252 GParamSpec *pspec);
253 static void totem_pl_parser_get_property (GObject *object,
254 guint prop_id,
255 GValue *value,
256 GParamSpec *pspec);
257
258 struct TotemPlParserPrivate {
259 GHashTable *ignore_schemes; /* key = char *, value = boolean */
260 GHashTable *ignore_mimetypes; /*key = char *, value = boolean */
261 GHashTable *ignore_globs; /*key = char *, value = boolean */
262 GMutex ignore_mutex;
263 GThread *main_thread; /* see CALL_ASYNC() in *-private.h */
264
265 guint recurse : 1;
266 guint debug : 1;
267 guint force : 1;
268 guint disable_unsafe : 1;
269 };
270
271 enum {
272 PROP_NONE,
273 PROP_RECURSE,
274 PROP_DEBUG,
275 PROP_FORCE,
276 PROP_DISABLE_UNSAFE
277 };
278
279 /* Signals */
280 enum {
281 ENTRY_PARSED,
282 PLAYLIST_STARTED,
283 PLAYLIST_ENDED,
284 LAST_SIGNAL
285 };
286
287 static int totem_pl_parser_table_signals[LAST_SIGNAL];
288 static GParamSpecPool *totem_pl_parser_pspec_pool = NULL;
289
290 static void totem_pl_parser_class_init (TotemPlParserClass *klass);
291 static void totem_pl_parser_base_class_finalize (TotemPlParserClass *klass);
292 static void totem_pl_parser_init (TotemPlParser *parser);
293 static void totem_pl_parser_finalize (GObject *object);
294
295 static gpointer totem_pl_parser_parent_class = NULL;
296
297 #define TOTEM_PL_PARSER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_PL_PARSER, TotemPlParserPrivate))
298
299 GType
totem_pl_parser_get_type(void)300 totem_pl_parser_get_type (void)
301 {
302 static volatile gsize g_define_type_id__volatile = 0;
303 if (g_once_init_enter (&g_define_type_id__volatile))
304 {
305 const GTypeInfo g_define_type_info = {
306 sizeof (TotemPlParserClass),
307 NULL,
308 (GBaseFinalizeFunc) totem_pl_parser_base_class_finalize,
309 (GClassInitFunc) totem_pl_parser_class_init,
310 NULL,
311 NULL,
312 sizeof (TotemPlParser),
313 0,
314 (GInstanceInitFunc) totem_pl_parser_init,
315 };
316 GType g_define_type_id = g_type_register_static (G_TYPE_OBJECT, "TotemPlParser", &g_define_type_info, 0);
317 g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
318 }
319 return g_define_type_id__volatile;
320 }
321
322 static void
totem_pl_parser_class_init(TotemPlParserClass * klass)323 totem_pl_parser_class_init (TotemPlParserClass *klass)
324 {
325 GParamSpec *pspec;
326 GObjectClass *object_class = G_OBJECT_CLASS (klass);
327
328 totem_pl_parser_parent_class = g_type_class_peek_parent (klass);
329
330 object_class->finalize = totem_pl_parser_finalize;
331 object_class->set_property = totem_pl_parser_set_property;
332 object_class->get_property = totem_pl_parser_get_property;
333
334 /* Properties */
335
336 /**
337 * TotemPlParser:recurse:
338 *
339 * If %TRUE, the parser will recursively fetch playlists linked to by
340 * the current one.
341 **/
342 g_object_class_install_property (object_class,
343 PROP_RECURSE,
344 g_param_spec_boolean ("recurse",
345 "recurse",
346 "Whether or not to process URIs further",
347 TRUE,
348 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
349
350 /**
351 * TotemPlParser:debug:
352 *
353 * If %TRUE, the parser will output debug information.
354 **/
355 g_object_class_install_property (object_class,
356 PROP_DEBUG,
357 g_param_spec_boolean ("debug",
358 "debug",
359 "Whether or not to enable debugging output",
360 FALSE,
361 G_PARAM_READWRITE));
362
363 /**
364 * TotemPlParser:force:
365 *
366 * If %TRUE, the parser will attempt to parse a playlist, even if it
367 * appears to be unsupported (usually because of its filename extension).
368 **/
369 g_object_class_install_property (object_class,
370 PROP_FORCE,
371 g_param_spec_boolean ("force",
372 "force",
373 "Whether or not to force parsing the file if the playlist looks unsupported",
374 FALSE,
375 G_PARAM_READWRITE));
376
377 /**
378 * TotemPlParser:disable-unsafe:
379 *
380 * If %TRUE, the parser will not parse unsafe locations, such as local devices
381 * and local files if the playlist isn't local. This is useful if the library
382 * is parsing a playlist from a remote location such as a website.
383 **/
384 g_object_class_install_property (object_class,
385 PROP_DISABLE_UNSAFE,
386 g_param_spec_boolean ("disable-unsafe",
387 "disable-unsafe",
388 "Whether or not to disable parsing of unsafe locations",
389 FALSE,
390 G_PARAM_READWRITE));
391
392 /**
393 * TotemPlParser::entry-parsed:
394 * @parser: the object which received the signal
395 * @uri: the URI of the entry parsed
396 * @metadata: (type GHashTable) (element-type utf8 utf8): a #GHashTable of metadata relating to the entry added
397 *
398 * The ::entry-parsed signal is emitted when a new entry is parsed.
399 */
400 totem_pl_parser_table_signals[ENTRY_PARSED] =
401 g_signal_new ("entry-parsed",
402 G_TYPE_FROM_CLASS (klass),
403 G_SIGNAL_RUN_LAST,
404 G_STRUCT_OFFSET (TotemPlParserClass, entry_parsed),
405 NULL, NULL,
406 _totemplparser_marshal_VOID__STRING_BOXED,
407 G_TYPE_NONE, 2, G_TYPE_STRING, TOTEM_TYPE_PL_PARSER_METADATA);
408 /**
409 * TotemPlParser::playlist-started:
410 * @parser: the object which received the signal
411 * @uri: the URI of the new playlist started
412 * @metadata: (type GHashTable) (element-type utf8 utf8): a #GHashTable of metadata relating to the playlist that
413 * started.
414 *
415 * The ::playlist-started signal is emitted when a playlist parsing has
416 * started. This signal isn't emitted for all types of playlists, but
417 * can be relied on to be called for playlists which support playlist
418 * metadata, such as title.
419 */
420 totem_pl_parser_table_signals[PLAYLIST_STARTED] =
421 g_signal_new ("playlist-started",
422 G_TYPE_FROM_CLASS (klass),
423 G_SIGNAL_RUN_LAST,
424 G_STRUCT_OFFSET (TotemPlParserClass, playlist_started),
425 NULL, NULL,
426 _totemplparser_marshal_VOID__STRING_BOXED,
427 G_TYPE_NONE, 2, G_TYPE_STRING, TOTEM_TYPE_PL_PARSER_METADATA);
428 /**
429 * TotemPlParser::playlist-ended:
430 * @parser: the object which received the signal
431 * @uri: the URI of the playlist that finished parsing.
432 *
433 * The ::playlist-ended signal is emitted when a playlist is finished
434 * parsing. It is only called when #TotemPlParser::playlist-started
435 * has been called for that playlist.
436 */
437 totem_pl_parser_table_signals[PLAYLIST_ENDED] =
438 g_signal_new ("playlist-ended",
439 G_TYPE_FROM_CLASS (klass),
440 G_SIGNAL_RUN_LAST,
441 G_STRUCT_OFFSET (TotemPlParserClass, playlist_ended),
442 NULL, NULL,
443 g_cclosure_marshal_VOID__STRING,
444 G_TYPE_NONE, 1, G_TYPE_STRING);
445
446 /* param specs */
447 totem_pl_parser_pspec_pool = g_param_spec_pool_new (FALSE);
448 pspec = g_param_spec_string ("url", "url",
449 "URI to be added", NULL,
450 G_PARAM_READABLE & G_PARAM_WRITABLE);
451 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
452 pspec = g_param_spec_string ("title", "title",
453 "Title of the item to be added", NULL,
454 G_PARAM_READABLE & G_PARAM_WRITABLE);
455 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
456 pspec = g_param_spec_string ("author", "author",
457 "Author of the item to be added", NULL,
458 G_PARAM_READABLE & G_PARAM_WRITABLE);
459 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
460 pspec = g_param_spec_string ("genre", "genre",
461 "Genre of the item to be added", NULL,
462 G_PARAM_READABLE & G_PARAM_WRITABLE);
463 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
464 pspec = g_param_spec_string ("album", "album",
465 "Album of the item to be added", NULL,
466 G_PARAM_READABLE & G_PARAM_WRITABLE);
467 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
468 pspec = g_param_spec_string ("base", "base",
469 "Base URI of the item to be added", NULL,
470 G_PARAM_READABLE & G_PARAM_WRITABLE);
471 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
472 pspec = g_param_spec_string ("volume", "volume",
473 "Default playback volume (in percents)", NULL,
474 G_PARAM_READABLE & G_PARAM_WRITABLE);
475 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
476 pspec = g_param_spec_string ("autoplay", "autoplay",
477 "Whether or not to autoplay the stream", NULL,
478 G_PARAM_READABLE & G_PARAM_WRITABLE);
479 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
480 pspec = g_param_spec_string ("duration", "duration",
481 "String representing the duration of the entry", NULL,
482 G_PARAM_READABLE & G_PARAM_WRITABLE);
483 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
484 pspec = g_param_spec_string ("duration-ms", "duration-ms",
485 "String representing the duration of the entry in milliseconds", NULL,
486 G_PARAM_READABLE & G_PARAM_WRITABLE);
487 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
488 pspec = g_param_spec_string ("starttime", "starttime",
489 "String representing the start time of the stream (initial seek)", NULL,
490 G_PARAM_READABLE & G_PARAM_WRITABLE);
491 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
492 pspec = g_param_spec_string ("copyright", "copyright",
493 "Copyright of the item to be added", NULL,
494 G_PARAM_READABLE & G_PARAM_WRITABLE);
495 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
496 pspec = g_param_spec_string ("abstract", "abstract",
497 "Abstract of the item to be added", NULL,
498 G_PARAM_READABLE & G_PARAM_WRITABLE);
499 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
500 pspec = g_param_spec_string ("moreinfo", "moreinfo",
501 "URI to get more information for item to be added", NULL,
502 G_PARAM_READABLE & G_PARAM_WRITABLE);
503 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
504 pspec = g_param_spec_string ("screensize", "screensize",
505 "String representing the default movie size (double, full or original)", NULL,
506 G_PARAM_READABLE & G_PARAM_WRITABLE);
507 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
508 pspec = g_param_spec_string ("ui-mode", "ui-mode",
509 "String representing the default UI mode (only compact is supported)", NULL,
510 G_PARAM_READABLE & G_PARAM_WRITABLE);
511 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
512 pspec = g_param_spec_string ("endtime", "endtime",
513 "String representing the end time of the stream", NULL,
514 G_PARAM_READABLE & G_PARAM_WRITABLE);
515 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
516 pspec = g_param_spec_boolean ("is-playlist", "is-playlist",
517 "Boolean saying whether the entry pushed is the top-level of a playlist", FALSE,
518 G_PARAM_READABLE & G_PARAM_WRITABLE);
519 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
520 pspec = g_param_spec_string ("description", "description",
521 "String representing the description of the stream", NULL,
522 G_PARAM_READABLE & G_PARAM_WRITABLE);
523 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
524 pspec = g_param_spec_string ("publication-date", "publication-date",
525 "String representing the publication date of the stream", NULL,
526 G_PARAM_READABLE & G_PARAM_WRITABLE);
527 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
528 pspec = g_param_spec_string ("filesize", "filesize",
529 "String representing the filesize of a file", NULL,
530 G_PARAM_READABLE & G_PARAM_WRITABLE);
531 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
532 pspec = g_param_spec_string ("language", "language",
533 "String representing the language of a stream", NULL,
534 G_PARAM_READABLE & G_PARAM_WRITABLE);
535 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
536 pspec = g_param_spec_string ("contact", "contact",
537 "String representing the contact for a playlist", NULL,
538 G_PARAM_READABLE & G_PARAM_WRITABLE);
539 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
540 pspec = g_param_spec_string ("image-url", "image-url",
541 "String representing the location of an image for a playlist", NULL,
542 G_PARAM_READABLE & G_PARAM_WRITABLE);
543 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
544 pspec = g_param_spec_object ("gfile-object", "gfile-object",
545 "Object representing the GFile for an entry", G_TYPE_FILE,
546 G_PARAM_READABLE & G_PARAM_WRITABLE);
547 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
548 pspec = g_param_spec_object ("gfile-object-base", "gfile-object-base",
549 "Object representing the GFile for base URI of an entry", G_TYPE_FILE,
550 G_PARAM_READABLE & G_PARAM_WRITABLE);
551 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
552 pspec = g_param_spec_string ("download-url", "download-url",
553 "String representing the location of a download URI", NULL,
554 G_PARAM_READABLE & G_PARAM_WRITABLE);
555 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
556 pspec = g_param_spec_string ("id", "id",
557 "String representing the identifier for an entry", NULL,
558 G_PARAM_READABLE & G_PARAM_WRITABLE);
559 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
560 pspec = g_param_spec_string ("subtitle-uri", "subtitle-uri",
561 "Subtitle URI to be added", NULL,
562 G_PARAM_READABLE & G_PARAM_WRITABLE);
563 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
564 pspec = g_param_spec_string ("content-type", "content-type",
565 "Content type for the video stream", NULL,
566 G_PARAM_READABLE & G_PARAM_WRITABLE);
567 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
568 pspec = g_param_spec_string ("playing", "playing",
569 "Whether the track is playing", NULL,
570 G_PARAM_READABLE & G_PARAM_WRITABLE);
571 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
572 pspec = g_param_spec_string ("audio-track", "audio-track",
573 "The default audio-track to play", NULL,
574 G_PARAM_READABLE & G_PARAM_WRITABLE);
575 g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
576 }
577
578 static void
totem_pl_parser_base_class_finalize(TotemPlParserClass * klass)579 totem_pl_parser_base_class_finalize (TotemPlParserClass *klass)
580 {
581 GList *list, *node;
582
583 list = g_param_spec_pool_list_owned (totem_pl_parser_pspec_pool, G_OBJECT_CLASS_TYPE (klass));
584 for (node = list; node; node = node->next) {
585 GParamSpec *pspec = node->data;
586
587 g_param_spec_pool_remove (totem_pl_parser_pspec_pool, pspec);
588 g_param_spec_unref (pspec);
589 }
590 g_list_free (list);
591 }
592
593 static void
totem_pl_parser_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)594 totem_pl_parser_set_property (GObject *object,
595 guint prop_id,
596 const GValue *value,
597 GParamSpec *pspec)
598 {
599 TotemPlParser *parser = TOTEM_PL_PARSER (object);
600
601 switch (prop_id)
602 {
603 case PROP_RECURSE:
604 parser->priv->recurse = g_value_get_boolean (value) != FALSE;
605 break;
606 case PROP_DEBUG:
607 parser->priv->debug = g_value_get_boolean (value) != FALSE;
608 break;
609 case PROP_FORCE:
610 parser->priv->force = g_value_get_boolean (value) != FALSE;
611 break;
612 case PROP_DISABLE_UNSAFE:
613 parser->priv->disable_unsafe = g_value_get_boolean (value) != FALSE;
614 break;
615 default:
616 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
617 break;
618 }
619 }
620
621 static void
totem_pl_parser_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)622 totem_pl_parser_get_property (GObject *object,
623 guint prop_id,
624 GValue *value,
625 GParamSpec *pspec)
626 {
627 TotemPlParser *parser = TOTEM_PL_PARSER (object);
628
629 switch (prop_id)
630 {
631 case PROP_RECURSE:
632 g_value_set_boolean (value, parser->priv->recurse);
633 break;
634 case PROP_DEBUG:
635 g_value_set_boolean (value, parser->priv->debug);
636 break;
637 case PROP_FORCE:
638 g_value_set_boolean (value, parser->priv->force);
639 break;
640 case PROP_DISABLE_UNSAFE:
641 g_value_set_boolean (value, parser->priv->disable_unsafe);
642 break;
643 default:
644 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
645 break;
646 }
647 }
648
649 GQuark
totem_pl_parser_error_quark(void)650 totem_pl_parser_error_quark (void)
651 {
652 static GQuark quark;
653 if (!quark)
654 quark = g_quark_from_static_string ("totem_pl_parser_error");
655
656 return quark;
657 }
658
659 static gpointer
totem_pl_parser_real_init_i18n(gpointer data)660 totem_pl_parser_real_init_i18n (gpointer data)
661 {
662 bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
663 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
664 return NULL;
665 }
666
667 static void
totem_pl_parser_init_i18n(void)668 totem_pl_parser_init_i18n (void)
669 {
670 static GOnce my_once = G_ONCE_INIT;
671 g_once (&my_once, totem_pl_parser_real_init_i18n, NULL);
672 }
673
674 /**
675 * totem_pl_parser_new:
676 *
677 * Creates a #TotemPlParser object.
678 *
679 * Return value: a new #TotemPlParser
680 */
681 TotemPlParser *
totem_pl_parser_new(void)682 totem_pl_parser_new (void)
683 {
684 totem_pl_parser_init_i18n ();
685 return TOTEM_PL_PARSER (g_object_new (TOTEM_TYPE_PL_PARSER, NULL));
686 }
687
688 typedef struct {
689 TotemPlParser *parser;
690 char *playlist_uri;
691 } PlaylistEndedSignalData;
692
693 static gboolean
emit_playlist_ended_signal(PlaylistEndedSignalData * data)694 emit_playlist_ended_signal (PlaylistEndedSignalData *data)
695 {
696 g_signal_emit (data->parser,
697 totem_pl_parser_table_signals[PLAYLIST_ENDED],
698 0, data->playlist_uri);
699
700 /* Free the data */
701 g_object_unref (data->parser);
702 g_free (data->playlist_uri);
703 g_free (data);
704
705 return FALSE;
706 }
707
708 /**
709 * totem_pl_parser_playlist_end:
710 * @parser: a #TotemPlParser
711 * @playlist_uri: the playlist URI
712 *
713 * Emits the #TotemPlParser::playlist-ended signal on @parser for
714 * the playlist @playlist_uri.
715 **/
716 void
totem_pl_parser_playlist_end(TotemPlParser * parser,const char * playlist_uri)717 totem_pl_parser_playlist_end (TotemPlParser *parser, const char *playlist_uri)
718 {
719 PlaylistEndedSignalData *data;
720
721 data = g_new (PlaylistEndedSignalData, 1);
722 data->parser = g_object_ref (parser);
723 data->playlist_uri = g_strdup (playlist_uri);
724
725 CALL_ASYNC (parser, emit_playlist_ended_signal, data);
726 }
727
728 static char *
my_g_file_info_get_mime_type_with_data(GFile * file,gpointer * data,TotemPlParser * parser)729 my_g_file_info_get_mime_type_with_data (GFile *file, gpointer *data, TotemPlParser *parser)
730 {
731 char *buffer;
732 gsize bytes_read;
733 GFileInputStream *stream;
734 GError *error = NULL;
735
736 *data = NULL;
737
738 #ifndef _WIN32
739 /* Stat for a block device, we're screwed as far as speed
740 * is concerned now */
741 if (g_file_is_native (file) != FALSE) {
742 struct stat buf;
743 char *path;
744
745 path = g_file_get_path (file);
746 if (g_stat (path, &buf) == 0 && S_ISBLK (buf.st_mode)) {
747 g_free (path);
748 return g_strdup (BLOCK_DEVICE_TYPE);
749 }
750 g_free (path);
751 }
752 #endif
753
754 /* Open the file. */
755 stream = g_file_read (file, NULL, &error);
756 if (stream == NULL) {
757 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY) != FALSE) {
758 g_error_free (error);
759 return g_strdup (DIR_MIME_TYPE);
760 }
761 DEBUG(file, g_print ("URI '%s' couldn't be opened in _get_mime_type_with_data: '%s'\n", uri, error->message));
762 g_error_free (error);
763 return NULL;
764 }
765 DEBUG(file, g_print ("URI '%s' was opened successfully in _get_mime_type_with_data\n", uri));
766
767 /* Read the whole thing, up to MIME_READ_CHUNK_SIZE */
768 buffer = g_malloc (MIME_READ_CHUNK_SIZE);
769 if (g_input_stream_read_all (G_INPUT_STREAM (stream), buffer, MIME_READ_CHUNK_SIZE, &bytes_read, NULL, &error) == FALSE) {
770 g_object_unref (stream);
771 DEBUG(file, g_print ("Couldn't read data from '%s'\n", uri));
772 g_free (buffer);
773 return NULL;
774 }
775 g_object_unref (G_INPUT_STREAM (stream));
776
777 /* Empty file */
778 if (bytes_read == 0) {
779 g_free (buffer);
780 DEBUG(file, g_print ("URI '%s' is empty in _get_mime_type_with_data\n", uri));
781 return g_strdup (EMPTY_FILE_TYPE);
782 }
783
784 /* Return the file null-terminated. */
785 buffer = g_realloc (buffer, bytes_read + 1);
786 buffer[bytes_read] = '\0';
787 *data = buffer;
788
789 return totem_pl_parser_mime_type_from_data (*data, bytes_read);
790 }
791
792 /**
793 * totem_pl_parser_is_debugging_enabled:
794 * @parser: a #TotemPlParser
795 *
796 * Returns whether debugging is enabled. This is a private method, not exposed by the library.
797 *
798 * Return value: %TRUE if debugging is enabled, %FALSE otherwise
799 **/
800 gboolean
totem_pl_parser_is_debugging_enabled(TotemPlParser * parser)801 totem_pl_parser_is_debugging_enabled (TotemPlParser *parser)
802 {
803 return parser->priv->debug;
804 }
805
806 /**
807 * totem_pl_parser_base_uri:
808 * @uri: a URI
809 *
810 * Returns the parent URI of @uri.
811 *
812 * Return value: a newly-allocated string containing @uri's parent URI, or %NULL
813 **/
814 char *
totem_pl_parser_base_uri(GFile * uri)815 totem_pl_parser_base_uri (GFile *uri)
816 {
817 GFile *parent;
818 char *ret;
819
820 parent = g_file_get_parent (uri);
821 ret = g_file_get_uri (parent);
822 g_object_unref (uri);
823
824 return ret;
825 }
826
827 /**
828 * totem_pl_parser_line_is_empty:
829 * @line: a playlist line to check
830 *
831 * Checks to see if the given string line is empty or %NULL,
832 * counting tabs and spaces, but not newlines, as "empty".
833 *
834 * Return value: %TRUE if @line is empty
835 **/
836 gboolean
totem_pl_parser_line_is_empty(const char * line)837 totem_pl_parser_line_is_empty (const char *line)
838 {
839 guint i;
840
841 if (line == NULL)
842 return TRUE;
843
844 for (i = 0; line[i] != '\0'; i++) {
845 if (line[i] != '\t' && line[i] != ' ')
846 return FALSE;
847 }
848 return TRUE;
849 }
850
851 /**
852 * totem_pl_parser_write_string:
853 * @handle: a #GFileOutputStream to an open file
854 * @buf: the string buffer to write out
855 * @cancellable: (allow-none): a #GCancellable, or %NULL
856 * @error: return location for a #GError, or %NULL
857 *
858 * Writes the string @buf out to the file specified by @handle.
859 * Possible error codes are as per totem_pl_parser_write_buffer().
860 *
861 * Return value: %TRUE on success
862 **/
863 gboolean
totem_pl_parser_write_string(GOutputStream * stream,const char * buf,GCancellable * cancellable,GError ** error)864 totem_pl_parser_write_string (GOutputStream *stream,
865 const char *buf,
866 GCancellable *cancellable,
867 GError **error)
868 {
869 guint len;
870
871 len = strlen (buf);
872 return totem_pl_parser_write_buffer (stream, buf, len, cancellable, error);
873 }
874
875 /**
876 * totem_pl_parser_write_buffer:
877 * @stream: a #GFileOutputStream to an open file
878 * @buf: the string buffer to write out
879 * @len: the length of the string to write out
880 * @cancellable: (allow-none): a #GCancellable, or %NULL
881 * @error: return location for a #GError, or %NULL
882 *
883 * Writes @len bytes of @buf to the file specified by @handle.
884 *
885 * A value of @len greater than #G_MAXSSIZE will cause a #G_IO_ERROR_INVALID_ARGUMENT argument.
886 *
887 * Return value: %TRUE on success
888 **/
889 gboolean
totem_pl_parser_write_buffer(GOutputStream * stream,const char * buf,guint len,GCancellable * cancellable,GError ** error)890 totem_pl_parser_write_buffer (GOutputStream *stream,
891 const char *buf,
892 guint len,
893 GCancellable *cancellable,
894 GError **error)
895 {
896 gsize bytes_written;
897
898 if (g_output_stream_write_all (stream,
899 buf, len,
900 &bytes_written,
901 cancellable, error) == FALSE) {
902 g_object_unref (stream);
903 return FALSE;
904 }
905
906 return TRUE;
907 }
908
909 /**
910 * totem_pl_parser_num_entries:
911 * @parser: a #TotemPlParser
912 * @playlist: a #TotemPlPlaylist
913 *
914 * Returns the number of valid entries in @playlist.
915 *
916 * Return value: the number of entries in the playlist
917 **/
918 int
totem_pl_parser_num_entries(TotemPlParser * parser,TotemPlPlaylist * playlist)919 totem_pl_parser_num_entries (TotemPlParser *parser,
920 TotemPlPlaylist *playlist)
921 {
922 int num_entries, ignored;
923 TotemPlPlaylistIter iter;
924 gboolean valid;
925
926 num_entries = totem_pl_playlist_size (playlist);
927 valid = totem_pl_playlist_iter_first (playlist, &iter);
928 ignored = 0;
929
930 while (valid) {
931 gchar *uri;
932 GFile *file;
933
934 totem_pl_playlist_get (playlist, &iter,
935 TOTEM_PL_PARSER_FIELD_URI, &uri,
936 NULL);
937
938 valid = totem_pl_playlist_iter_next (playlist, &iter);
939
940 if (!uri) {
941 ignored++;
942 continue;
943 }
944
945 file = g_file_new_for_uri (uri);
946
947 if (totem_pl_parser_scheme_is_ignored (parser, file)) {
948 ignored++;
949 }
950
951 g_object_unref (file);
952 g_free (uri);
953 }
954
955 return num_entries - ignored;
956 }
957
958 char *
totem_pl_parser_relative(GFile * output,const char * filepath)959 totem_pl_parser_relative (GFile *output, const char *filepath)
960 {
961 GFile *parent, *file;
962 char *retval;
963
964 parent = g_file_get_parent (output);
965 file = g_file_new_for_commandline_arg (filepath);
966
967 retval = g_file_get_relative_path (parent, file);
968
969 g_object_unref (parent);
970 g_object_unref (file);
971
972 return retval;
973 }
974
975 static char *
relative_uri_remove_query(const char * uri,char ** query)976 relative_uri_remove_query (const char *uri, char **query)
977 {
978 char *qmark;
979
980 /* Look for '?' */
981 qmark = strrchr (uri, '?');
982 if (qmark == NULL)
983 return NULL;
984
985 if (query != NULL)
986 *query = g_strdup (qmark);
987 return g_strndup (uri, qmark - uri);
988 }
989
990 static const char *suffixes[] = {
991 ".jsp",
992 ".php",
993 ".asp"
994 };
995
996 static gboolean
is_probably_dir(const char * filename)997 is_probably_dir (const char *filename)
998 {
999 gboolean ret;
1000 char *content_type, *short_name;
1001
1002 short_name = relative_uri_remove_query (filename, NULL);
1003 if (short_name == NULL)
1004 short_name = g_strdup (filename);
1005 content_type = g_content_type_guess (short_name, NULL, 0, NULL);
1006 if (g_content_type_is_unknown (content_type) != FALSE) {
1007 guint i;
1008 for (i = 0; i < G_N_ELEMENTS (suffixes); i++) {
1009 if (g_str_has_suffix (short_name, suffixes[i]) != FALSE) {
1010 g_free (content_type);
1011 g_free (short_name);
1012 return FALSE;
1013 }
1014 }
1015 ret = TRUE;
1016 } else {
1017 ret = FALSE;
1018 }
1019 g_free (content_type);
1020 g_free (short_name);
1021
1022 return ret;
1023 }
1024
1025 char *
totem_pl_parser_resolve_uri(GFile * base_gfile,const char * relative_uri)1026 totem_pl_parser_resolve_uri (GFile *base_gfile,
1027 const char *relative_uri)
1028 {
1029 char *uri, *scheme, *query, *new_relative_uri, *base_uri;
1030 GFile *base_parent_gfile, *resolved_gfile;
1031
1032 if (relative_uri == NULL) {
1033 if (base_gfile == NULL)
1034 return NULL;
1035 return g_file_get_uri (base_gfile);
1036 }
1037
1038 if (base_gfile == NULL)
1039 return g_strdup (relative_uri);
1040
1041 /* If |relative_uri| has a scheme, it's a full URI, just return it */
1042 scheme = g_uri_parse_scheme (relative_uri);
1043 if (scheme != NULL) {
1044 g_free (scheme);
1045 return g_strdup (relative_uri);
1046 }
1047
1048 /* Check whether we need to get the parent for the base or not */
1049 base_uri = g_file_get_path (base_gfile);
1050 if (base_uri == NULL)
1051 base_uri = g_file_get_uri (base_gfile);
1052 if (is_probably_dir (base_uri) == FALSE)
1053 base_parent_gfile = g_file_get_parent (base_gfile);
1054 else
1055 base_parent_gfile = g_object_ref (base_gfile);
1056 g_free (base_uri);
1057
1058 if (base_parent_gfile == NULL) {
1059 resolved_gfile = g_file_resolve_relative_path (base_gfile, relative_uri);
1060 uri = g_file_get_uri (resolved_gfile);
1061 g_object_unref (resolved_gfile);
1062 return uri;
1063 }
1064
1065 /* Remove the query portion of the URI, to transplant it again
1066 * if there is any */
1067 query = NULL;
1068 new_relative_uri = relative_uri_remove_query (relative_uri, &query);
1069
1070 if (new_relative_uri) {
1071 char *tmpuri;
1072
1073 resolved_gfile = g_file_resolve_relative_path (base_parent_gfile, new_relative_uri);
1074 g_object_unref (base_parent_gfile);
1075 if (!resolved_gfile) {
1076 base_uri = g_file_get_uri (base_gfile);
1077 g_warning ("Failed to resolve relative URI '%s' against base '%s'\n", relative_uri, base_uri);
1078 g_free (base_uri);
1079 g_free (new_relative_uri);
1080 g_free (query);
1081 return NULL;
1082 }
1083
1084 tmpuri = g_file_get_uri (resolved_gfile);
1085 g_object_unref (resolved_gfile);
1086 uri = g_strdup_printf ("%s%s", tmpuri, query);
1087
1088 g_free (tmpuri);
1089 g_free (new_relative_uri);
1090 g_free (query);
1091
1092 return uri;
1093 } else {
1094 resolved_gfile = g_file_resolve_relative_path (base_parent_gfile, relative_uri);
1095 g_object_unref (base_parent_gfile);
1096 if (!resolved_gfile) {
1097 base_uri = g_file_get_uri (base_gfile);
1098 g_warning ("Failed to resolve relative URI '%s' against base '%s'\n", relative_uri, base_uri);
1099 g_free (base_uri);
1100 return NULL;
1101 }
1102
1103 uri = g_file_get_uri (resolved_gfile);
1104 g_object_unref (resolved_gfile);
1105
1106 return uri;
1107 }
1108 }
1109
1110 #ifndef TOTEM_PL_PARSER_MINI
1111 typedef struct {
1112 TotemPlPlaylist *playlist;
1113 GFile *dest;
1114 char *title;
1115 TotemPlParserType type;
1116 } PlParserSaveData;
1117
1118 static void
pl_parser_save_data_free(PlParserSaveData * data)1119 pl_parser_save_data_free (PlParserSaveData *data)
1120 {
1121 g_clear_object (&data->playlist);
1122 g_clear_object (&data->dest);
1123 g_clear_pointer (&data->title, g_free);
1124 g_free (data);
1125 }
1126
1127 static void
pl_parser_save_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1128 pl_parser_save_thread (GTask *task,
1129 gpointer source_object,
1130 gpointer task_data,
1131 GCancellable *cancellable)
1132 {
1133 PlParserSaveData *data = task_data;
1134 GError *error = NULL;
1135 gboolean ret = FALSE;
1136
1137 switch (data->type) {
1138 case TOTEM_PL_PARSER_PLS:
1139 ret = totem_pl_parser_save_pls (source_object,
1140 data->playlist,
1141 data->dest,
1142 data->title,
1143 cancellable,
1144 &error);
1145 break;
1146 case TOTEM_PL_PARSER_M3U:
1147 case TOTEM_PL_PARSER_M3U_DOS:
1148 ret = totem_pl_parser_save_m3u (source_object,
1149 data->playlist,
1150 data->dest,
1151 (data->type == TOTEM_PL_PARSER_M3U_DOS),
1152 cancellable,
1153 &error);
1154 break;
1155 case TOTEM_PL_PARSER_XSPF:
1156 ret = totem_pl_parser_save_xspf (source_object,
1157 data->playlist,
1158 data->dest,
1159 data->title,
1160 cancellable,
1161 &error);
1162 break;
1163 case TOTEM_PL_PARSER_IRIVER_PLA:
1164 ret = totem_pl_parser_save_pla (source_object,
1165 data->playlist,
1166 data->dest,
1167 data->title,
1168 cancellable,
1169 &error);
1170 break;
1171 default:
1172 g_assert_not_reached ();
1173 }
1174
1175 if (ret == FALSE)
1176 g_task_return_error (task, error);
1177 else
1178 g_task_return_boolean (task, TRUE);
1179 }
1180
1181 static gboolean
pl_parser_save_check_size(TotemPlPlaylist * playlist,GTask * task)1182 pl_parser_save_check_size (TotemPlPlaylist *playlist,
1183 GTask *task)
1184 {
1185 if (totem_pl_playlist_size (playlist) > 0)
1186 return TRUE;
1187
1188 /* FIXME add translation */
1189 g_task_return_new_error (task,
1190 TOTEM_PL_PARSER_ERROR,
1191 TOTEM_PL_PARSER_ERROR_EMPTY_PLAYLIST,
1192 "Playlist selected for saving is empty");
1193 g_object_unref (task);
1194 return FALSE;
1195 }
1196
1197 /**
1198 * totem_pl_parser_save:
1199 * @parser: a #TotemPlParser
1200 * @playlist: a #TotemPlPlaylist
1201 * @dest: output #GFile
1202 * @title: the playlist title
1203 * @type: a #TotemPlParserType for the outputted playlist
1204 * @error: return location for a #GError, or %NULL
1205 *
1206 * Writes the playlist held by @parser and @playlist out to the path
1207 * pointed by @dest. The playlist is written in the format @type and is
1208 * given the title @title.
1209 *
1210 * If the @output file is a directory the #G_IO_ERROR_IS_DIRECTORY error
1211 * will be returned, and if the file is some other form of non-regular file
1212 * then a #G_IO_ERROR_NOT_REGULAR_FILE error will be returned. Some file
1213 * systems don't allow all file names, and may return a
1214 * #G_IO_ERROR_INVALID_FILENAME error, and if the name is too long,
1215 * #G_IO_ERROR_FILENAME_TOO_LONG will be returned. Other errors are possible
1216 * too, and depend on what kind of filesystem the file is on.
1217 *
1218 * In extreme cases, a #G_IO_ERROR_INVALID_ARGUMENT error can be returned, if
1219 * parts of the playlist to be written are too long.
1220 *
1221 * If writing a PLA playlist and there is an error converting a URI's encoding,
1222 * a code from #GConvertError will be returned.
1223 *
1224 * Returns: %TRUE on success
1225 **/
1226 gboolean
totem_pl_parser_save(TotemPlParser * parser,TotemPlPlaylist * playlist,GFile * dest,const gchar * title,TotemPlParserType type,GError ** error)1227 totem_pl_parser_save (TotemPlParser *parser,
1228 TotemPlPlaylist *playlist,
1229 GFile *dest,
1230 const gchar *title,
1231 TotemPlParserType type,
1232 GError **error)
1233 {
1234 GTask *task;
1235 PlParserSaveData *data;
1236
1237 g_return_val_if_fail (TOTEM_PL_IS_PARSER (parser), FALSE);
1238 g_return_val_if_fail (TOTEM_PL_IS_PLAYLIST (playlist), FALSE);
1239 g_return_val_if_fail (G_IS_FILE (dest), FALSE);
1240
1241 task = g_task_new (parser, NULL, NULL, NULL);
1242 if (!pl_parser_save_check_size (playlist, task))
1243 return g_task_propagate_boolean (task, error);
1244
1245 data = g_new0 (PlParserSaveData, 1);
1246 data->playlist = g_object_ref (playlist);
1247 data->dest = g_object_ref (dest);
1248 data->title = g_strdup (title);
1249 data->type = type;
1250
1251 g_task_set_task_data (task, data, (GDestroyNotify) pl_parser_save_data_free);
1252 g_task_run_in_thread_sync (task, pl_parser_save_thread);
1253
1254 return g_task_propagate_boolean (task, error);
1255 }
1256
1257 /**
1258 * totem_pl_parser_save_async:
1259 * @parser: a #TotemPlParser
1260 * @playlist: a #TotemPlPlaylist
1261 * @dest: output #GFile
1262 * @title: the playlist title
1263 * @type: a #TotemPlParserType for the outputted playlist
1264 * @cancellable: (allow-none): a #GCancellable, or %NULL
1265 * @callback: (allow-none): a #GAsyncReadyCallback to call when saving has finished
1266 * @user_data: data to pass to the @callback function
1267 *
1268 * Starts asynchronous version of totem_pl_parser_save(). For more details
1269 * see totem_pl_parser_save().
1270 *
1271 * When the operation is finished, @callback will be called. You can then call
1272 * totem_pl_parser_save_finish() to get the results of the operation.
1273 **/
1274 void
totem_pl_parser_save_async(TotemPlParser * parser,TotemPlPlaylist * playlist,GFile * dest,const gchar * title,TotemPlParserType type,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1275 totem_pl_parser_save_async (TotemPlParser *parser,
1276 TotemPlPlaylist *playlist,
1277 GFile *dest,
1278 const gchar *title,
1279 TotemPlParserType type,
1280 GCancellable *cancellable,
1281 GAsyncReadyCallback callback,
1282 gpointer user_data)
1283 {
1284 GTask *task;
1285 PlParserSaveData *data;
1286
1287 g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
1288 g_return_if_fail (TOTEM_PL_IS_PLAYLIST (playlist));
1289 g_return_if_fail (G_IS_FILE (dest));
1290
1291 task = g_task_new (parser, cancellable, callback, user_data);
1292 if (!pl_parser_save_check_size (playlist, task))
1293 return;
1294
1295 data = g_new0 (PlParserSaveData, 1);
1296 data->playlist = g_object_ref (playlist);
1297 data->dest = g_object_ref (dest);
1298 data->title = g_strdup (title);
1299 data->type = type;
1300
1301 g_task_set_task_data (task, data, (GDestroyNotify) pl_parser_save_data_free);
1302 g_task_run_in_thread (task, pl_parser_save_thread);
1303 }
1304
1305 /**
1306 * totem_pl_parser_save_finish:
1307 * @parser: a #TotemPlParser
1308 * @async_result: a #GAsyncResult
1309 * @error: a #GError, or %NULL
1310 *
1311 * Finishes an asynchronous playlist saving operation started with totem_pl_parser_save_async().
1312 *
1313 * If saving of the playlist is cancelled part-way through, %G_IO_ERROR_CANCELLED will be
1314 * returned when this function is called.
1315 *
1316 * Return value: %TRUE on success, %FALSE on failure.
1317 **/
1318 gboolean
totem_pl_parser_save_finish(TotemPlParser * parser,GAsyncResult * async_result,GError ** error)1319 totem_pl_parser_save_finish (TotemPlParser *parser,
1320 GAsyncResult *async_result,
1321 GError **error)
1322 {
1323 g_return_val_if_fail (g_task_is_valid (async_result, parser), NULL);
1324
1325 return g_task_propagate_boolean (G_TASK (async_result), error);
1326 }
1327 #endif /* TOTEM_PL_PARSER_MINI */
1328
1329 /**
1330 * totem_pl_parser_read_ini_line_int:
1331 * @lines: a NULL-terminated array of INI lines to read
1332 * @key: the key to match
1333 *
1334 * Returns the first integer value case-insensitively matching the specified
1335 * key as an integer. The parser ignores leading whitespace on lines.
1336 *
1337 * Return value: the integer value, or -1 on error
1338 **/
1339 int
totem_pl_parser_read_ini_line_int(char ** lines,const char * key)1340 totem_pl_parser_read_ini_line_int (char **lines, const char *key)
1341 {
1342 int retval = -1;
1343 int i;
1344
1345 if (lines == NULL || key == NULL)
1346 return -1;
1347
1348 for (i = 0; (lines[i] != NULL && retval == -1); i++) {
1349 char *line = lines[i];
1350
1351 while (*line == '\t' || *line == ' ')
1352 line++;
1353
1354 if (g_ascii_strncasecmp (line, key, strlen (key)) == 0) {
1355 char **bits;
1356
1357 bits = g_strsplit (line, "=", 2);
1358 if (bits[0] == NULL || bits [1] == NULL) {
1359 g_strfreev (bits);
1360 return -1;
1361 }
1362
1363 retval = (gint) g_strtod (bits[1], NULL);
1364 g_strfreev (bits);
1365 }
1366 }
1367
1368 return retval;
1369 }
1370
1371 /**
1372 * totem_pl_parser_read_ini_line_string_with_sep:
1373 * @lines: a NULL-terminated array of INI lines to read
1374 * @key: the key to match
1375 * @sep: the key-value separator
1376 *
1377 * Returns the first string value case-insensitively matching the specified
1378 * key, where the two are separated by @sep. The parser ignores leading whitespace
1379 * on lines.
1380 *
1381 * Return value: a newly-allocated string value, or %NULL
1382 **/
1383 char*
totem_pl_parser_read_ini_line_string_with_sep(char ** lines,const char * key,const char * sep)1384 totem_pl_parser_read_ini_line_string_with_sep (char **lines, const char *key,
1385 const char *sep)
1386 {
1387 char *retval = NULL;
1388 int i;
1389
1390 if (lines == NULL || key == NULL)
1391 return NULL;
1392
1393 for (i = 0; (lines[i] != NULL && retval == NULL); i++) {
1394 char *line = lines[i];
1395
1396 while (*line == '\t' || *line == ' ')
1397 line++;
1398
1399 if (g_ascii_strncasecmp (line, key, strlen (key)) == 0) {
1400 char **bits;
1401
1402 bits = g_strsplit (line, sep, 2);
1403 if (bits[0] == NULL || bits [1] == NULL) {
1404 g_strfreev (bits);
1405 return NULL;
1406 }
1407
1408 retval = g_strdup (bits[1]);
1409 g_strfreev (bits);
1410 }
1411 }
1412
1413 return retval;
1414 }
1415
1416 /**
1417 * totem_pl_parser_read_ini_line_string:
1418 * @lines: a NULL-terminated array of INI lines to read
1419 * @key: the key to match
1420 *
1421 * Returns the first string value case-insensitively matching the
1422 * specified key. The parser ignores leading whitespace on lines.
1423 *
1424 * Return value: a newly-allocated string value, or %NULL
1425 **/
1426 char*
totem_pl_parser_read_ini_line_string(char ** lines,const char * key)1427 totem_pl_parser_read_ini_line_string (char **lines, const char *key)
1428 {
1429 return totem_pl_parser_read_ini_line_string_with_sep (lines, key, "=");
1430 }
1431
1432 static void
totem_pl_parser_init(TotemPlParser * parser)1433 totem_pl_parser_init (TotemPlParser *parser)
1434 {
1435 parser->priv = g_new0 (TotemPlParserPrivate, 1);
1436 parser->priv->main_thread = g_thread_self ();
1437 g_mutex_init (&parser->priv->ignore_mutex);
1438 parser->priv->ignore_schemes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1439 parser->priv->ignore_mimetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1440 parser->priv->ignore_globs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1441 }
1442
1443 static void
totem_pl_parser_finalize(GObject * object)1444 totem_pl_parser_finalize (GObject *object)
1445 {
1446 TotemPlParser *parser = TOTEM_PL_PARSER (object);
1447 TotemPlParserPrivate *priv = parser->priv;
1448
1449 g_clear_pointer (&priv->ignore_schemes, g_hash_table_destroy);
1450 g_clear_pointer (&priv->ignore_mimetypes, g_hash_table_destroy);
1451 g_clear_pointer (&priv->ignore_globs, g_hash_table_destroy);
1452 g_mutex_clear (&priv->ignore_mutex);
1453 g_clear_pointer (&parser->priv, g_free);
1454
1455 G_OBJECT_CLASS (totem_pl_parser_parent_class)->finalize (object);
1456 }
1457
1458 typedef struct {
1459 TotemPlParser *parser;
1460 guint signal_id;
1461 char *uri;
1462 GHashTable *metadata;
1463 } EntryParsedSignalData;
1464
1465 static gboolean
emit_entry_parsed_signal(EntryParsedSignalData * data)1466 emit_entry_parsed_signal (EntryParsedSignalData *data)
1467 {
1468 g_signal_emit (data->parser, data->signal_id, 0, data->uri, data->metadata);
1469
1470 /* Free the data */
1471 g_object_unref (data->parser);
1472 g_free (data->uri);
1473 g_hash_table_unref (data->metadata);
1474 g_free (data);
1475
1476 return FALSE;
1477 }
1478
1479 gboolean
totem_pl_parser_fix_string(const char * name,const char * value,char ** ret)1480 totem_pl_parser_fix_string (const char *name,
1481 const char *value,
1482 char **ret)
1483 {
1484 char *fixed = NULL;
1485
1486 /* Check for UTF-8 or ISO8859-1 string */
1487 if (g_utf8_validate (value, -1, NULL) == FALSE) {
1488 fixed = g_convert (value, -1, "UTF-8", "ISO8859-1", NULL, NULL, NULL);
1489 if (fixed == NULL) {
1490 g_warning ("Ignored non-UTF-8 and non-ISO8859-1 string for field '%s'", name);
1491 return FALSE;
1492 }
1493 }
1494
1495 /* Remove trailing spaces from titles */
1496 if (g_str_equal (name, TOTEM_PL_PARSER_FIELD_TITLE)) {
1497 if (fixed == NULL)
1498 fixed = g_strchomp (g_strdup (value));
1499 else
1500 g_strchomp (fixed);
1501 }
1502
1503 *ret = fixed;
1504
1505 return TRUE;
1506 }
1507
1508 void
totem_pl_parser_add_hash_table(TotemPlParser * parser,GHashTable * metadata,const char * uri,gboolean is_playlist)1509 totem_pl_parser_add_hash_table (TotemPlParser *parser,
1510 GHashTable *metadata,
1511 const char *uri,
1512 gboolean is_playlist)
1513 {
1514 if (g_hash_table_size (metadata) > 0 || uri != NULL) {
1515 EntryParsedSignalData *data;
1516
1517 /* Make sure to emit the signals asynchronously, as we could be in the main loop
1518 * *or* a worker thread at this point. */
1519 data = g_new (EntryParsedSignalData, 1);
1520 data->parser = g_object_ref (parser);
1521 data->uri = g_strdup (uri);
1522 data->metadata = g_hash_table_ref (metadata);
1523
1524 if (is_playlist == FALSE)
1525 data->signal_id = totem_pl_parser_table_signals[ENTRY_PARSED];
1526 else
1527 data->signal_id = totem_pl_parser_table_signals[PLAYLIST_STARTED];
1528
1529 CALL_ASYNC (parser, emit_entry_parsed_signal, data);
1530 }
1531 }
1532
1533 static void
totem_pl_parser_add_uri_valist(TotemPlParser * parser,const gchar * first_property_name,va_list var_args)1534 totem_pl_parser_add_uri_valist (TotemPlParser *parser,
1535 const gchar *first_property_name,
1536 va_list var_args)
1537 {
1538 const char *name;
1539 char *uri;
1540 GHashTable *metadata;
1541 gboolean is_playlist;
1542
1543 uri = NULL;
1544 is_playlist = FALSE;
1545
1546 g_object_ref (G_OBJECT (parser));
1547 metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1548
1549 name = first_property_name;
1550
1551 while (name) {
1552 GValue value = { 0, };
1553 GParamSpec *pspec;
1554 char *error = NULL;
1555 const char *string;
1556
1557 pspec = g_param_spec_pool_lookup (totem_pl_parser_pspec_pool,
1558 name,
1559 G_OBJECT_TYPE (parser),
1560 FALSE);
1561
1562 if (!pspec) {
1563 g_warning ("Unknown property '%s'", name);
1564 name = va_arg (var_args, char*);
1565 continue;
1566 }
1567
1568 g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
1569 G_VALUE_COLLECT (&value, var_args, 0, &error);
1570 if (error != NULL) {
1571 g_warning ("Error getting the value for property '%s'", name);
1572 break;
1573 }
1574
1575 if (strcmp (name, TOTEM_PL_PARSER_FIELD_URI) == 0) {
1576 if (uri == NULL)
1577 uri = g_value_dup_string (&value);
1578 } else if (strcmp (name, TOTEM_PL_PARSER_FIELD_FILE) == 0) {
1579 GFile *file;
1580
1581 file = g_value_get_object (&value);
1582 uri = g_file_get_uri (file);
1583
1584 g_value_unset (&value);
1585 name = va_arg (var_args, char*);
1586 continue;
1587 } else if (strcmp (name, TOTEM_PL_PARSER_FIELD_BASE_FILE) == 0) {
1588 GFile *file;
1589 char *base_uri;
1590
1591 file = g_value_get_object (&value);
1592 base_uri = g_file_get_uri (file);
1593
1594 g_hash_table_insert (metadata,
1595 g_strdup (TOTEM_PL_PARSER_FIELD_BASE),
1596 base_uri);
1597
1598 g_value_unset (&value);
1599 name = va_arg (var_args, char*);
1600 continue;
1601 } else if (strcmp (name, TOTEM_PL_PARSER_FIELD_IS_PLAYLIST) == 0) {
1602 is_playlist = g_value_get_boolean (&value);
1603 g_value_unset (&value);
1604 name = va_arg (var_args, char*);
1605 continue;
1606 }
1607
1608 /* Ignore empty values */
1609 string = g_value_get_string (&value);
1610 if (string != NULL && string[0] != '\0') {
1611 char *fixed = NULL;
1612
1613 if (!totem_pl_parser_fix_string (name, string, &fixed)) {
1614 g_value_unset (&value);
1615 name = va_arg (var_args, char*);
1616 continue;
1617 }
1618
1619 /* Add other values to the metadata hashtable */
1620 g_hash_table_insert (metadata,
1621 g_strdup (name),
1622 fixed ? fixed : g_strdup (string));
1623 }
1624
1625 g_value_unset (&value);
1626 name = va_arg (var_args, char*);
1627 }
1628
1629 if (parser->priv->disable_unsafe != FALSE) {
1630 //FIXME fix this! 396710
1631 }
1632
1633 totem_pl_parser_add_hash_table (parser,
1634 metadata,
1635 uri,
1636 is_playlist);
1637
1638 g_hash_table_unref (metadata);
1639
1640 g_free (uri);
1641 g_object_unref (G_OBJECT (parser));
1642 }
1643
1644 /**
1645 * totem_pl_parser_add_uri:
1646 * @parser: a #TotemPlParser
1647 * @first_property_name: the first property name
1648 * @...: value for the first property, followed optionally by more
1649 * name/value pairs, followed by %NULL
1650 *
1651 * Adds a URI to the playlist with the properties given in @first_property_name
1652 * and @....
1653 **/
1654 void
totem_pl_parser_add_uri(TotemPlParser * parser,const char * first_property_name,...)1655 totem_pl_parser_add_uri (TotemPlParser *parser,
1656 const char *first_property_name,
1657 ...)
1658 {
1659 va_list var_args;
1660 va_start (var_args, first_property_name);
1661 totem_pl_parser_add_uri_valist (parser, first_property_name, var_args);
1662 va_end (var_args);
1663 }
1664
1665 /**
1666 * totem_pl_parser_add_one_uri:
1667 * @parser: a #TotemPlParser
1668 * @uri: the entry's URI
1669 * @title: the entry's title
1670 *
1671 * Adds a single URI entry with only URI and title strings to the playlist.
1672 **/
1673 void
totem_pl_parser_add_one_uri(TotemPlParser * parser,const char * uri,const char * title)1674 totem_pl_parser_add_one_uri (TotemPlParser *parser, const char *uri, const char *title)
1675 {
1676 totem_pl_parser_add_uri (parser,
1677 TOTEM_PL_PARSER_FIELD_URI, uri,
1678 TOTEM_PL_PARSER_FIELD_TITLE, title,
1679 NULL);
1680 }
1681
1682 void
totem_pl_parser_add_one_file(TotemPlParser * parser,GFile * file,const char * title)1683 totem_pl_parser_add_one_file (TotemPlParser *parser, GFile *file, const char *title)
1684 {
1685 totem_pl_parser_add_uri (parser,
1686 TOTEM_PL_PARSER_FIELD_FILE, file,
1687 TOTEM_PL_PARSER_FIELD_TITLE, title,
1688 NULL);
1689 }
1690
1691 static PlaylistTypes ignore_types[] = {
1692 PLAYLIST_TYPE3 ("image/*"),
1693 PLAYLIST_TYPE3 ("text/plain"),
1694 PLAYLIST_TYPE3 ("application/x-rar"),
1695 PLAYLIST_TYPE3 ("application/zip"),
1696 PLAYLIST_TYPE3 ("application/x-trash"),
1697 };
1698
1699 /**
1700 * totem_pl_parser_scheme_is_ignored:
1701 * @parser: a #TotemPlParser
1702 * @uri: a URI
1703 *
1704 * Checks to see if @uri's scheme is in the @parser's list of
1705 * schemes to ignore.
1706 *
1707 * Return value: %TRUE if @uri's scheme is ignored
1708 **/
1709 gboolean
totem_pl_parser_scheme_is_ignored(TotemPlParser * parser,GFile * uri)1710 totem_pl_parser_scheme_is_ignored (TotemPlParser *parser, GFile *uri)
1711 {
1712 char *scheme;
1713 gboolean ret;
1714
1715 g_mutex_lock (&parser->priv->ignore_mutex);
1716
1717 scheme = g_file_get_uri_scheme (uri);
1718 if (!scheme) {
1719 g_mutex_unlock (&parser->priv->ignore_mutex);
1720 return TRUE;
1721 }
1722 ret = GPOINTER_TO_INT (g_hash_table_lookup (parser->priv->ignore_schemes, scheme));
1723 g_free (scheme);
1724
1725 g_mutex_unlock (&parser->priv->ignore_mutex);
1726
1727 return ret;
1728 }
1729
1730 static gboolean
totem_pl_parser_mimetype_is_ignored(TotemPlParser * parser,const char * mimetype)1731 totem_pl_parser_mimetype_is_ignored (TotemPlParser *parser,
1732 const char *mimetype)
1733 {
1734 gboolean ret;
1735
1736 g_mutex_lock (&parser->priv->ignore_mutex);
1737 ret = GPOINTER_TO_INT (g_hash_table_lookup (parser->priv->ignore_mimetypes, mimetype));
1738 g_mutex_unlock (&parser->priv->ignore_mutex);
1739
1740 return ret;
1741 }
1742
1743 static gboolean
totem_pl_parser_glob_is_ignored(TotemPlParser * parser,const char * filename)1744 totem_pl_parser_glob_is_ignored (TotemPlParser *parser,
1745 const char *filename)
1746 {
1747 GHashTableIter iter;
1748 gpointer key;
1749 int ret;
1750
1751 g_mutex_lock (&parser->priv->ignore_mutex);
1752 g_hash_table_iter_init (&iter, parser->priv->ignore_globs);
1753 while (g_hash_table_iter_next (&iter, &key, NULL)) {
1754 const char *glob = key;
1755
1756 ret = fnmatch (glob, filename, 0);
1757 if (ret == 0)
1758 break;
1759 }
1760 g_mutex_unlock (&parser->priv->ignore_mutex);
1761
1762 return (ret == 0);
1763 }
1764
1765 /**
1766 * totem_pl_parser_ignore:
1767 * @parser: a #TotemPlParser
1768 * @uri: a URI
1769 *
1770 * Checks if the URI should be ignored. URIs are <emphasis>not</emphasis> ignored if:
1771 * <itemizedlist>
1772 * <listitem><para>they have an unknown mimetype,</para></listitem>
1773 * <listitem><para>they have a special mimetype,</para></listitem>
1774 * <listitem><para>they have a mimetype which could be a video or a playlist.</para></listitem>
1775 * </itemizedlist>
1776 *
1777 * URIs are automatically ignored if their scheme is ignored as per totem_pl_parser_scheme_is_ignored(),
1778 * and are ignored if all the other tests are inconclusive.
1779 *
1780 * Return value: %TRUE if @uri is to be ignored
1781 **/
1782 gboolean
totem_pl_parser_ignore(TotemPlParser * parser,const char * uri)1783 totem_pl_parser_ignore (TotemPlParser *parser, const char *uri)
1784 {
1785 g_autofree char *mimetype;
1786 g_autoptr(GFile) file;
1787 guint i;
1788
1789 if (totem_pl_parser_glob_is_ignored (parser, uri) != FALSE)
1790 return TRUE;
1791
1792 file = g_file_new_for_path (uri);
1793 if (totem_pl_parser_scheme_is_ignored (parser, file) != FALSE)
1794 return TRUE;
1795
1796 //FIXME wrong for win32
1797 mimetype = g_content_type_guess (uri, NULL, 0, NULL);
1798 if (mimetype == NULL || strcmp (mimetype, UNKNOWN_TYPE) == 0)
1799 return FALSE;
1800
1801 for (i = 0; i < G_N_ELEMENTS (special_types); i++) {
1802 if (strcmp (special_types[i].mimetype, mimetype) == 0)
1803 return FALSE;
1804 }
1805
1806 for (i = 0; i < G_N_ELEMENTS (dual_types); i++) {
1807 if (strcmp (dual_types[i].mimetype, mimetype) == 0)
1808 return FALSE;
1809 }
1810
1811 return TRUE;
1812 }
1813
1814 /**
1815 * totem_pl_parser_cleanup_xml:
1816 * @contents: the contents of the file
1817 *
1818 * Removes HTML comments from a string representing the contents of an XML file.
1819 * The function modifies the string in place.
1820 */
1821 static void
totem_pl_parser_cleanup_xml(char * contents)1822 totem_pl_parser_cleanup_xml (char *contents)
1823 {
1824 char *needle;
1825
1826 needle = contents;
1827 while ((needle = strstr (needle, "<!--")) != NULL) {
1828 char *end;
1829
1830 /* Find end of comments */
1831 end = strstr (needle, "-->");
1832 /* Broken file? */
1833 if (end == NULL)
1834 return;
1835 if (g_strstr_len (needle, end - needle, "]]>") != NULL) {
1836 /* Advance 3 and skip */
1837 needle += 3;
1838 continue;
1839 }
1840 /* Empty the comment */
1841 memset (needle, ' ', end + 3 - needle);
1842 }
1843 }
1844
1845 xml_node_t *
totem_pl_parser_parse_xml_relaxed(char * contents,gsize size)1846 totem_pl_parser_parse_xml_relaxed (char *contents,
1847 gsize size)
1848 {
1849 xml_node_t* doc, *node;
1850 char *encoding, *new_contents;
1851 gsize new_size;
1852 xml_parser_t *xml_parser;
1853
1854 totem_pl_parser_cleanup_xml (contents);
1855 xml_parser = xml_parser_init_r (contents, size, XML_PARSER_CASE_INSENSITIVE);
1856 if (xml_parser_build_tree_with_options_r (xml_parser, &doc, XML_PARSER_RELAXED | XML_PARSER_MULTI_TEXT) < 0) {
1857 xml_parser_finalize_r (xml_parser);
1858 return NULL;
1859 }
1860
1861 xml_parser_finalize_r (xml_parser);
1862
1863 encoding = NULL;
1864 for (node = doc; node != NULL; node = node->next) {
1865 if (node->name == NULL || g_str_equal (node->name, "?XML") == FALSE)
1866 continue;
1867 encoding = g_strdup (xml_parser_get_property (node, "ENCODING"));
1868 break;
1869 }
1870
1871 if (encoding == NULL || g_str_equal (encoding, "UTF-8") != FALSE) {
1872 g_free (encoding);
1873 return doc;
1874 }
1875
1876 xml_parser_free_tree (doc);
1877
1878 new_contents = g_convert (contents, size, "UTF-8", encoding, NULL, &new_size, NULL);
1879 if (new_contents == NULL) {
1880 g_warning ("Failed to convert XML data to UTF-8");
1881 g_free (encoding);
1882 return NULL;
1883 }
1884 g_free (encoding);
1885
1886 xml_parser = xml_parser_init_r (new_contents, new_size, XML_PARSER_CASE_INSENSITIVE);
1887 if (xml_parser_build_tree_with_options_r (xml_parser, &doc, XML_PARSER_RELAXED | XML_PARSER_MULTI_TEXT) < 0) {
1888 xml_parser_finalize_r (xml_parser);
1889 g_free (new_contents);
1890 return NULL;
1891 }
1892
1893 xml_parser_finalize_r (xml_parser);
1894 g_free (new_contents);
1895
1896 return doc;
1897 }
1898
1899 static gboolean
totem_pl_parser_ignore_from_mimetype(TotemPlParser * parser,const char * mimetype)1900 totem_pl_parser_ignore_from_mimetype (TotemPlParser *parser, const char *mimetype)
1901 {
1902 guint i;
1903
1904 if (mimetype == NULL)
1905 return FALSE;
1906
1907 for (i = 0; i < G_N_ELEMENTS (ignore_types); i++) {
1908 /* Up until we have a way to detect private inheritance
1909 * in shared-mime-info */
1910 if (strcmp (mimetype, "application/vnd.apple.mpegurl") != 0 &&
1911 strcmp (mimetype, "audio/x-mpegurl") != 0 &&
1912 strcmp (mimetype, "video/x-mjpeg") != 0 &&
1913 g_content_type_is_a (mimetype, ignore_types[i].mimetype) != FALSE) {
1914 if (parser->priv->debug)
1915 g_print ("Ignoring %s because it's a %s\n", mimetype, ignore_types[i].mimetype);
1916 return TRUE;
1917 }
1918 if (g_content_type_equals (mimetype, ignore_types[i].mimetype) != FALSE) {
1919 if (parser->priv->debug)
1920 g_print ("Ignoring %s because it's equal to %s\n", mimetype, ignore_types[i].mimetype);
1921 return TRUE;
1922 }
1923 }
1924
1925 return FALSE;
1926 }
1927
1928 static PlaylistCallback
totem_pl_parser_get_function_for_mimetype(const char * mimetype)1929 totem_pl_parser_get_function_for_mimetype (const char *mimetype)
1930 {
1931 guint i;
1932
1933 if (mimetype == NULL)
1934 return NULL;
1935
1936 for (i = 0; i < G_N_ELEMENTS(special_types); i++) {
1937 if (strcmp (special_types[i].mimetype, mimetype) == 0)
1938 return special_types[i].func;
1939 }
1940 for (i = 0; i < G_N_ELEMENTS(dual_types); i++) {
1941 if (strcmp (dual_types[i].mimetype, mimetype) == 0)
1942 return dual_types[i].func;
1943 }
1944 return NULL;
1945 }
1946
1947 TotemPlParserResult
totem_pl_parser_parse_internal(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data)1948 totem_pl_parser_parse_internal (TotemPlParser *parser,
1949 GFile *file,
1950 GFile *base_file,
1951 TotemPlParseData *parse_data)
1952 {
1953 g_autofree char *mimetype = NULL;
1954 g_autofree gpointer data = NULL;
1955 g_autofree char *uri = NULL;
1956 guint i;
1957 TotemPlParserResult ret = TOTEM_PL_PARSER_RESULT_UNHANDLED;
1958 gboolean found = FALSE;
1959
1960 if (parse_data->recurse_level > RECURSE_LEVEL_MAX)
1961 return TOTEM_PL_PARSER_RESULT_ERROR;
1962
1963 if (g_file_has_uri_scheme (file, "mms") != FALSE
1964 || g_file_has_uri_scheme (file, "rtsp") != FALSE
1965 || g_file_has_uri_scheme (file, "rtmp") != FALSE
1966 || g_file_has_uri_scheme (file, "icy") != FALSE
1967 || g_file_has_uri_scheme (file, "pnm") != FALSE) {
1968 DEBUG(file, g_print ("URI '%s' is MMS, RTSP, RTMP, PNM or ICY, not a playlist\n", uri));
1969 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
1970 }
1971
1972 /* Fix up itpc, see http://www.apple.com/itunes/store/podcaststechspecs.html,
1973 * feed:// as used by Firefox 3,
1974 * as well as zcast:// as used by ZENCast */
1975 if (g_file_has_uri_scheme (file, "itpc") != FALSE
1976 || g_file_has_uri_scheme (file, "feed") != FALSE
1977 || g_file_has_uri_scheme (file, "zcast") != FALSE) {
1978 DEBUG(file, g_print ("URI '%s' is getting special cased for ITPC/FEED/ZCAST parsing\n", uri));
1979 return totem_pl_parser_add_itpc (parser, file, base_file, parse_data, NULL);
1980 }
1981 if (g_file_has_uri_scheme (file, "zune") != FALSE) {
1982 DEBUG(file, g_print ("URI '%s' is getting special cased for ZUNE parsing\n", uri));
1983 return totem_pl_parser_add_zune (parser, file, base_file, parse_data, NULL);
1984 }
1985 /* Try itms Podcast references, see itunes.py in PenguinTV */
1986 if (totem_pl_parser_is_itms_feed (file) != FALSE) {
1987 DEBUG(file, g_print ("URI '%s' is getting special cased for ITMS parsing\n", uri));
1988 return totem_pl_parser_add_itms (parser, file, NULL, parse_data, NULL);
1989 }
1990
1991 if (!parse_data->recurse && parse_data->recurse_level > 0)
1992 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
1993
1994 uri = g_file_get_uri (file);
1995
1996 /* Should we try to parse it with quvi? */
1997 if (g_file_has_uri_scheme (file, "http") ||
1998 g_file_has_uri_scheme (file, "https")) {
1999 if (uri != NULL && totem_pl_parser_is_videosite (uri, parser->priv->debug) != FALSE) {
2000 ret = totem_pl_parser_add_videosite (parser, file, base_file, parse_data, NULL);
2001 if (ret == TOTEM_PL_PARSER_RESULT_SUCCESS)
2002 return ret;
2003 }
2004 }
2005
2006 if (uri != NULL) {
2007 if (totem_pl_parser_glob_is_ignored (parser, uri))
2008 return TOTEM_PL_PARSER_RESULT_IGNORED;
2009 }
2010
2011 /* In force mode we want to get the data */
2012 if (parse_data->force != FALSE) {
2013 mimetype = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2014 } else {
2015 char *uri;
2016
2017 uri = g_file_get_uri (file);
2018 #ifdef G_OS_WIN32
2019 {
2020 char *content_type;
2021 content_type = g_content_type_guess (uri, NULL, 0, NULL);
2022 mimetype = g_content_type_get_mime_type (content_type);
2023 g_free (content_type);
2024 }
2025 #else
2026 mimetype = g_content_type_guess (uri, NULL, 0, NULL);
2027 #endif
2028
2029 g_free (uri);
2030 }
2031
2032 /* We're much more likely to have an MP2T file instead */
2033 if (g_strcmp0 (mimetype, "application/x-linguist") == 0 ||
2034 g_strcmp0 (mimetype, "text/vnd.trolltech.linguist") == 0) {
2035 g_free (mimetype);
2036 mimetype = g_strdup ("video/mp2t");
2037 }
2038
2039 /* Not a directory on http though */
2040 if (g_strcmp0 (mimetype, "inode/directory") == 0 &&
2041 g_file_has_uri_scheme (file, "http")) {
2042 g_clear_pointer (&mimetype, g_free);
2043 }
2044
2045 DEBUG(file, g_print ("_get_mime_type_for_name for '%s' returned '%s'\n", uri, mimetype));
2046 if (mimetype == NULL ||
2047 strcmp (UNKNOWN_TYPE, mimetype) == 0 ||
2048 g_content_type_is_a (mimetype, "text/plain") != FALSE) {
2049 char *new_mimetype;
2050 new_mimetype = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2051 if (new_mimetype) {
2052 g_free (mimetype);
2053 mimetype = new_mimetype;
2054 DEBUG(file, g_print ("_get_mime_type_with_data for '%s' returned '%s'\n", uri, mimetype ? mimetype : "NULL"));
2055 } else {
2056 DEBUG(file, g_print ("_get_mime_type_with_data for '%s' returned NULL, ignoring\n", uri));
2057 }
2058 }
2059
2060 if (mimetype == NULL)
2061 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2062
2063 if (strcmp (mimetype, EMPTY_FILE_TYPE) == 0)
2064 return TOTEM_PL_PARSER_RESULT_SUCCESS;
2065 else if (strcmp (mimetype, HLS_MIME_TYPE) == 0)
2066 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2067
2068 /* If we're at the top-level of the parsing, try to get more
2069 * data from the playlist parser */
2070 if (strcmp (mimetype, AUDIO_MPEG_TYPE) == 0 && parse_data->recurse_level == 0 && data == NULL) {
2071 char *tmp;
2072 tmp = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2073 if (tmp != NULL) {
2074 g_free (mimetype);
2075 mimetype = tmp;
2076 }
2077 DEBUG(file, g_print ("_get_mime_type_with_data for '%s' returned '%s' (was %s)\n", uri, mimetype, AUDIO_MPEG_TYPE));
2078
2079 if (strcmp (mimetype, AUDIO_MPEG_TYPE) == 0)
2080 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2081 }
2082
2083 if (totem_pl_parser_mimetype_is_ignored (parser, mimetype) != FALSE)
2084 return TOTEM_PL_PARSER_RESULT_IGNORED;
2085
2086 if (parse_data->recurse || parse_data->recurse_level == 0) {
2087 parse_data->recurse_level++;
2088
2089 for (i = 0; i < G_N_ELEMENTS(special_types); i++) {
2090 if (strcmp (special_types[i].mimetype, mimetype) == 0) {
2091 DEBUG(file, g_print ("URI '%s' is special type '%s'\n", uri, mimetype));
2092 if (parse_data->disable_unsafe != FALSE && special_types[i].unsafe != FALSE) {
2093 DEBUG(file, g_print ("URI '%s' is unsafe so was ignored\n", uri));
2094 return TOTEM_PL_PARSER_RESULT_IGNORED;
2095 }
2096 if (base_file == NULL)
2097 base_file = g_file_get_parent (file);
2098 else
2099 base_file = g_object_ref (base_file);
2100
2101 DEBUG (file, g_print ("Using %s function for '%s'\n", special_types[i].mimetype, uri));
2102 ret = (* special_types[i].func) (parser, file, base_file, parse_data, data);
2103
2104 if (base_file != NULL)
2105 g_object_unref (base_file);
2106
2107 found = TRUE;
2108 break;
2109 }
2110 }
2111
2112 for (i = 0; i < G_N_ELEMENTS(dual_types) && found == FALSE; i++) {
2113 if (strcmp (dual_types[i].mimetype, mimetype) == 0) {
2114 PlaylistCallback func;
2115
2116 DEBUG(file, g_print ("URI '%s' is dual type '%s'\n", uri, mimetype));
2117 if (data == NULL) {
2118 g_free (mimetype);
2119 mimetype = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2120 DEBUG(file, g_print ("URI '%s' dual type has type '%s' from data\n", uri, mimetype));
2121 }
2122 /* If it's _still_ a text/plain, we don't want it */
2123 if (mimetype != NULL &&
2124 g_content_type_is_a (mimetype, "text/plain") &&
2125 g_content_type_is_a (mimetype, "application/xml") == FALSE) {
2126 DEBUG(file, g_print ("Ignoring URI '%s' dual type because '%s' is a text/plain\n", uri, mimetype));
2127 ret = TOTEM_PL_PARSER_RESULT_IGNORED;
2128 g_free (mimetype);
2129 mimetype = NULL;
2130 break;
2131 }
2132 /* Now look for the proper function to use */
2133 func = totem_pl_parser_get_function_for_mimetype (mimetype);
2134 if ((func == NULL && mimetype != NULL) || (mimetype == NULL && dual_types[i].func == NULL)) {
2135 DEBUG(file, g_print ("Ignoring URI '%s' because we couldn't find a playlist parser for '%s'\n", uri, mimetype));
2136 ret = TOTEM_PL_PARSER_RESULT_UNHANDLED;
2137 g_clear_pointer (&mimetype, g_free);
2138 break;
2139 } else if (func == NULL) {
2140 func = dual_types[i].func;
2141 }
2142
2143 if (base_file == NULL)
2144 base_file = g_file_get_parent (file);
2145 else
2146 base_file = g_object_ref (base_file);
2147
2148 ret = (* func) (parser, file, base_file ? base_file : file, parse_data, data);
2149
2150 if (base_file != NULL)
2151 g_object_unref (base_file);
2152
2153 found = TRUE;
2154 break;
2155 }
2156 }
2157
2158 parse_data->recurse_level--;
2159 }
2160
2161 if (ret == TOTEM_PL_PARSER_RESULT_SUCCESS)
2162 return ret;
2163
2164 if (totem_pl_parser_ignore_from_mimetype (parser, mimetype) != FALSE)
2165 return TOTEM_PL_PARSER_RESULT_IGNORED;
2166
2167 if (ret != TOTEM_PL_PARSER_RESULT_SUCCESS && parse_data->fallback) {
2168 totem_pl_parser_add_one_file (parser, file, NULL);
2169 return TOTEM_PL_PARSER_RESULT_SUCCESS;
2170 }
2171
2172 return ret;
2173 }
2174
2175 typedef struct {
2176 char *uri;
2177 char *base;
2178 gboolean fallback;
2179 } ParseAsyncData;
2180
2181 static void
parse_async_data_free(ParseAsyncData * data)2182 parse_async_data_free (ParseAsyncData *data)
2183 {
2184 g_free (data->uri);
2185 g_free (data->base);
2186 g_slice_free (ParseAsyncData, data);
2187 }
2188
2189 static void
parse_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)2190 parse_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
2191 {
2192 TotemPlParser *parser = TOTEM_PL_PARSER (source_object);
2193 TotemPlParserResult parse_result;
2194 GError *error = NULL;
2195 ParseAsyncData *data = task_data;
2196
2197 /* Check to see if it's been cancelled already */
2198 if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
2199 g_task_return_error (task, error);
2200 return;
2201 }
2202
2203 /* Parse and return */
2204 parse_result = totem_pl_parser_parse_with_base (parser, data->uri, data->base, data->fallback);
2205 g_task_return_int (task, parse_result);
2206 }
2207
2208 /**
2209 * totem_pl_parser_parse_with_base_async:
2210 * @parser: a #TotemPlParser
2211 * @uri: the URI of the playlist to parse
2212 * @base: (allow-none): the base path for relative filenames, or %NULL
2213 * @fallback: %TRUE if the parser should add the playlist URI to the
2214 * end of the playlist on parse failure
2215 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
2216 * @callback: (allow-none): a #GAsyncReadyCallback to call when parsing is finished
2217 * @user_data: data to pass to the @callback function
2218 *
2219 * Starts asynchronous parsing of a playlist given by the absolute URI @uri, using @base to resolve relative paths where appropriate.
2220 * @parser and @uri are both reffed/copied when this function is called, so can safely be freed after this function returns.
2221 *
2222 * For more details, see totem_pl_parser_parse_with_base(), which is the synchronous version of this function.
2223 *
2224 * When the operation is finished, @callback will be called. You can then call totem_pl_parser_parse_finish()
2225 * to get the results of the operation.
2226 **/
2227 void
totem_pl_parser_parse_with_base_async(TotemPlParser * parser,const char * uri,const char * base,gboolean fallback,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2228 totem_pl_parser_parse_with_base_async (TotemPlParser *parser, const char *uri, const char *base, gboolean fallback,
2229 GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
2230 {
2231 GTask *task;
2232 ParseAsyncData *data;
2233
2234 g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2235 g_return_if_fail (uri != NULL);
2236 g_return_if_fail (strstr (uri, "://") != NULL);
2237
2238 data = g_slice_new (ParseAsyncData);
2239 data->uri = g_strdup (uri);
2240 data->base = g_strdup (base);
2241 data->fallback = fallback;
2242
2243 task = g_task_new (parser, cancellable, callback, user_data);
2244 g_task_set_task_data (task, data, (GDestroyNotify) parse_async_data_free);
2245 g_task_run_in_thread (task, parse_thread);
2246 g_object_unref (task);
2247 }
2248
2249 /**
2250 * totem_pl_parser_parse_with_base:
2251 * @parser: a #TotemPlParser
2252 * @uri: the URI of the playlist to parse
2253 * @base: (allow-none): the base path for relative filenames, or %NULL
2254 * @fallback: %TRUE if the parser should add the playlist URI to the
2255 * end of the playlist on parse failure
2256 *
2257 * Parses a playlist given by the absolute URI @uri, using
2258 * @base to resolve relative paths where appropriate.
2259 *
2260 * Return value: a #TotemPlParserResult
2261 **/
2262 TotemPlParserResult
totem_pl_parser_parse_with_base(TotemPlParser * parser,const char * uri,const char * base,gboolean fallback)2263 totem_pl_parser_parse_with_base (TotemPlParser *parser, const char *uri,
2264 const char *base, gboolean fallback)
2265 {
2266 GFile *file, *base_file;
2267 TotemPlParserResult retval;
2268 TotemPlParseData data;
2269
2270 g_return_val_if_fail (TOTEM_PL_IS_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED);
2271 g_return_val_if_fail (uri != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED);
2272 g_return_val_if_fail (strstr (uri, "://") != NULL,
2273 TOTEM_PL_PARSER_RESULT_ERROR);
2274
2275 file = g_file_new_for_uri (uri);
2276 base_file = NULL;
2277
2278 if (totem_pl_parser_scheme_is_ignored (parser, file) != FALSE) {
2279 g_object_unref (file);
2280 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2281 }
2282
2283 /* Use a struct to store copies of the options as set for this parse operation */
2284 data.recurse_level = 0;
2285 data.fallback = fallback;
2286 data.recurse = parser->priv->recurse;
2287 data.force = parser->priv->force;
2288 data.disable_unsafe = parser->priv->disable_unsafe;
2289
2290 if (base != NULL)
2291 base_file = g_file_new_for_uri (base);
2292 retval = totem_pl_parser_parse_internal (parser, file, base_file, &data);
2293
2294 g_object_unref (file);
2295 if (base_file != NULL)
2296 g_object_unref (base_file);
2297
2298 return retval;
2299 }
2300
2301 /**
2302 * totem_pl_parser_parse_async:
2303 * @parser: a #TotemPlParser
2304 * @uri: the URI of the playlist to parse
2305 * @fallback: %TRUE if the parser should add the playlist URI to the
2306 * end of the playlist on parse failure
2307 * @cancellable: (allow-none): optional #GCancellable object, or %NULL
2308 * @callback: (allow-none): a #GAsyncReadyCallback to call when parsing is finished
2309 * @user_data: data to pass to the @callback function
2310 *
2311 * Starts asynchronous parsing of a playlist given by the absolute URI @uri. @parser and @uri are both reffed/copied
2312 * when this function is called, so can safely be freed after this function returns.
2313 *
2314 * For more details, see totem_pl_parser_parse(), which is the synchronous version of this function.
2315 *
2316 * When the operation is finished, @callback will be called. You can then call totem_pl_parser_parse_finish()
2317 * to get the results of the operation.
2318 **/
2319 void
totem_pl_parser_parse_async(TotemPlParser * parser,const char * uri,gboolean fallback,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2320 totem_pl_parser_parse_async (TotemPlParser *parser, const char *uri, gboolean fallback,
2321 GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
2322 {
2323 totem_pl_parser_parse_with_base_async (parser, uri, NULL, fallback, cancellable, callback, user_data);
2324 }
2325
2326 /**
2327 * totem_pl_parser_parse_finish:
2328 * @parser: a #TotemPlParser
2329 * @async_result: a #GAsyncResult
2330 * @error: a #GError, or %NULL
2331 *
2332 * Finishes an asynchronous playlist parsing operation started with totem_pl_parser_parse_async()
2333 * or totem_pl_parser_parse_with_base_async().
2334 *
2335 * If parsing of the playlist is cancelled part-way through, %TOTEM_PL_PARSER_RESULT_CANCELLED is returned when
2336 * this function is called.
2337 *
2338 * Return value: a #TotemPlParserResult
2339 **/
2340 TotemPlParserResult
totem_pl_parser_parse_finish(TotemPlParser * parser,GAsyncResult * async_result,GError ** error)2341 totem_pl_parser_parse_finish (TotemPlParser *parser, GAsyncResult *async_result, GError **error)
2342 {
2343 GTask *task = G_TASK (async_result);
2344
2345 g_return_val_if_fail (TOTEM_PL_IS_PARSER (parser), FALSE);
2346 g_return_val_if_fail (g_task_is_valid (async_result, parser), FALSE);
2347
2348 /* Propagate any errors which were caught and return the result; otherwise just return the result */
2349 return g_task_propagate_int (task, error);
2350 }
2351
2352 /**
2353 * totem_pl_parser_parse:
2354 * @parser: a #TotemPlParser
2355 * @uri: the URI of the playlist to parse
2356 * @fallback: %TRUE if the parser should add the playlist URI to the
2357 * end of the playlist on parse failure
2358 *
2359 * Parses a playlist given by the absolute URI @uri. This method is
2360 * synchronous, and will block on (e.g.) network requests to slow
2361 * servers. totem_pl_parser_parse_async() is recommended instead.
2362 *
2363 * Return values are as totem_pl_parser_parse_with_base().
2364 *
2365 * Return value: a #TotemPlParserResult
2366 **/
2367 TotemPlParserResult
totem_pl_parser_parse(TotemPlParser * parser,const char * uri,gboolean fallback)2368 totem_pl_parser_parse (TotemPlParser *parser, const char *uri,
2369 gboolean fallback)
2370 {
2371 return totem_pl_parser_parse_with_base (parser, uri, NULL, fallback);
2372 }
2373
2374 /**
2375 * totem_pl_parser_add_ignored_scheme:
2376 * @parser: a #TotemPlParser
2377 * @scheme: the scheme to ignore
2378 *
2379 * Adds a scheme to the list of schemes to ignore, so that
2380 * any URI using that scheme is ignored during playlist parsing.
2381 **/
2382 void
totem_pl_parser_add_ignored_scheme(TotemPlParser * parser,const char * scheme)2383 totem_pl_parser_add_ignored_scheme (TotemPlParser *parser,
2384 const char *scheme)
2385 {
2386 char *s;
2387
2388 g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2389
2390 g_mutex_lock (&parser->priv->ignore_mutex);
2391
2392 s = g_strdup (scheme);
2393 if (s[strlen (s) - 1] == ':')
2394 s[strlen (s) - 1] = '\0';
2395 g_hash_table_insert (parser->priv->ignore_schemes, s, GINT_TO_POINTER (1));
2396
2397 g_mutex_unlock (&parser->priv->ignore_mutex);
2398 }
2399
2400 /**
2401 * totem_pl_parser_add_ignored_mimetype:
2402 * @parser: a #TotemPlParser
2403 * @mimetype: the mimetype to ignore
2404 *
2405 * Adds a mimetype to the list of mimetypes to ignore, so that
2406 * any URI of that mimetype is ignored during playlist parsing.
2407 **/
2408 void
totem_pl_parser_add_ignored_mimetype(TotemPlParser * parser,const char * mimetype)2409 totem_pl_parser_add_ignored_mimetype (TotemPlParser *parser,
2410 const char *mimetype)
2411 {
2412 g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2413
2414 g_mutex_lock (&parser->priv->ignore_mutex);
2415 g_hash_table_insert (parser->priv->ignore_mimetypes, g_strdup (mimetype), GINT_TO_POINTER (1));
2416 g_mutex_unlock (&parser->priv->ignore_mutex);
2417 }
2418
2419 /**
2420 * totem_pl_parser_add_ignored_glob:
2421 * @parser: a #TotemPlParser
2422 * @glob: a glob to ignore
2423 *
2424 * Adds a glob to the list of mimetypes to ignore, so that
2425 * any URI of that glob is ignored during playlist parsing.
2426 *
2427 * Since: 3.26.4
2428 **/
2429 void
totem_pl_parser_add_ignored_glob(TotemPlParser * parser,const char * glob)2430 totem_pl_parser_add_ignored_glob (TotemPlParser *parser,
2431 const char *glob)
2432 {
2433 g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2434
2435 g_mutex_lock (&parser->priv->ignore_mutex);
2436 g_hash_table_insert (parser->priv->ignore_globs, g_strdup (glob), GINT_TO_POINTER (1));
2437 g_mutex_unlock (&parser->priv->ignore_mutex);
2438 }
2439
2440 /**
2441 * totem_pl_parser_parse_duration:
2442 * @duration: the duration string to parse
2443 * @debug: %TRUE if debug statements should be printed
2444 *
2445 * Parses the given duration string and returns it as a <type>gint64</type>
2446 * denoting the duration in seconds.
2447 *
2448 * Return value: the duration in seconds, or -1 on error
2449 **/
2450 gint64
totem_pl_parser_parse_duration(const char * duration,gboolean debug)2451 totem_pl_parser_parse_duration (const char *duration, gboolean debug)
2452 {
2453 int hours, minutes, seconds, fractions;
2454
2455 if (duration == NULL) {
2456 D(g_print ("No duration passed\n"));
2457 return -1;
2458 }
2459
2460 /* Formats used by both ASX and RAM files */
2461 if (sscanf (duration, "%d:%d:%d.%d", &hours, &minutes, &seconds, &fractions) == 4) {
2462 gint64 ret = (gint64) hours * 3600 + (gint64) minutes * 60 + seconds;
2463 if (ret == 0 && fractions > 0) {
2464 D(g_print ("Used 00:00:00.00 format, with fractions rounding\n"));
2465 ret = 1;
2466 } else {
2467 D(g_print ("Used 00:00:00.00 format\n"));
2468 }
2469 return ret;
2470 }
2471 if (sscanf (duration, "%d:%d:%d", &hours, &minutes, &seconds) == 3) {
2472 D(g_print ("Used 00:00:00 format\n"));
2473 return (gint64) hours * 3600 + (gint64) minutes * 60 + seconds;
2474 }
2475 if (sscanf (duration, "%d:%d.%d", &minutes, &seconds, &fractions) == 3) {
2476 gint64 ret = minutes * 60 + seconds;
2477 if (ret == 0 && fractions > 0) {
2478 D(g_print ("Used 00:00.00 format, with fractions rounding\n"));
2479 ret = 1;
2480 } else {
2481 D(g_print ("Used 00:00.00 format\n"));
2482 }
2483 return ret;
2484 }
2485 if (sscanf (duration, "%d:%d", &minutes, &seconds) == 2) {
2486 D(g_print ("Used 00:00 format\n"));
2487 return (gint64) minutes * 60 + seconds;
2488 }
2489 if (sscanf (duration, "%d.%d", &minutes, &seconds) == 2) {
2490 D(g_print ("Used broken float format (00.00)\n"));
2491 return (gint64) minutes * 60 + seconds;
2492 }
2493 /* YouTube format */
2494 if (sscanf (duration, "%dm%ds", &minutes, &seconds) == 2) {
2495 D(g_print ("Used YouTube format\n"));
2496 return (gint64) minutes * 60 + seconds;
2497 }
2498 /* PLS files format */
2499 if (sscanf (duration, "%d", &seconds) == 1) {
2500 D(g_print ("Used PLS format\n"));
2501 return seconds;
2502 }
2503
2504 D(g_message ("Couldn't parse duration '%s'\n", duration));
2505
2506 return -1;
2507 }
2508
2509
2510 /**
2511 * totem_pl_parser_parse_date:
2512 * @date_str: the date string to parse
2513 * @debug: %TRUE if debug statements should be printed
2514 *
2515 * Parses the given date string and returns it as a <type>gint64</type>
2516 * denoting the date in seconds since the UNIX Epoch.
2517 *
2518 * Return value: the date in seconds, or -1 on error
2519 **/
2520 guint64
totem_pl_parser_parse_date(const char * date_str,gboolean debug)2521 totem_pl_parser_parse_date (const char *date_str, gboolean debug)
2522 {
2523 g_autoptr(GDateTime) date = NULL;
2524
2525 g_return_val_if_fail (date_str != NULL, -1);
2526
2527 /* Try to parse as an ISO8601/RFC3339 date */
2528 date = g_date_time_new_from_iso8601 (date_str, NULL);
2529 if (date != NULL) {
2530 D(g_message ("Parsed duration '%s' using the ISO8601 parser", date_str));
2531 return g_date_time_to_unix (date);
2532 }
2533 D(g_message ("Failed to parse duration '%s' using the ISO8601 parser", date_str));
2534 /* Fall back to RFC 2822 date parsing */
2535 date = g_mime_utils_header_decode_date (date_str);
2536 if (!date) {
2537 D(g_message ("Failed to parse duration '%s' using the RFC 2822 parser", date_str));
2538 return -1;
2539 }
2540 return g_date_time_to_unix (date);
2541 }
2542 #endif /* !TOTEM_PL_PARSER_MINI */
2543
2544 static char *
totem_pl_parser_mime_type_from_data(gconstpointer data,int len)2545 totem_pl_parser_mime_type_from_data (gconstpointer data, int len)
2546 {
2547 char *mime_type;
2548 gboolean uncertain;
2549
2550 #ifdef G_OS_WIN32
2551 char *content_type;
2552
2553 content_type = g_content_type_guess (NULL, data, len, &uncertain);
2554 if (uncertain == FALSE) {
2555 mime_type = g_content_type_get_mime_type (content_type);
2556 g_free (content_type);
2557 } else {
2558 mime_type = NULL;
2559 }
2560 #else
2561 mime_type = g_content_type_guess (NULL, data, len, &uncertain);
2562 if (uncertain != FALSE) {
2563 g_free (mime_type);
2564 mime_type = NULL;
2565 }
2566 #endif
2567
2568 if (mime_type != NULL &&
2569 (strcmp (mime_type, "text/plain") == 0 ||
2570 strcmp (mime_type, "application/octet-stream") == 0 ||
2571 strcmp (mime_type, "application/xml") == 0 ||
2572 strcmp (mime_type, "text/html") == 0)) {
2573 PlaylistIdenCallback func;
2574 guint i;
2575
2576 func = NULL;
2577
2578 for (i = 0; i < G_N_ELEMENTS(dual_types); i++) {
2579 const char *res;
2580
2581 if (func == dual_types[i].iden)
2582 continue;
2583 func = dual_types[i].iden;
2584 if (func == NULL)
2585 continue;
2586 res = func (data, len);
2587 if (res != NULL) {
2588 g_free (mime_type);
2589 return g_strdup (res);
2590 }
2591 }
2592
2593 return NULL;
2594 }
2595
2596 return mime_type;
2597 }
2598
2599 /**
2600 * totem_pl_parser_can_parse_from_data:
2601 * @data: the data to check for parsability
2602 * @len: the length of data to check
2603 * @debug: %TRUE if debug statements should be printed
2604 *
2605 * Checks if the first @len bytes of @data can be parsed.
2606 *
2607 * Return value: %TRUE if @data can be parsed
2608 **/
2609 gboolean
totem_pl_parser_can_parse_from_data(const char * data,gsize len,gboolean debug)2610 totem_pl_parser_can_parse_from_data (const char *data,
2611 gsize len,
2612 gboolean debug)
2613 {
2614 char *mimetype;
2615 guint i;
2616
2617 g_return_val_if_fail (data != NULL, FALSE);
2618
2619 /* Bad cast! */
2620 mimetype = totem_pl_parser_mime_type_from_data ((gpointer) data, (int) len);
2621
2622 if (mimetype == NULL) {
2623 D(g_message ("totem_pl_parser_can_parse_from_data couldn't get mimetype"));
2624 return FALSE;
2625 }
2626
2627 for (i = 0; i < G_N_ELEMENTS(special_types); i++) {
2628 if (strcmp (special_types[i].mimetype, mimetype) == 0) {
2629 D(g_message ("Is special type '%s'", mimetype));
2630 g_free (mimetype);
2631 return TRUE;
2632 }
2633 }
2634
2635 for (i = 0; i < G_N_ELEMENTS(dual_types); i++) {
2636 if (strcmp (dual_types[i].mimetype, mimetype) == 0) {
2637 D(g_message ("Should be dual type '%s', making sure now", mimetype));
2638 if (dual_types[i].iden != NULL) {
2639 gboolean retval = ((* dual_types[i].iden) (data, len) != NULL);
2640 D(g_message ("%s dual type '%s'",
2641 retval ? "Is" : "Is not", mimetype));
2642 g_free (mimetype);
2643 return retval;
2644 }
2645 g_free (mimetype);
2646 return FALSE;
2647 }
2648 }
2649
2650 D(g_message ("Is unsupported mime-type '%s'", mimetype));
2651
2652 g_free (mimetype);
2653
2654 return FALSE;
2655 }
2656
2657 /**
2658 * totem_pl_parser_can_parse_from_filename:
2659 * @filename: the file to check for parsability
2660 * @debug: %TRUE if debug statements should be printed
2661 *
2662 * Checks if the file can be parsed. Files can be parsed if:
2663 * <itemizedlist>
2664 * <listitem><para>they have a special mimetype, or</para></listitem>
2665 * <listitem><para>they have a mimetype which could be a video or a playlist.</para></listitem>
2666 * </itemizedlist>
2667 *
2668 * Return value: %TRUE if @filename can be parsed
2669 **/
2670 gboolean
totem_pl_parser_can_parse_from_filename(const char * filename,gboolean debug)2671 totem_pl_parser_can_parse_from_filename (const char *filename, gboolean debug)
2672 {
2673 GMappedFile *map;
2674 GError *err = NULL;
2675 gboolean retval;
2676
2677 g_return_val_if_fail (filename != NULL, FALSE);
2678
2679 map = g_mapped_file_new (filename, FALSE, &err);
2680 if (map == NULL) {
2681 D(g_message ("couldn't mmap %s: %s", filename, err->message));
2682 g_error_free (err);
2683 return FALSE;
2684 }
2685
2686 retval = totem_pl_parser_can_parse_from_data
2687 (g_mapped_file_get_contents (map),
2688 g_mapped_file_get_length (map), debug);
2689
2690 g_mapped_file_unref (map);
2691
2692 return retval;
2693 }
2694
2695 /**
2696 * totem_pl_parser_can_parse_from_uri:
2697 * @uri: the remote URI to check for parsability
2698 * @debug: %TRUE if debug statements should be printed
2699 *
2700 * Checks if the remote URI can be parsed. Note that this does
2701 * not actually try to open the remote URI, or deduce its mime-type
2702 * from filename, as this would bring too many false positives.
2703 *
2704 * Return value: %TRUE if @uri could be parsed
2705 **/
2706 gboolean
totem_pl_parser_can_parse_from_uri(const char * uri,gboolean debug)2707 totem_pl_parser_can_parse_from_uri (const char *uri, gboolean debug)
2708 {
2709 return totem_pl_parser_is_videosite (uri, debug);
2710 }
2711
2712 #ifndef TOTEM_PL_PARSER_MINI
2713 GType
totem_pl_parser_metadata_get_type(void)2714 totem_pl_parser_metadata_get_type (void)
2715 {
2716 static volatile gsize g_define_type_id__volatile = 0;
2717 if (g_once_init_enter (&g_define_type_id__volatile))
2718 {
2719 /* NOTE: This is equivalent to the definition for GHashTable in gboxed.c, in that it uses the same copy/free functions.
2720 * This means that if we box a TotemPlParserMetadata inside a GValue, we can safely unbox it as a GHashTable (and vice-versa).
2721 * This means we can hide TotemPlParserMetadata from introspection, and just pretend it's actually been a GHashTable all along. */
2722 GType g_define_type_id = g_boxed_type_register_static (
2723 g_intern_static_string ("TotemPlParserMetadata"),
2724 (GBoxedCopyFunc) g_hash_table_ref,
2725 (GBoxedFreeFunc) g_hash_table_unref);
2726 g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
2727 }
2728 return g_define_type_id__volatile;
2729 }
2730 #endif /* !TOTEM_PL_PARSER_MINI */
2731
2732