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