No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2005 Renato Araujo Oliveira Filho - INdT <renato.filho@indt.org.br>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <string.h>
32
33 #include <totem-pl-parser.h>
34 #include <glib/gi18n.h>
35 #include <gio/gio.h>
36 #include <glib.h>
37 #include <glib/gprintf.h>
38
39 #include "rb-debug.h"
40 #include "rb-podcast-parse.h"
41 #include "rb-file-helpers.h"
42
43 GQuark
44 rb_podcast_parse_error_quark (void)
45 {
46 static GQuark quark = 0;
47 if (!quark)
48 quark = g_quark_from_static_string ("rb_podcast_parse_error");
49
50 return quark;
51 }
52
53 static void
54 playlist_metadata_foreach (const char *key,
55 const char *value,
56 gpointer data)
57 {
58 RBPodcastChannel *channel = (RBPodcastChannel *) data;
59
60 if (strcmp (key, TOTEM_PL_PARSER_FIELD_TITLE) == 0) {
61 channel->title = g_strdup (value);
62 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_LANGUAGE) == 0) {
63 channel->lang = g_strdup (value);
64 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_DESCRIPTION) == 0) {
65 channel->description = g_strdup (value);
66 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_AUTHOR) == 0) {
67 channel->author = g_strdup (value);
68 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_CONTACT) == 0) {
69 channel->contact = g_strdup (value);
70 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_IMAGE_URI) == 0) {
71 channel->img = g_strdup (value);
72 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_PUB_DATE) == 0) {
73 channel->pub_date = totem_pl_parser_parse_date (value, FALSE);
74 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_COPYRIGHT) == 0) {
75 channel->copyright = g_strdup (value);
76 }
77 }
78
79 static void
80 playlist_started (TotemPlParser *parser,
81 const char *uri,
82 GHashTable *metadata,
83 gpointer data)
84 {
85 g_hash_table_foreach (metadata, (GHFunc) playlist_metadata_foreach, data);
86 }
87
88 static void
89 playlist_ended (TotemPlParser *parser,
90 const char *uri,
91 gpointer data)
92 {
93 RBPodcastChannel *channel = (RBPodcastChannel *) data;
94
95 channel->posts = g_list_reverse (channel->posts);
96 }
97
98 static void
99 entry_metadata_foreach (const char *key,
100 const char *value,
101 gpointer data)
102 {
103 RBPodcastItem *item = (RBPodcastItem *) data;
104
105 if (strcmp (key, TOTEM_PL_PARSER_FIELD_TITLE) == 0) {
106 item->title = g_strdup (value);
107 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_URI) == 0) {
108 item->url = g_strdup (value);
109 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_DESCRIPTION) == 0) {
110 item->description = g_strdup (value);
111 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_AUTHOR) == 0) {
112 item->author = g_strdup (value);
113 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_PUB_DATE) == 0) {
114 item->pub_date = totem_pl_parser_parse_date (value, FALSE);
115 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_DURATION) == 0) {
116 item->duration = totem_pl_parser_parse_duration (value, FALSE);
117 } else if (strcmp (key, TOTEM_PL_PARSER_FIELD_FILESIZE) == 0) {
118 item->filesize = g_ascii_strtoull (value, NULL, 10);
119 }
120 }
121
122 static void
123 entry_parsed (TotemPlParser *parser,
124 const char *uri,
125 GHashTable *metadata,
126 gpointer data)
127 {
128 RBPodcastChannel *channel = (RBPodcastChannel *) data;
129 RBPodcastItem *item;
130 char *scheme = NULL;
131
132 item = g_new0 (RBPodcastItem, 1);
133 g_hash_table_foreach (metadata, (GHFunc) entry_metadata_foreach, item);
134
135 /* make sure the item URI is at least URI-like */
136 if (item->url != NULL)
137 scheme = g_uri_parse_scheme (item->url);
138
139 if (scheme == NULL) {
140 rb_debug ("ignoring podcast entry from feed %s with no/invalid uri %s",
141 channel->url,
142 item->url ? item->url : "<null>");
143 rb_podcast_parse_item_free (item);
144 return;
145 }
146 g_free (scheme);
147
148 channel->posts = g_list_prepend (channel->posts, item);
149 }
150
151 gboolean
152 rb_podcast_parse_load_feed (RBPodcastChannel *data,
153 const char *file_name,
154 gboolean existing_feed,
155 GError **error)
156 {
157 GFile *file;
158 GFileInfo *fileinfo;
159 TotemPlParser *plparser;
160
161 data->url = g_strdup (file_name);
162
163 /* if the URL has a .rss, .xml or .atom extension (before the query string),
164 * don't bother checking the MIME type.
165 */
166 if (rb_uri_could_be_podcast (file_name, &data->is_opml) || existing_feed) {
167 rb_debug ("not checking mime type for %s (should be %s file)", file_name,
168 data->is_opml ? "OPML" : "Podcast");
169 } else {
170 GError *ferror = NULL;
171 char *content_type;
172
173 rb_debug ("checking mime type for %s", file_name);
174
175 file = g_file_new_for_uri (file_name);
176 fileinfo = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0, NULL, &ferror);
177 if (ferror != NULL) {
178 g_set_error (error,
179 RB_PODCAST_PARSE_ERROR,
180 RB_PODCAST_PARSE_ERROR_FILE_INFO,
181 _("Unable to check file type: %s"),
182 ferror->message);
183 g_object_unref (file);
184 g_clear_error (&ferror);
185 return FALSE;
186 }
187
188 content_type = g_file_info_get_attribute_as_string (fileinfo, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
189 g_object_unref (file);
190 g_object_unref (fileinfo);
191
192 if (content_type != NULL
193 && strstr (content_type, "html") == NULL
194 && strstr (content_type, "xml") == NULL
195 && strstr (content_type, "rss") == NULL
196 && strstr (content_type, "opml") == NULL) {
197 g_set_error (error,
198 RB_PODCAST_PARSE_ERROR,
199 RB_PODCAST_PARSE_ERROR_MIME_TYPE,
200 _("Unexpected file type: %s"),
201 content_type);
202 g_free (content_type);
203 return FALSE;
204 } else if (content_type != NULL
205 && strstr (content_type, "opml") != NULL) {
206 data->is_opml = TRUE;
207 }
208
209 g_free (content_type);
210 }
211
212 plparser = totem_pl_parser_new ();
213 g_object_set (plparser, "recurse", FALSE, "force", TRUE, NULL);
214 g_signal_connect (G_OBJECT (plparser), "entry-parsed", G_CALLBACK (entry_parsed), data);
215 g_signal_connect (G_OBJECT (plparser), "playlist-started", G_CALLBACK (playlist_started), data);
216 g_signal_connect (G_OBJECT (plparser), "playlist-ended", G_CALLBACK (playlist_ended), data);
217
218 if (totem_pl_parser_parse (plparser, file_name, FALSE) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
219 rb_debug ("Parsing %s as a Podcast failed", file_name);
220 g_set_error (error,
221 RB_PODCAST_PARSE_ERROR,
222 RB_PODCAST_PARSE_ERROR_XML_PARSE,
223 _("Unable to parse the feed contents"));
224 g_object_unref (plparser);
225 return FALSE;
226 }
227 g_object_unref (plparser);
228
229 /* treat empty feeds, or feeds that don't contain any downloadable items, as
230 * an error.
231 */
232 if (data->posts == NULL) {
233 rb_debug ("Parsing %s as a podcast succeeded, but the feed contains no downloadable items", file_name);
234 g_set_error (error,
235 RB_PODCAST_PARSE_ERROR,
236 RB_PODCAST_PARSE_ERROR_NO_ITEMS,
237 _("The feed does not contain any downloadable items"));
238 return FALSE;
239 }
240
241 rb_debug ("Parsing %s as a Podcast succeeded", file_name);
242 return TRUE;
243 }
244
245 RBPodcastChannel *
246 rb_podcast_parse_channel_copy (RBPodcastChannel *data)
247 {
248 RBPodcastChannel *copy;
249 copy = g_new0 (RBPodcastChannel, 1);
250 copy->url = g_strdup (data->url);
251 copy->title = g_strdup (data->title);
252 copy->lang = g_strdup (data->lang);
253 copy->description = g_strdup (data->description);
254 copy->author = g_strdup (data->author);
255 copy->contact = g_strdup (data->contact);
256 copy->img = g_strdup (data->img);
257 copy->pub_date = data->pub_date;
258 copy->copyright = g_strdup (data->copyright);
259 copy->is_opml = data->is_opml;
260
261 if (data->posts != NULL) {
262 GList *l;
263 for (l = data->posts; l != NULL; l = l->next) {
264 RBPodcastItem *copyitem;
265 copyitem = rb_podcast_parse_item_copy (l->data);
266 data->posts = g_list_prepend (data->posts, copyitem);
267 }
268 data->posts = g_list_reverse (data->posts);
269 } else {
270 copy->num_posts = data->num_posts;
271 }
272
273 return copy;
274 }
275
276 void
277 rb_podcast_parse_channel_free (RBPodcastChannel *data)
278 {
279 g_return_if_fail (data != NULL);
280
281 g_list_foreach (data->posts, (GFunc) rb_podcast_parse_item_free, NULL);
282 g_list_free (data->posts);
283 data->posts = NULL;
284
285 g_free (data->url);
286 g_free (data->title);
287 g_free (data->lang);
288 g_free (data->description);
289 g_free (data->author);
290 g_free (data->contact);
291 g_free (data->img);
292 g_free (data->copyright);
293
294 g_free (data);
295 data = NULL;
296 }
297
298 RBPodcastItem *
299 rb_podcast_parse_item_copy (RBPodcastItem *item)
300 {
301 RBPodcastItem *copy;
302 copy = g_new0 (RBPodcastItem, 1);
303 copy->title = g_strdup (item->title);
304 copy->url = g_strdup (item->url);
305 copy->description = g_strdup (item->description);
306 copy->author = g_strdup (item->author);
307 copy->pub_date = item->pub_date;
308 copy->duration = item->duration;
309 copy->filesize = item->filesize;
310 return copy;
311 }
312
313 void
314 rb_podcast_parse_item_free (RBPodcastItem *item)
315 {
316 g_return_if_fail (item != NULL);
317
318 g_free (item->title);
319 g_free (item->url);
320 g_free (item->description);
321 g_free (item->author);
322
323 g_free (item);
324 }