hythmbox-2.98/podcast/rb-podcast-parse.c

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 }