RSSyl: Stop earlier when an invalid feed is encountered.
[claws.git] / src / plugins / rssyl / rssyl_update_feed.c
1 /*
2  * Copyright (C) 2006 Andrej Kacian <andrej@kacian.sk>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 /* Global includes */
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <pthread.h>
28
29 /* Claws Mail includes */
30 #include <common/claws.h>
31 #include <mainwindow.h>
32 #include <statusbar.h>
33 #include <alertpanel.h>
34 #include <log.h>
35 #include <prefs_common.h>
36 #include <inc.h>
37 #include <main.h>
38
39 /* Local includes */
40 #include "libfeed/feed.h"
41 #include "rssyl.h"
42 #include "rssyl_deleted.h"
43 #include "rssyl_feed.h"
44 #include "rssyl_parse_feed.h"
45 #include "rssyl_prefs.h"
46 #include "rssyl_update_comments.h"
47
48 /* rssyl_fetch_feed_thr() */
49
50 static void *rssyl_fetch_feed_thr(void *arg)
51 {
52         RFetchCtx *ctx = (RFetchCtx *)arg;
53
54         /* Fetch and parse the feed. */
55         ctx->response_code = feed_update(ctx->feed, -1);
56
57         /* Signal main thread that we're done here. */
58         ctx->ready = TRUE;
59
60         return NULL;
61 }
62
63 /* rssyl_fetch_feed() */
64 void rssyl_fetch_feed(RFetchCtx *ctx, RSSylVerboseFlags verbose)
65 {
66 #ifdef USE_PTHREAD
67         pthread_t pt;
68 #endif
69
70         g_return_if_fail(ctx != NULL);
71
72 #ifdef USE_PTHREAD
73         if( pthread_create(&pt, PTHREAD_CREATE_JOINABLE, rssyl_fetch_feed_thr,
74                                 (void *)ctx) != 0 ) {
75                 /* Bummer, couldn't create thread. Continue non-threaded. */
76                 rssyl_fetch_feed_thr(ctx);
77         } else {
78                 /* Thread created, let's wait until it finishes. */
79                 debug_print("RSSyl: waiting for thread to finish (timeout: %ds)\n",
80                                 feed_get_timeout(ctx->feed));
81                 while( !ctx->ready ) {
82                         claws_do_idle();
83                 }
84
85                 debug_print("RSSyl: thread finished\n");
86                 pthread_join(pt, NULL);
87         }
88 #else
89         debug_print("RSSyl: no pthreads available, running non-threaded fetch\n");
90         rssyl_fetch_feed_thr(ctx);
91 #endif
92
93         if( ctx->response_code == FEED_ERR_INIT ) {
94                 debug_print("RSSyl: libfeed reports init error from libcurl\n");
95                 ctx->error = g_strdup("Internal error");
96         } else if( ctx->response_code == FEED_ERR_FETCH ) {
97                 debug_print("RSSyl: libfeed reports some other error from libcurl\n");
98                 ctx->error = g_strdup(ctx->feed->fetcherr);
99         } else if( ctx->response_code == FEED_ERR_UNAUTH ) {
100                 debug_print("RSSyl: URL authorization type is unknown\n");
101                 ctx->error = g_strdup("Unknown value for URL authorization type");
102         } else if( ctx->response_code >= 400 && ctx->response_code < 500 ) {
103                 switch( ctx->response_code ) {
104                         case 401:
105                                 ctx->error = g_strdup(_("401 (Authorisation required)"));
106                                 break;
107                         case 403:
108                                 ctx->error = g_strdup(_("403 (Unauthorised)"));
109                                 break;
110                         case 404:
111                                 ctx->error = g_strdup(_("404 (Not found)"));
112                                 break;
113                         default:
114                                 ctx->error = g_strdup_printf(_("Error %d"), ctx->response_code);
115                                 break;
116                 }
117         }
118
119         /* Here we handle "imperfect" conditions. If requested, we also
120          * display error dialogs for user. We always log the error. */
121         if( ctx->error != NULL ) {
122                 /* libcurl wasn't happy */
123                 debug_print("RSSyl: Error: %s\n", ctx->error);
124                 if( verbose & RSSYL_SHOW_ERRORS) {
125                         gchar *msg = g_markup_printf_escaped(
126                                         (const char *) C_("First parameter is URL, second is error text",
127                                                 "Error fetching feed at\n<b>%s</b>:\n\n%s"),
128                                         feed_get_url(ctx->feed), ctx->error);
129                         alertpanel_error("%s", msg);
130                         g_free(msg);
131                 }
132
133                 log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_FETCH, ctx->feed->url, ctx->error);
134
135                 ctx->success = FALSE;
136         } else {
137                 if( ctx->feed == NULL || ctx->response_code == FEED_ERR_NOFEED) {
138                         if( verbose & RSSYL_SHOW_ERRORS) {
139                                 gchar *msg = g_markup_printf_escaped(
140                                                 (const char *) _("No valid feed found at\n<b>%s</b>"),
141                                                 feed_get_url(ctx->feed));
142                                 alertpanel_error("%s", msg);
143                                 g_free(msg);
144                         }
145
146                         log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_NOFEED,
147                                         feed_get_url(ctx->feed));
148
149                         ctx->success = FALSE;
150                 } else if (feed_get_title(ctx->feed) == NULL) {
151                         /* We shouldn't do this, since a title is mandatory. */
152                         feed_set_title(ctx->feed, _("Untitled feed"));
153                         log_print(LOG_PROTOCOL,
154                                         _("RSSyl: Possibly invalid feed without title at %s.\n"),
155                                         feed_get_url(ctx->feed));
156                 }
157         }
158 }
159
160 RFetchCtx *rssyl_prep_fetchctx_from_item(RFolderItem *ritem)
161 {
162         RFetchCtx *ctx = NULL;
163
164         g_return_val_if_fail(ritem != NULL, NULL);
165
166         ctx = g_new0(RFetchCtx, 1);
167         ctx->feed = feed_new(ritem->url);
168         ctx->error = NULL;
169         ctx->success = TRUE;
170         ctx->ready = FALSE;
171
172         if (ritem->auth->type != FEED_AUTH_NONE)
173                 ritem->auth->password = rssyl_passwd_get(ritem);
174
175         feed_set_timeout(ctx->feed, prefs_common_get_prefs()->io_timeout_secs);
176         feed_set_cookies_path(ctx->feed, rssyl_prefs_get()->cookies_path);
177         feed_set_ssl_verify_peer(ctx->feed, ritem->ssl_verify_peer);
178         feed_set_auth(ctx->feed, ritem->auth);
179 #ifdef G_OS_WIN32
180         if (!g_ascii_strncasecmp(ritem->url, "https", 5)) {
181                 feed_set_cacert_file(ctx->feed, claws_ssl_get_cert_file());
182                 debug_print("RSSyl: using cert file '%s'\n", feed_get_cacert_file(ctx->feed));
183         }
184 #endif
185
186         return ctx;
187 }
188
189 RFetchCtx *rssyl_prep_fetchctx_from_url(gchar *url)
190 {
191         RFetchCtx *ctx = NULL;
192
193         g_return_val_if_fail(url != NULL, NULL);
194
195         ctx = g_new0(RFetchCtx, 1);
196         ctx->feed = feed_new(url);
197         ctx->error = NULL;
198         ctx->success = TRUE;
199         ctx->ready = FALSE;
200
201         feed_set_timeout(ctx->feed, prefs_common_get_prefs()->io_timeout_secs);
202         feed_set_cookies_path(ctx->feed, rssyl_prefs_get()->cookies_path);
203         feed_set_ssl_verify_peer(ctx->feed, rssyl_prefs_get()->ssl_verify_peer);
204 #ifdef G_OS_WIN32
205         if (!g_ascii_strncasecmp(url, "https", 5)) {
206                 feed_set_cacert_file(ctx->feed, claws_ssl_get_cert_file());
207                 debug_print("RSSyl: using cert file '%s'\n", feed_get_cacert_file(ctx->feed));
208         }
209 #endif
210
211         return ctx;
212 }
213
214 /* rssyl_update_feed() */
215
216 gboolean rssyl_update_feed(RFolderItem *ritem, RSSylVerboseFlags verbose)
217 {
218         RFetchCtx *ctx = NULL;
219         MainWindow *mainwin = mainwindow_get_mainwindow();
220         gchar *msg = NULL;
221         gboolean success = FALSE;
222
223         g_return_val_if_fail(ritem != NULL, FALSE);
224         g_return_val_if_fail(ritem->url != NULL, FALSE);
225
226         debug_print("RSSyl: starting to update '%s' (%s)\n",
227                         ritem->item.name, ritem->url);
228
229         log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATING, ritem->url);
230
231         msg = g_strdup_printf(_("Updating feed '%s'..."), ritem->item.name);
232         STATUSBAR_PUSH(mainwin, msg);
233         g_free(msg);
234
235         GTK_EVENTS_FLUSH();
236
237         /* Prepare context for fetching the feed file */
238         ctx = rssyl_prep_fetchctx_from_item(ritem);
239         g_return_val_if_fail(ctx != NULL, FALSE);
240
241         /* Fetch the feed file */
242         rssyl_fetch_feed(ctx, verbose);
243
244         if (ritem->auth != NULL && ritem->auth->password != NULL) {
245                 memset(ritem->auth->password, 0, strlen(ritem->auth->password));
246                 g_free(ritem->auth->password);
247         }
248
249         debug_print("RSSyl: fetch done; success == %s\n",
250                         ctx->success ? "TRUE" : "FALSE");
251
252         debug_print("RSSyl: STARTING TO PARSE FEED\n");
253   if( ctx->success && !(ctx->success = rssyl_parse_feed(ritem, ctx->feed)) ) {
254                 /* both libcurl and libfeed were happy, but we weren't */
255                 debug_print("RSSyl: Error processing feed\n");
256                 if( verbose & RSSYL_SHOW_ERRORS ) {
257                         gchar *msg = g_markup_printf_escaped(
258                                         (const char *) _("Couldn't process feed at\n<b>%s</b>\n\n"
259                                                 "Please contact developers, this should not happen."),
260                                         feed_get_url(ctx->feed));
261                         alertpanel_error("%s", msg);
262                         g_free(msg);
263                 }
264
265                 log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PROC, ctx->feed->url);
266         }
267         
268         debug_print("RSSyl: FEED PARSED\n");
269
270         STATUSBAR_POP(mainwin);
271
272         if( claws_is_exiting() ) {
273                 feed_free(ctx->feed);
274                 g_free(ctx->error);
275                 g_free(ctx);
276                 return success;
277         }
278
279         if( ritem->fetch_comments )
280                 rssyl_update_comments(ritem);
281
282         /* Prune our deleted items list of items which are no longer in
283          * upstream feed. */
284         rssyl_deleted_expire(ritem, ctx->feed);
285
286         /* Clean up. */
287         success = ctx->success;
288         feed_free(ctx->feed);
289         g_free(ctx->error);
290         g_free(ctx);
291
292         return success;
293 }
294
295 static gboolean rssyl_update_recursively_func(GNode *node, gpointer data)
296 {
297         FolderItem *item;
298         RFolderItem *ritem;
299
300         g_return_val_if_fail(node->data != NULL, FALSE);
301
302         item = FOLDER_ITEM(node->data);
303         ritem = (RFolderItem *)item;
304
305         if( ritem->url != NULL ) {
306                 debug_print("RSSyl: Updating feed '%s'\n", item->name);
307                 rssyl_update_feed(ritem, 0);
308         } else
309                 debug_print("RSSyl: Updating in folder '%s'\n", item->name);
310
311         return FALSE;
312 }
313
314 void rssyl_update_recursively(FolderItem *item)
315 {
316         g_return_if_fail(item != NULL);
317         g_return_if_fail(item->folder != NULL);
318
319         if( item->folder->klass != rssyl_folder_get_class() )
320                 return;
321
322         debug_print("Recursively updating '%s'\n", item->name);
323
324         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
325                         rssyl_update_recursively_func, NULL);
326 }
327
328 void rssyl_update_all_func(FolderItem *item, gpointer data)
329 {
330         /* Only try to refresh our feed folders */
331         if( !IS_RSSYL_FOLDER_ITEM(item) )
332                 return;
333
334         if( folder_item_parent(item) == NULL )
335                 rssyl_update_recursively(item);
336 }
337
338 void rssyl_update_all_feeds(void)
339 {
340         if (prefs_common_get_prefs()->work_offline &&
341                         !inc_offline_should_override(TRUE,
342                                 _("Claws Mail needs network access in order to update your feeds.")) ) {
343                 return;
344         }
345
346         folder_func_to_all_folders((FolderItemFunc)rssyl_update_all_func, NULL);
347 }