1 /*
2  * Copyright (C) 2009-2015, Roberto Guido <rguido@src.gnome.org>
3  *                          Michele Tameni <michele@amdplanet.it>
4  * Copyright (C) 2015 Igor Gnatenko <ignatenko@src.gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA  02110-1301, USA.
20  */
21 
22 #include "utils.h"
23 #include "feed-channel.h"
24 #include "feed-parser.h"
25 
26 #define FEED_CHANNEL_GET_PRIVATE(obj)	(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GRSS_FEED_CHANNEL_TYPE, GrssFeedChannelPrivate))
27 #define FEED_CHANNEL_ERROR		feed_channel_error_quark()
28 
29 /**
30  * SECTION: feed-channel
31  * @short_description: a feed
32  *
33  * #GrssFeedChannel rappresents a single feed which may be fetched and parsed.
34  */
35 
36 typedef struct {
37 	gchar	*hub;
38 } PubSub;
39 
40 typedef struct {
41 	gchar	*path;
42 	gchar	*protocol;
43 } RSSCloud;
44 
45 struct _GrssFeedChannelPrivate {
46 	gchar		*format;
47 	gchar		*source;
48 
49 	gchar		*title;
50 	gchar		*homepage;
51 	gchar		*description;
52 	gchar		*image;
53 	gchar		*icon;
54 	gchar		*language;
55 	gchar		*category;
56 	PubSub		pubsub;
57 	RSSCloud	rsscloud;
58 
59 	gchar		*copyright;
60 	GrssPerson	*editor;
61 	GList		*contributors;
62 	SoupCookieJar   *jar;
63 	gchar		*webmaster;
64 	gchar		*generator;
65 	gboolean	gzip;
66 
67 	time_t		pub_time;
68 	time_t		update_time;
69 	int		update_interval;
70 
71 	GCancellable	*fetchcancel;
72 };
73 
74 enum {
75 	FEED_CHANNEL_FETCH_ERROR,
76 	FEED_CHANNEL_PARSE_ERROR,
77 	FEED_CHANNEL_FILE_ERROR,
78 };
79 
80 G_DEFINE_TYPE (GrssFeedChannel, grss_feed_channel, G_TYPE_OBJECT);
81 
82 static GQuark
feed_channel_error_quark()83 feed_channel_error_quark ()
84 {
85 	return g_quark_from_static_string ("feed_channel_error");
86 }
87 
88 static void
grss_feed_channel_finalize(GObject * obj)89 grss_feed_channel_finalize (GObject *obj)
90 {
91 	GList *iter;
92 	GrssFeedChannel *chan;
93 
94 	chan = GRSS_FEED_CHANNEL (obj);
95 	FREE_STRING (chan->priv->title);
96 	FREE_STRING (chan->priv->homepage);
97 	FREE_STRING (chan->priv->description);
98 	FREE_STRING (chan->priv->image);
99 	FREE_STRING (chan->priv->icon);
100 	FREE_STRING (chan->priv->language);
101 	FREE_STRING (chan->priv->category);
102 	FREE_STRING (chan->priv->pubsub.hub);
103 	FREE_STRING (chan->priv->rsscloud.path);
104 	FREE_STRING (chan->priv->rsscloud.protocol);
105 	FREE_STRING (chan->priv->copyright);
106 	if (chan->priv->editor)
107 		grss_person_unref (chan->priv->editor);
108 	FREE_STRING (chan->priv->webmaster);
109 	FREE_STRING (chan->priv->generator);
110 
111 	if (chan->priv->contributors != NULL) {
112 		for (iter = chan->priv->contributors; iter; iter = g_list_next (iter))
113 			grss_person_unref (iter->data);
114 		g_list_free (chan->priv->contributors);
115 	}
116 
117 	if (chan->priv->jar != NULL)
118 		g_free (chan->priv->jar);
119 }
120 
121 static void
grss_feed_channel_class_init(GrssFeedChannelClass * klass)122 grss_feed_channel_class_init (GrssFeedChannelClass *klass)
123 {
124 	GObjectClass *gobject_class;
125 
126 	g_type_class_add_private (klass, sizeof (GrssFeedChannelPrivate));
127 
128 	gobject_class = G_OBJECT_CLASS (klass);
129 	gobject_class->finalize = grss_feed_channel_finalize;
130 }
131 
132 static void
grss_feed_channel_init(GrssFeedChannel * node)133 grss_feed_channel_init (GrssFeedChannel *node)
134 {
135 	node->priv = FEED_CHANNEL_GET_PRIVATE (node);
136 	memset (node->priv, 0, sizeof (GrssFeedChannelPrivate));
137 }
138 
139 /**
140  * grss_feed_channel_new:
141  *
142  * Allocates a new #GrssFeedChannel.
143  *
144  * Returns: a #GrssFeedChannel.
145  */
146 GrssFeedChannel*
grss_feed_channel_new()147 grss_feed_channel_new ()
148 {
149 	return g_object_new (GRSS_FEED_CHANNEL_TYPE, NULL);
150 }
151 
152 /**
153  * grss_feed_channel_new_with_source:
154  * @source: URL of the feed.
155  *
156  * Allocates a new #GrssFeedChannel and assign it the given remote source.
157  *
158  * Returns: a #GrssFeedChannel.
159  */
160 GrssFeedChannel*
grss_feed_channel_new_with_source(gchar * source)161 grss_feed_channel_new_with_source (gchar *source)
162 {
163 	GrssFeedChannel *ret;
164 
165 	ret = grss_feed_channel_new ();
166 	grss_feed_channel_set_source (ret, source);
167 	return ret;
168 }
169 
170 /**
171  * grss_feed_channel_new_from_xml:
172  * @doc: an XML document previously parsed with libxml2.
173  * @error: if an error occurred, %NULL is returned and this is filled with the
174  *         message.
175  *
176  * Allocates a new #GrssFeedChannel and init it with contents found in specified
177  * XML document.
178  *
179  * Returns: a #GrssFeedChannel, or %NULL if an error occurs.
180  */
181 GrssFeedChannel*
grss_feed_channel_new_from_xml(xmlDocPtr doc,GError ** error)182 grss_feed_channel_new_from_xml (xmlDocPtr doc, GError **error)
183 {
184 	GrssFeedParser *parser;
185 	GrssFeedChannel *ret;
186 	GError *myerror;
187 
188 	ret = g_object_new (GRSS_FEED_CHANNEL_TYPE, NULL);
189 	parser = grss_feed_parser_new ();
190 
191 	myerror = NULL;
192 	grss_feed_parser_parse_channel (parser, ret, doc, &myerror);
193 	if (myerror != NULL) {
194 		g_propagate_error (error, myerror);
195 		g_object_unref (ret);
196 		ret = NULL;
197 	}
198 
199 	g_object_unref (parser);
200 	return ret;
201 }
202 
203 /**
204  * grss_feed_channel_new_from_memory:
205  * @data: string to parse.
206  * @error: if an error occurred, %NULL is returned and this is filled with the
207  *         message.
208  *
209  * Allocates a new #GrssFeedChannel and init it with contents found in specified
210  * memory block.
211  *
212  * Returns: a #GrssFeedChannel, or %NULL if an error occurs.
213  */
214 GrssFeedChannel*
grss_feed_channel_new_from_memory(const gchar * data,GError ** error)215 grss_feed_channel_new_from_memory (const gchar *data, GError **error)
216 {
217 	xmlDocPtr doc;
218 
219 	doc = content_to_xml (data, strlen (data));
220 	if (doc == NULL) {
221 		g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_PARSE_ERROR, "Unable to parse data");
222 		return NULL;
223 	}
224 
225 	return grss_feed_channel_new_from_xml (doc, error);
226 }
227 
228 /**
229  * grss_feed_channel_new_from_file:
230  * @path: path of the file to parse.
231  * @error: if an error occurred, %NULL is returned and this is filled with the
232  *         message.
233  *
234  * Allocates a new #GrssFeedChannel and init it with contents found in specified
235  * file.
236  *
237  * Returns: a #GrssFeedChannel, or %NULL if the file in @path is not a
238  * valid document.
239  */
240 GrssFeedChannel*
grss_feed_channel_new_from_file(const gchar * path,GError ** error)241 grss_feed_channel_new_from_file (const gchar *path, GError **error)
242 {
243 	struct stat sbuf;
244 	xmlDocPtr doc;
245 	GrssFeedChannel *ret;
246 
247 	ret = NULL;
248 
249 	if (stat (path, &sbuf) == -1) {
250 		g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_FILE_ERROR, "Unable to open file: %s", strerror (errno));
251 		return NULL;
252 	}
253 
254 	doc = file_to_xml (path);
255 	if (doc == NULL) {
256 		g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_PARSE_ERROR, "Unable to parse file");
257 		return NULL;
258 	}
259 
260 	ret = grss_feed_channel_new_from_xml (doc, error);
261 
262 	xmlFreeDoc (doc);
263 	return ret;
264 }
265 
266 /**
267  * grss_feed_channel_set_format:
268  * @channel: a #GrssFeedChannel.
269  * @format: format of the file, such as "application/atom+xml" or
270  * "application/rss+xml".
271  *
272  * To assign a file format to the feed.
273  */
274 void
grss_feed_channel_set_format(GrssFeedChannel * channel,gchar * format)275 grss_feed_channel_set_format (GrssFeedChannel *channel, gchar *format)
276 {
277 	FREE_STRING (channel->priv->format);
278 	channel->priv->format = g_strdup (format);
279 }
280 
281 /**
282  * grss_feed_channel_get_format:
283  * @channel: a #GrssFeedChannel.
284  *
285  * Retrieves the file format of @channel.
286  *
287  * Returns: file format of channel.
288  */
289 const gchar*
grss_feed_channel_get_format(GrssFeedChannel * channel)290 grss_feed_channel_get_format (GrssFeedChannel *channel)
291 {
292 	return (const gchar*) channel->priv->format;
293 }
294 
295 /**
296  * grss_feed_channel_set_source:
297  * @channel: a #GrssFeedChannel.
298  * @source: URL of the feed.
299  *
300  * To assign the URL where to fetch the feed.
301  *
302  * Returns: %TRUE if @source is a valid URL, %FALSE otherwise
303  */
304 gboolean
grss_feed_channel_set_source(GrssFeedChannel * channel,gchar * source)305 grss_feed_channel_set_source (GrssFeedChannel *channel, gchar *source)
306 {
307 	FREE_STRING (channel->priv->source);
308 
309 	if (test_url ((const gchar*) source) == TRUE) {
310 		channel->priv->source = SET_STRING (source);
311 		return TRUE;
312 	}
313 	else {
314 		return FALSE;
315 	}
316 }
317 
318 /**
319  * grss_feed_channel_get_source:
320  * @channel: a #GrssFeedChannel.
321  *
322  * Retrieves URL where to fetch the @channel.
323  *
324  * Returns: URL of the channel.
325  */
326 const gchar*
grss_feed_channel_get_source(GrssFeedChannel * channel)327 grss_feed_channel_get_source (GrssFeedChannel *channel)
328 {
329 	return (const gchar*) channel->priv->source;
330 }
331 
332 /**
333  * grss_feed_channel_set_title:
334  * @channel: a #GrssFeedChannel.
335  * @title: title of the feed.
336  *
337  * To set a title to the @channel.
338  */
339 void
grss_feed_channel_set_title(GrssFeedChannel * channel,gchar * title)340 grss_feed_channel_set_title (GrssFeedChannel *channel, gchar *title)
341 {
342 	FREE_STRING (channel->priv->title);
343 	channel->priv->title = g_strdup (title);
344 }
345 
346 /**
347  * grss_feed_channel_get_title:
348  * @channel: a #GrssFeedChannel.
349  *
350  * Retrieves title of the @channel.
351  *
352  * Returns: title of the feed, or %NULL.
353  */
354 const gchar*
grss_feed_channel_get_title(GrssFeedChannel * channel)355 grss_feed_channel_get_title (GrssFeedChannel *channel)
356 {
357 	return (const gchar*) channel->priv->title;
358 }
359 
360 /**
361  * grss_feed_channel_set_homepage:
362  * @channel: a #GrssFeedChannel.
363  * @homepage: homepage for the main website.
364  *
365  * To set the homepage of the site the @channel belongs.
366  *
367  * Returns: %TRUE if @homepage is a valid URL, %FALSE otherwise
368  */
369 gboolean
grss_feed_channel_set_homepage(GrssFeedChannel * channel,gchar * homepage)370 grss_feed_channel_set_homepage (GrssFeedChannel *channel, gchar *homepage)
371 {
372 	FREE_STRING (channel->priv->homepage);
373 
374 	if (test_url ((const gchar*) homepage) == TRUE) {
375 		channel->priv->homepage = SET_STRING (homepage);
376 		return TRUE;
377 	}
378 	else {
379 		return FALSE;
380 	}
381 }
382 
383 /**
384  * grss_feed_channel_get_homepage:
385  * @channel: a #GrssFeedChannel.
386  *
387  * Retrieves the homepage of the site for which @channel is the feed.
388  *
389  * Returns: reference homepage of the feed, or %NULL.
390  */
391 const gchar*
grss_feed_channel_get_homepage(GrssFeedChannel * channel)392 grss_feed_channel_get_homepage (GrssFeedChannel *channel)
393 {
394 	return (const gchar*) channel->priv->homepage;
395 }
396 
397 /**
398  * grss_feed_channel_set_description:
399  * @channel: a #GrssFeedChannel.
400  * @description: description of the feed.
401  *
402  * To set the description of @channel.
403  */
404 void
grss_feed_channel_set_description(GrssFeedChannel * channel,gchar * description)405 grss_feed_channel_set_description (GrssFeedChannel *channel, gchar *description)
406 {
407 	FREE_STRING (channel->priv->description);
408 	channel->priv->description = g_strdup (description);
409 }
410 
411 /**
412  * grss_feed_channel_get_description:
413  * @channel: a #GrssFeedChannel.
414  *
415  * Retrieves the description of @channel.
416  *
417  * Returns: description of the feed, or %NULL.
418  */
419 const gchar*
grss_feed_channel_get_description(GrssFeedChannel * channel)420 grss_feed_channel_get_description (GrssFeedChannel *channel)
421 {
422 	return (const gchar*) channel->priv->description;
423 }
424 
425 /**
426  * grss_feed_channel_set_image:
427  * @channel: a #GrssFeedChannel.
428  * @image: URL of the image.
429  *
430  * To set a rappresentative image to @channel.
431  *
432  * Returns: %TRUE if @image is a valid URL, %FALSE otherwise
433  */
434 gboolean
grss_feed_channel_set_image(GrssFeedChannel * channel,gchar * image)435 grss_feed_channel_set_image (GrssFeedChannel *channel, gchar *image)
436 {
437 	FREE_STRING (channel->priv->image);
438 
439 	if (test_url ((const gchar*) image) == TRUE) {
440 		channel->priv->image = SET_STRING (image);
441 		return TRUE;
442 	}
443 	else {
444 		return FALSE;
445 	}
446 }
447 
448 /**
449  * grss_feed_channel_get_image:
450  * @channel: a #GrssFeedChannel.
451  *
452  * Retrieves the URL of the image assigned to the channel.
453  *
454  * Returns: URL of the image, or %NULL.
455  */
456 const gchar*
grss_feed_channel_get_image(GrssFeedChannel * channel)457 grss_feed_channel_get_image (GrssFeedChannel *channel)
458 {
459 	return (const gchar*) channel->priv->image;
460 }
461 
462 /**
463  * grss_feed_channel_set_icon:
464  * @channel: a #GrssFeedChannel.
465  * @icon: URL where to retrieve the favicon.
466  *
467  * To set the URL of the icon rappresenting @channel.
468  *
469  * Returns: %TRUE if @icon is a valid URL, %FALSE otherwise
470  */
471 gboolean
grss_feed_channel_set_icon(GrssFeedChannel * channel,gchar * icon)472 grss_feed_channel_set_icon (GrssFeedChannel *channel, gchar *icon)
473 {
474 	FREE_STRING (channel->priv->icon);
475 
476 	if (test_url ((const gchar*) icon) == TRUE) {
477 		channel->priv->icon = SET_STRING (icon);
478 		return TRUE;
479 	}
480 	else {
481 		return FALSE;
482 	}
483 }
484 
485 /**
486  * grss_feed_channel_get_icon:
487  * @channel: a #GrssFeedChannel.
488  *
489  * Retrieves URL of the favicon of the channel (and/or the website for which
490  * this is the feed).
491  *
492  * Returns: URL of the favicon, or %NULL.
493  */
494 const gchar*
grss_feed_channel_get_icon(GrssFeedChannel * channel)495 grss_feed_channel_get_icon (GrssFeedChannel *channel)
496 {
497 	return (const gchar*) channel->priv->icon;
498 }
499 
500 /**
501  * grss_feed_channel_set_language:
502  * @channel: a #GrssFeedChannel.
503  * @language: string holding the language of the feed.
504  *
505  * To set the language of @channel.
506  */
507 void
grss_feed_channel_set_language(GrssFeedChannel * channel,gchar * language)508 grss_feed_channel_set_language (GrssFeedChannel *channel, gchar *language)
509 {
510 	FREE_STRING (channel->priv->language);
511 	channel->priv->language = g_strdup (language);
512 }
513 
514 /**
515  * grss_feed_channel_get_language:
516  * @channel: a #GrssFeedChannel.
517  *
518  * Retrieves the language of the @channel.
519  *
520  * Returns: string rappresenting the language of channel, or %NULL.
521  */
522 const gchar*
grss_feed_channel_get_language(GrssFeedChannel * channel)523 grss_feed_channel_get_language (GrssFeedChannel *channel)
524 {
525 	return (const gchar*) channel->priv->language;
526 }
527 
528 /**
529  * grss_feed_channel_set_category:
530  * @channel: a #GrssFeedChannel.
531  * @category: category of the feed.
532  *
533  * To set the category of the @channel.
534  */
535 void
grss_feed_channel_set_category(GrssFeedChannel * channel,gchar * category)536 grss_feed_channel_set_category (GrssFeedChannel *channel, gchar *category)
537 {
538 	FREE_STRING (channel->priv->category);
539 	channel->priv->category = g_strdup (category);
540 }
541 
542 /**
543  * grss_feed_channel_get_category:
544  * @channel: a #GrssFeedChannel.
545  *
546  * Retrieves category of the @channel.
547  *
548  * Returns: category of the feed, or %NULL.
549  */
550 const gchar*
grss_feed_channel_get_category(GrssFeedChannel * channel)551 grss_feed_channel_get_category (GrssFeedChannel *channel)
552 {
553 	return (const gchar*) channel->priv->category;
554 }
555 
556 /**
557  * grss_feed_channel_set_pubsubhub:
558  * @channel: a #GrssFeedChannel.
559  * @hub: hub for the feed, or %NULL.
560  *
561  * To set information about PubSubHubbub for the channel. To unset the hub,
562  * pass %NULL as parameter.
563  *
564  * Returns: %TRUE if @hub is a valid URL, %FALSE otherwise
565  */
566 gboolean
grss_feed_channel_set_pubsubhub(GrssFeedChannel * channel,gchar * hub)567 grss_feed_channel_set_pubsubhub (GrssFeedChannel *channel, gchar *hub)
568 {
569 	FREE_STRING (channel->priv->pubsub.hub);
570 
571 	if (test_url ((const gchar*) hub) == TRUE) {
572 		channel->priv->pubsub.hub = SET_STRING (hub);
573 		return TRUE;
574 	}
575 	else {
576 		return FALSE;
577 	}
578 }
579 
580 /**
581  * grss_feed_channel_get_pubsubhub:
582  * @channel: a #GrssFeedChannel.
583  * @hub: location for the hub string, or %NULL.
584  *
585  * Retrieves information about the PubSubHubbub hub of the channel.
586  *
587  * Returns: %TRUE if a valid PubSubHubbub hub has been set for the
588  * @channel, %FALSE otherwise.
589  */
590 gboolean
grss_feed_channel_get_pubsubhub(GrssFeedChannel * channel,gchar ** hub)591 grss_feed_channel_get_pubsubhub (GrssFeedChannel *channel, gchar **hub)
592 {
593 	if (hub != NULL)
594 		*hub = channel->priv->pubsub.hub;
595 
596 	return (channel->priv->pubsub.hub != NULL);
597 }
598 
599 /**
600  * grss_feed_channel_set_rsscloud:
601  * @channel: a #GrssFeedChannel.
602  * @path: complete references of the URL where to register subscription, e.g.
603  *        http://example.com/rsscloudNotify .
604  * @protocol: type of protocol used for notifications.
605  *
606  * To set information about RSSCloud notifications for the channel.
607  */
608 void
grss_feed_channel_set_rsscloud(GrssFeedChannel * channel,gchar * path,gchar * protocol)609 grss_feed_channel_set_rsscloud (GrssFeedChannel *channel, gchar *path, gchar *protocol)
610 {
611 	FREE_STRING (channel->priv->rsscloud.path);
612 	FREE_STRING (channel->priv->rsscloud.protocol);
613 
614 	if (path != NULL && protocol != NULL) {
615 		channel->priv->rsscloud.path = g_strdup (path);
616 		channel->priv->rsscloud.protocol = g_strdup (protocol);
617 	}
618 }
619 
620 /**
621  * grss_feed_channel_get_rsscloud:
622  * @channel: a #GrssFeedChannel.
623  * @path: location for the path string, or %NULL.
624  * @protocol: location for the protocol string, or %NULL.
625  *
626  * Retrieves information about the RSSCloud coordinates of the channel.
627  *
628  * Returns: %TRUE if a valid RSSCloud path has been set for the
629  * @channel, %FALSE otherwise.
630  */
631 gboolean
grss_feed_channel_get_rsscloud(GrssFeedChannel * channel,gchar ** path,gchar ** protocol)632 grss_feed_channel_get_rsscloud (GrssFeedChannel *channel, gchar **path, gchar **protocol)
633 {
634 	if (path != NULL)
635 		*path = channel->priv->rsscloud.path;
636 	if (protocol != NULL)
637 		*protocol = channel->priv->rsscloud.protocol;
638 
639 	return (channel->priv->rsscloud.path != NULL && channel->priv->rsscloud.protocol != NULL);
640 }
641 
642 /**
643  * grss_feed_channel_set_copyright:
644  * @channel: a #GrssFeedChannel.
645  * @copyright: copyright of the channel.
646  *
647  * To set the copyright of the feed.
648  */
649 void
grss_feed_channel_set_copyright(GrssFeedChannel * channel,gchar * copyright)650 grss_feed_channel_set_copyright (GrssFeedChannel *channel, gchar *copyright)
651 {
652 	FREE_STRING (channel->priv->copyright);
653 	channel->priv->copyright = g_strdup (copyright);
654 }
655 
656 /**
657  * grss_feed_channel_get_copyright:
658  * @channel: a #GrssFeedChannel.
659  *
660  * Retrieves indications about the copyright.
661  *
662  * Returns: copyright of the @channel, or %NULL.
663  */
664 const gchar*
grss_feed_channel_get_copyright(GrssFeedChannel * channel)665 grss_feed_channel_get_copyright (GrssFeedChannel *channel)
666 {
667 	return (const gchar*) channel->priv->copyright;
668 }
669 
670 /**
671  * grss_feed_channel_set_editor:
672  * @channel: a #GrssFeedChannel.
673  * @editor: a #GrssPerson.
674  *
675  * To set the editor of the @channel.
676  */
677 void
grss_feed_channel_set_editor(GrssFeedChannel * channel,GrssPerson * editor)678 grss_feed_channel_set_editor (GrssFeedChannel *channel,
679                               GrssPerson      *editor)
680 {
681 	if (editor)
682 		grss_person_ref (editor);
683 	if (channel->priv->editor != NULL)
684 		grss_person_unref (channel->priv->editor);
685 	channel->priv->editor = editor;
686 }
687 
688 /**
689  * grss_feed_channel_get_editor:
690  * @channel: a #GrssFeedChannel.
691  *
692  * Retrieves reference to the editor or the @channel.
693  *
694  * Returns: #GrssPerson, or %NULL.
695  */
696 GrssPerson*
grss_feed_channel_get_editor(GrssFeedChannel * channel)697 grss_feed_channel_get_editor (GrssFeedChannel *channel)
698 {
699 	return channel->priv->editor;
700 }
701 
702 /**
703  * grss_feed_channel_add_contributor:
704  * @channel: a #GrssFeedChannel.
705  * @contributor: a #GrssPerson.
706  *
707  * To add a contributor to the @channel.
708  */
709 void
grss_feed_channel_add_contributor(GrssFeedChannel * channel,GrssPerson * contributor)710 grss_feed_channel_add_contributor (GrssFeedChannel *channel,
711                                    GrssPerson      *contributor)
712 {
713 	if (channel->priv->contributors == NULL)
714 		channel->priv->contributors = g_list_prepend (channel->priv->contributors,
715 		                                              grss_person_ref (contributor));
716 	else
717 		channel->priv->contributors = g_list_append (channel->priv->contributors,
718 		                                             grss_person_ref (contributor));
719 }
720 
721 /**
722  * grss_feed_channel_get_contributors:
723  * @channel: a #GrssFeedChannel.
724  *
725  * Retrieves reference to the contributors of the @channel.
726  *
727  * Returns: (element-type GrssPerson) (transfer none): list of contributors to
728  * the channel, or %NULL.
729  */
730 const GList*
grss_feed_channel_get_contributors(GrssFeedChannel * channel)731 grss_feed_channel_get_contributors (GrssFeedChannel *channel)
732 {
733 	return (const GList*) channel->priv->contributors;
734 }
735 
736 /**
737  * grss_feed_channel_add_cookie:
738  * @channel: a #GrssFeedChannel.
739  * @cookie: HTML cookie to add to the #GrssFeedChannel's cookie jar
740  *
741  * To add a cookie related to the @channel, will be involved in HTTP sessions
742  * while fetching it. More cookie can be added to every #GrssFeedChannel
743  */
744 void
grss_feed_channel_add_cookie(GrssFeedChannel * channel,SoupCookie * cookie)745 grss_feed_channel_add_cookie (GrssFeedChannel *channel, SoupCookie *cookie)
746 {
747 	if (cookie != NULL) {
748 		if (channel->priv->jar == NULL)
749 			channel->priv->jar = soup_cookie_jar_new ();
750 		soup_cookie_jar_add_cookie (channel->priv->jar, cookie);
751 	}
752 }
753 
754 /**
755  * grss_feed_channel_get_cookies:
756  * @channel: a #GrssFeedChannel.
757  *
758  * Retrieves reference to the HTML cookies of the @channel.
759  * The list and the individual cookies should all be freed after use.
760  * You can use soup_cookies_free.
761  *
762  * Returns: (element-type SoupCookie) (transfer full): list of cookies to
763  * the channel, or %NULL.
764  */
765 GSList*
grss_feed_channel_get_cookies(GrssFeedChannel * channel)766 grss_feed_channel_get_cookies (GrssFeedChannel *channel)
767 {
768 	if (channel->priv->jar != NULL)
769 		return soup_cookie_jar_all_cookies(channel->priv->jar);
770 
771 	return NULL;
772 }
773 
774 /**
775  * grss_feed_channel_set_webmaster:
776  * @channel: a #GrssFeedChannel.
777  * @webmaster: webmaster of the feed.
778  *
779  * To assign a webmaster to the @channel.
780  */
781 void
grss_feed_channel_set_webmaster(GrssFeedChannel * channel,gchar * webmaster)782 grss_feed_channel_set_webmaster (GrssFeedChannel *channel, gchar *webmaster)
783 {
784 	FREE_STRING (channel->priv->webmaster);
785 	channel->priv->webmaster = g_strdup (webmaster);
786 }
787 
788 /**
789  * grss_feed_channel_get_webmaster:
790  * @channel: a #GrssFeedChannel.
791  *
792  * Retrieves reference to the webmaster of the feed.
793  *
794  * Returns: webmaster of @channel, or %NULL.
795  */
796 const gchar*
grss_feed_channel_get_webmaster(GrssFeedChannel * channel)797 grss_feed_channel_get_webmaster (GrssFeedChannel *channel)
798 {
799 	return (const gchar*) channel->priv->webmaster;
800 }
801 
802 /**
803  * grss_feed_channel_set_generator:
804  * @channel: a #GrssFeedChannel.
805  * @generator: software generator of the feed.
806  *
807  * To set information about the software generator of @channel.
808  */
809 void
grss_feed_channel_set_generator(GrssFeedChannel * channel,gchar * generator)810 grss_feed_channel_set_generator (GrssFeedChannel *channel, gchar *generator)
811 {
812 	FREE_STRING (channel->priv->generator);
813 	channel->priv->generator = g_strdup (generator);
814 }
815 
816 /**
817  * grss_feed_channel_get_generator:
818  * @channel: a #GrssFeedChannel.
819  *
820  * Retrieves information about the feed's software generator.
821  *
822  * Returns: generator of @channel, or %NULL.
823  */
824 const gchar*
grss_feed_channel_get_generator(GrssFeedChannel * channel)825 grss_feed_channel_get_generator (GrssFeedChannel *channel)
826 {
827 	return (const gchar*) channel->priv->generator;
828 }
829 
830 /**
831  * grss_feed_channel_set_gzip_compression:
832  * @channel: a #GrssFeedChannel.
833  * @value: %TRUE to enable GZIP compression when fetching the channel
834  *
835  * Set the GZIP compression for the channel to on or off.
836  */
837 void
grss_feed_channel_set_gzip_compression(GrssFeedChannel * channel,gboolean value)838 grss_feed_channel_set_gzip_compression(GrssFeedChannel *channel, gboolean value)
839 {
840 	channel->priv->gzip = value;
841 }
842 
843 /**
844  * grss_feed_channel_get_gzip_compression:
845  * @channel: a #GrssFeedChannel.
846  *
847  * GZIP compression of the channel is either on or off.
848  *
849  * Returns: %TRUE if @channel has GZIP compression on.
850  */
851 gboolean
grss_feed_channel_get_gzip_compression(GrssFeedChannel * channel)852 grss_feed_channel_get_gzip_compression (GrssFeedChannel *channel)
853 {
854 	return channel->priv->gzip;
855 }
856 
857 /**
858  * grss_feed_channel_set_publish_time:
859  * @channel: a #GrssFeedChannel.
860  * @publish: timestamp of publishing.
861  *
862  * To set the time of publishing for the feed.
863  */
864 void
grss_feed_channel_set_publish_time(GrssFeedChannel * channel,time_t publish)865 grss_feed_channel_set_publish_time (GrssFeedChannel *channel, time_t publish)
866 {
867 	channel->priv->pub_time = publish;
868 }
869 
870 /**
871  * grss_feed_channel_get_publish_time:
872  * @channel: a #GrssFeedChannel.
873  *
874  * Retrieves the publishing time of @channel.
875  *
876  * Returns: time of feed's publish.
877  */
878 time_t
grss_feed_channel_get_publish_time(GrssFeedChannel * channel)879 grss_feed_channel_get_publish_time (GrssFeedChannel *channel)
880 {
881 	return channel->priv->pub_time;
882 }
883 
884 /**
885  * grss_feed_channel_set_update_time:
886  * @channel: a #GrssFeedChannel.
887  * @update: update time of the feed.
888  *
889  * To set the latest update time of @channel.
890  */
891 void
grss_feed_channel_set_update_time(GrssFeedChannel * channel,time_t update)892 grss_feed_channel_set_update_time (GrssFeedChannel *channel, time_t update)
893 {
894 	channel->priv->update_time = update;
895 }
896 
897 /**
898  * grss_feed_channel_get_update_time:
899  * @channel: a #GrssFeedChannel.
900  *
901  * Retrieves the update time of @channel.
902  *
903  * Returns: time of the feed's latest update. If this value was not set
904  * (with grss_feed_channel_set_update_time()) returns
905  * grss_feed_channel_get_publish_time().
906  */
907 time_t
grss_feed_channel_get_update_time(GrssFeedChannel * channel)908 grss_feed_channel_get_update_time (GrssFeedChannel *channel)
909 {
910 	return channel->priv->update_time;
911 }
912 
913 /**
914  * grss_feed_channel_set_update_interval:
915  * @channel: a #GrssFeedChannel.
916  * @minutes: update interval, in minutes.
917  *
918  * To set the update interval for @channel.
919  */
920 void
grss_feed_channel_set_update_interval(GrssFeedChannel * channel,int minutes)921 grss_feed_channel_set_update_interval (GrssFeedChannel *channel, int minutes)
922 {
923 	channel->priv->update_interval = minutes;
924 }
925 
926 /**
927  * grss_feed_channel_get_update_interval:
928  * @channel: a #GrssFeedChannel.
929  *
930  * Retrieves the update interval for the feed. Pay attention to the fact the
931  * value can be unset, and the function returns 0: in this case the caller
932  * must manually set a default update interval with
933  * grss_feed_channel_set_update_interval().
934  *
935  * Returns: update interval for the @channel, in minutes.
936  */
937 int
grss_feed_channel_get_update_interval(GrssFeedChannel * channel)938 grss_feed_channel_get_update_interval (GrssFeedChannel *channel)
939 {
940 	return channel->priv->update_interval;
941 }
942 
943 static gboolean
quick_and_dirty_parse(GrssFeedChannel * channel,SoupMessage * msg,GList ** save_items)944 quick_and_dirty_parse (GrssFeedChannel *channel, SoupMessage *msg, GList **save_items)
945 {
946 	GList *items;
947 	GList *iter;
948 	xmlDocPtr doc;
949 	GrssFeedParser *parser;
950 
951 	doc = content_to_xml (msg->response_body->data, msg->response_body->length);
952 
953 	if (doc != NULL) {
954 		parser = grss_feed_parser_new ();
955 
956 		if (save_items == NULL) {
957 			grss_feed_parser_parse_channel (parser, channel, doc, NULL);
958 		}
959 		else {
960 			items = grss_feed_parser_parse (parser, channel, doc, NULL);
961 			*save_items = items;
962 		}
963 
964 		g_object_unref (parser);
965 		xmlFreeDoc (doc);
966 		return TRUE;
967 	}
968 	else {
969 		return FALSE;
970 	}
971 }
972 
973 static void
init_soup_session(SoupSession * session,GrssFeedChannel * channel)974 init_soup_session (SoupSession *session, GrssFeedChannel *channel)
975 {
976 	if (channel->priv->jar != NULL)
977 		soup_session_add_feature (session, SOUP_SESSION_FEATURE (channel->priv->jar));
978 	if (channel->priv->gzip == TRUE)
979 		soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER);
980 }
981 
982 static void
init_soup_message(SoupMessage * msg,GrssFeedChannel * channel)983 init_soup_message (SoupMessage* msg, GrssFeedChannel *channel)
984 {
985 	if (channel->priv->gzip == TRUE)
986 		soup_message_headers_append (msg->request_headers, "Accept-encoding", "gzip");
987 }
988 
989 /**
990  * grss_feed_channel_fetch:
991  * @channel: a #GrssFeedChannel.
992  * @error: if an error occurred, %FALSE is returned and this is filled with the
993  *         message.
994  *
995  * Utility to fetch and populate a #GrssFeedChannel for the first time, and init
996  * all his internal values. Only the source URL has to be set in @channel
997  * (with grss_feed_channel_set_source()). Be aware this function is sync, do not
998  * returns until the feed isn't downloaded and parsed.
999  *
1000  * Returns: %TRUE if the feed is correctly fetched and parsed, %FALSE
1001  * otherwise.
1002  */
1003 gboolean
grss_feed_channel_fetch(GrssFeedChannel * channel,GError ** error)1004 grss_feed_channel_fetch (GrssFeedChannel *channel, GError **error)
1005 {
1006 	gboolean ret;
1007 	guint status;
1008 	SoupMessage *msg;
1009 	SoupSession *session;
1010 
1011 	ret = FALSE;
1012 
1013 	session = soup_session_sync_new ();
1014 	init_soup_session (session, channel);
1015 
1016 	msg = soup_message_new ("GET", grss_feed_channel_get_source (channel));
1017 	init_soup_message (msg, channel);
1018 
1019 	status = soup_session_send_message (session, msg);
1020 
1021 	if (status >= 200 && status <= 299) {
1022 		ret = quick_and_dirty_parse (channel, msg, NULL);
1023 		if (ret == FALSE)
1024 			g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_PARSE_ERROR, "Unable to parse file");
1025 	}
1026 	else {
1027 		g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_FETCH_ERROR,
1028 		             "Unable to download from %s", grss_feed_channel_get_source (channel));
1029 	}
1030 
1031 	g_object_unref (session);
1032 	g_object_unref (msg);
1033 	return ret;
1034 }
1035 
1036 static void
feed_downloaded(SoupSession * session,SoupMessage * msg,gpointer user_data)1037 feed_downloaded (SoupSession *session, SoupMessage *msg, gpointer user_data) {
1038 	guint status;
1039 	GSimpleAsyncResult *result;
1040 	GrssFeedChannel *channel;
1041 
1042 	result = user_data;
1043 	channel = GRSS_FEED_CHANNEL (g_async_result_get_source_object (G_ASYNC_RESULT (result)));
1044 	g_object_get (msg, "status-code", &status, NULL);
1045 
1046 	if (status >= 200 && status <= 299) {
1047 		if (quick_and_dirty_parse (channel, msg, NULL) == FALSE)
1048 			g_simple_async_result_set_error (result, FEED_CHANNEL_ERROR, FEED_CHANNEL_PARSE_ERROR,
1049 						 "Unable to parse feed from %s", grss_feed_channel_get_source (channel));
1050 	}
1051 	else {
1052 		g_simple_async_result_set_error (result, FEED_CHANNEL_ERROR, FEED_CHANNEL_FETCH_ERROR,
1053 						 "Unable to download from %s", grss_feed_channel_get_source (channel));
1054 	}
1055 
1056 	g_simple_async_result_complete_in_idle (result);
1057 	g_clear_object (&channel->priv->fetchcancel);
1058 	g_object_unref (result);
1059 }
1060 
1061 /**
1062  * grss_feed_channel_fetch_finish:
1063  * @channel: a #GrssFeedChannel.
1064  * @res: the #GAsyncResult passed to the callback.
1065  * @error: if an error occurred, %FALSE is returned and this is filled with the
1066  *         message.
1067  *
1068  * Finalizes an asyncronous operation started with
1069  * grss_feed_channel_fetch_async().
1070  *
1071  * Returns: %TRUE if @channel informations have been successfully fetched,
1072  * %FALSE otherwise.
1073  */
1074 gboolean
grss_feed_channel_fetch_finish(GrssFeedChannel * channel,GAsyncResult * res,GError ** error)1075 grss_feed_channel_fetch_finish (GrssFeedChannel *channel, GAsyncResult *res, GError **error)
1076 {
1077 	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
1078 		return FALSE;
1079 	else
1080 		return TRUE;
1081 }
1082 
1083 static void
do_prefetch(GrssFeedChannel * channel)1084 do_prefetch (GrssFeedChannel *channel)
1085 {
1086 	grss_feed_channel_fetch_cancel (channel);
1087 	channel->priv->fetchcancel = g_cancellable_new ();
1088 }
1089 
1090 /**
1091  * grss_feed_channel_fetch_async:
1092  * @channel: a #GrssFeedChannel.
1093  * @callback: function to invoke at the end of the download.
1094  * @user_data: data passed to the callback.
1095  *
1096  * Similar to grss_feed_channel_fetch(), but asyncronous.
1097  */
1098 void
grss_feed_channel_fetch_async(GrssFeedChannel * channel,GAsyncReadyCallback callback,gpointer user_data)1099 grss_feed_channel_fetch_async (GrssFeedChannel *channel, GAsyncReadyCallback callback, gpointer user_data)
1100 {
1101 	GSimpleAsyncResult *result;
1102 	SoupMessage *msg;
1103 	SoupSession *session;
1104 
1105 	/*
1106 		TODO: if the source is not valid, call anyway the callback with an error
1107 	*/
1108 
1109 	do_prefetch (channel);
1110 	result = g_simple_async_result_new (G_OBJECT (channel), callback, user_data, grss_feed_channel_fetch_async);
1111 	g_simple_async_result_set_check_cancellable (result, channel->priv->fetchcancel);
1112 
1113 	session = soup_session_async_new ();
1114 	init_soup_session (session, channel);
1115 
1116 	msg = soup_message_new ("GET", grss_feed_channel_get_source (channel));
1117 	init_soup_message (msg, channel);
1118 
1119 	soup_session_queue_message (session, msg, feed_downloaded, result);
1120 }
1121 
1122 /**
1123  * grss_feed_channel_fetch_all:
1124  * @channel: a #GrssFeedChannel.
1125  * @error: if an error occurred, %NULL is returned and this is filled with the
1126  *         message.
1127  *
1128  * Utility to fetch and populate a #GrssFeedChannel, and retrieve all its
1129  * items.
1130  *
1131  * Returns: (element-type GrssFeedItem) (transfer full): a GList
1132  * of #GrssFeedItem, to be completely unreferenced and freed when no
1133  * longer in use, or %NULL if an error occurs.
1134  */
1135 GList*
grss_feed_channel_fetch_all(GrssFeedChannel * channel,GError ** error)1136 grss_feed_channel_fetch_all (GrssFeedChannel *channel, GError **error)
1137 {
1138 	guint status;
1139 	GList *items;
1140 	SoupMessage *msg;
1141 	SoupSession *session;
1142 
1143 	session = soup_session_sync_new ();
1144 	init_soup_session (session, channel);
1145 
1146 	msg = soup_message_new ("GET", grss_feed_channel_get_source (channel));
1147 	init_soup_message (msg, channel);
1148 
1149 	status = soup_session_send_message (session, msg);
1150 	items = NULL;
1151 
1152 	if (status >= 200 && status <= 299) {
1153 		if (quick_and_dirty_parse (channel, msg, &items) == FALSE)
1154 			g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_PARSE_ERROR, "Unable to parse file");
1155 	}
1156 	else {
1157 		g_set_error (error, FEED_CHANNEL_ERROR, FEED_CHANNEL_FETCH_ERROR,
1158 		             "Unable to download from %s", grss_feed_channel_get_source (channel));
1159 	}
1160 
1161 	g_object_unref (session);
1162 	g_object_unref (msg);
1163 	return items;
1164 }
1165 
1166 static void
free_items_list(gpointer list)1167 free_items_list (gpointer list)
1168 {
1169 	GList *items;
1170 	GList *iter;
1171 
1172 	items = list;
1173 
1174 	for (iter = items; iter; iter = g_list_next (iter))
1175 		g_object_unref (iter->data);
1176 
1177 	g_list_free (items);
1178 }
1179 
1180 static void
feed_downloaded_return_items(SoupSession * session,SoupMessage * msg,gpointer user_data)1181 feed_downloaded_return_items (SoupSession *session, SoupMessage *msg, gpointer user_data)
1182 {
1183 	guint status;
1184 	GList *items;
1185 	GSimpleAsyncResult *result;
1186 	GrssFeedChannel *channel;
1187 
1188 	result = user_data;
1189 	channel = GRSS_FEED_CHANNEL (g_async_result_get_source_object (G_ASYNC_RESULT (result)));
1190 	g_object_get (msg, "status-code", &status, NULL);
1191 
1192 	if (status >= 200 && status <= 299) {
1193 		items = NULL;
1194 
1195 		if (quick_and_dirty_parse (channel, msg, &items) == TRUE)
1196 			g_simple_async_result_set_op_res_gpointer (result, items, free_items_list);
1197 		else
1198 			g_simple_async_result_set_error (result, FEED_CHANNEL_ERROR, FEED_CHANNEL_PARSE_ERROR,
1199 						 "Unable to parse feed from %s", grss_feed_channel_get_source (channel));
1200 	}
1201 	else {
1202 		g_simple_async_result_set_error (result, FEED_CHANNEL_ERROR, FEED_CHANNEL_FETCH_ERROR,
1203 						 "Unable to download from %s", grss_feed_channel_get_source (channel));
1204 	}
1205 
1206 	g_simple_async_result_complete_in_idle (result);
1207 	g_clear_object (&channel->priv->fetchcancel);
1208 	g_object_unref (result);
1209 }
1210 
1211 /**
1212  * grss_feed_channel_fetch_all_async:
1213  * @channel: a #GrssFeedChannel.
1214  * @callback: function to invoke at the end of the download.
1215  * @user_data: data passed to the callback.
1216  *
1217  * Similar to grss_feed_channel_fetch_all(), but asyncronous.
1218  */
1219 void
grss_feed_channel_fetch_all_async(GrssFeedChannel * channel,GAsyncReadyCallback callback,gpointer user_data)1220 grss_feed_channel_fetch_all_async (GrssFeedChannel *channel, GAsyncReadyCallback callback, gpointer user_data)
1221 {
1222 	GSimpleAsyncResult *result;
1223 	SoupMessage *msg;
1224 	SoupSession *session;
1225 
1226 	do_prefetch (channel);
1227 	result = g_simple_async_result_new (G_OBJECT (channel), callback, user_data, grss_feed_channel_fetch_async);
1228 	g_simple_async_result_set_check_cancellable (result, channel->priv->fetchcancel);
1229 
1230 	session = soup_session_async_new ();
1231 	init_soup_session (session, channel);
1232 
1233 	msg = soup_message_new ("GET", grss_feed_channel_get_source (channel));
1234 	init_soup_message (msg, channel);
1235 
1236 	soup_session_queue_message (session, msg, feed_downloaded_return_items, result);
1237 }
1238 
1239 /**
1240  * grss_feed_channel_fetch_all_finish:
1241  * @channel: a #GrssFeedChannel.
1242  * @res: the #GAsyncResult passed to the callback.
1243  * @error: if an error occurred, %NULL is returned and this is filled with the
1244  *         message.
1245  *
1246  * Finalizes an asyncronous operation started with
1247  * grss_feed_channel_fetch_all_async().
1248  *
1249  * Returns: (element-type GrssFeedItem) (transfer none): list of
1250  * items fetched from the #GrssFeedChannel, or %NULL if @error is
1251  * set. The list (and contained items) is freed at the end of the
1252  * callback
1253  */
1254 GList*
grss_feed_channel_fetch_all_finish(GrssFeedChannel * channel,GAsyncResult * res,GError ** error)1255 grss_feed_channel_fetch_all_finish (GrssFeedChannel *channel, GAsyncResult *res, GError **error)
1256 {
1257 	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
1258 		return NULL;
1259 	else
1260 		return (GList*) g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
1261 }
1262 
1263 /**
1264  * grss_feed_channel_fetch_cancel:
1265  * @channel: a #GrssFeedChannel.
1266  *
1267  * If a fetch operation was scheduled with grss_feed_channel_fetch_async() or
1268  * grss_feed_channel_fetch_all_async(), cancel it.
1269  *
1270  * Returns: %TRUE if a fetch was scheduled (and now cancelled), %FALSE if
1271  * this function had nothing to do
1272  */
1273 gboolean
grss_feed_channel_fetch_cancel(GrssFeedChannel * channel)1274 grss_feed_channel_fetch_cancel (GrssFeedChannel *channel)
1275 {
1276 	if (channel->priv->fetchcancel != NULL) {
1277 		g_cancellable_cancel (channel->priv->fetchcancel);
1278 		g_object_unref (channel->priv->fetchcancel);
1279 		return TRUE;
1280 	}
1281 	else {
1282 		return FALSE;
1283 	}
1284 }
1285 
1286