diff --git a/css/liferea.css b/css/liferea.css index d2f25cd62..d2f125e10 100644 --- a/css/liferea.css +++ b/css/liferea.css @@ -231,48 +231,33 @@ a.favicon img { padding:0; } -/* style for the HTTP error box at the beginning +/* style for the feed fetch error box at the beginning of the feed description and for item comment feeds */ -#errors, #commentFeedError { - width:100%; +#errors { border:0; border-bottom:1px solid black; - margin:0; + margin:12px 24px; + padding:12px; background:#ffa; color:black; + border:1px solid #GTK-COLOR-DARK; } -#commentFeedError { - border:1px solid black; +#errors ul { + list-style-type: none; + padding-inline-start: 15px; } -#parseError, #filterError, #updateError { - padding:2px; - padding-left:5px; - padding-right:5px; +#errors * li { + padding:1px; } -div.xmlparseroutput { - margin:10px 0px 10px 15px; +pre.errorOutput { + margin:10px 15px; padding:5px; border:2px solid #GTK-COLOR-DARK; background:#GTK-COLOR-LIGHT; -} - -span.details, span.detaillink { - visibility:hidden; - display:none; -} - -span.details { - padding:5px; - margin:15px; -} - -span.showmore { - text-decoration:underline; - color:blue; - cursor:pointer; + overflow:auto; } del { @@ -284,12 +269,6 @@ ins { } /* namespace specific styles */ -div.blogchanneltitle { - padding-left:10px; - padding-right:10px; - background-color:#GTK-COLOR-DARK; - color:#GTK-COLOR-TEXT; -} .slash { background:#60A080; diff --git a/css/user.css b/css/user.css index 1b19625b7..4dae105ce 100644 --- a/css/user.css +++ b/css/user.css @@ -84,18 +84,12 @@ // div.comment_body { } // div.comment_title { } -/* Styles for the HTTP error box at the beginning +/* Styles for the feed fetch error box at the beginning of the feed description and for item comment feeds */ -// #errors, #commentFeedError { } -// #parseError, #filterError, #updateError { } -// div.xmlparseroutput { } -// span.details, span.detaillink { } -// span.details { } -// span.showmore { } +// #errors { } +// pre.errorOutput { } /* namespace specific styles */ -// div.blogchanneltitle { } -// div.photoheader { } /* Gravatar embedding */ // img.gravatar { } diff --git a/src/comments.c b/src/comments.c index 71bcab5b1..008eda8af 100644 --- a/src/comments.c +++ b/src/comments.c @@ -125,13 +125,9 @@ comments_process_update_result (const struct updateResult * const result, gpoint /* parse the new downloaded feed into fake node, subscription and feed */ node = node_new (feed_get_node_type ()); - ctxt = feed_create_parser_ctxt (); - ctxt->subscription = subscription_new (result->source, NULL, NULL); - ctxt->feed = feed_new (); - node_set_data (node, ctxt->feed); - node_set_subscription (node, ctxt->subscription); - ctxt->data = result->data; - ctxt->dataLength = result->size; + node_set_data (node, feed_new ()); + node_set_subscription (node, subscription_new (result->source, NULL, NULL)); + ctxt = feed_parser_ctxt_new (node->subscription, result->data, result->size); if (!feed_parse (ctxt)) { debug0 (DEBUG_UPDATE, "parsing comment feed failed!"); @@ -159,16 +155,15 @@ comments_process_update_result (const struct updateResult * const result, gpoint } node_free (ctxt->subscription->node); - feed_free_parser_ctxt (ctxt); + feed_parser_ctxt_free (ctxt); } /* update error message */ g_free (commentFeed->error); commentFeed->error = NULL; - if ((result->httpstatus < 200) || (result->httpstatus >= 400)) { - commentFeed->error = g_strdup (network_strerror (result->returncode, result->httpstatus)); - } + if ((result->httpstatus < 200) || (result->httpstatus >= 400)) + commentFeed->error = g_strdup (network_strerror (result->httpstatus)); /* clean up... */ commentFeed->updateJob = NULL; diff --git a/src/feed.c b/src/feed.c index 6bb057893..585ed82a6 100644 --- a/src/feed.c +++ b/src/feed.c @@ -1,7 +1,7 @@ /** * @file feed.c feed node and subscription type * - * Copyright (C) 2003-2020 Lars Windolf + * Copyright (C) 2003-2021 Lars Windolf * Copyright (C) 2004-2006 Nathan J. Conrad * * This program is free software; you can redistribute it and/or modify @@ -178,6 +178,10 @@ feed_add_xml_attributes (nodePtr node, xmlNodePtr feedNode) if(feed->parseErrors && (strlen(feed->parseErrors->str) > 0)) xmlNewTextChild(feedNode, NULL, "parseError", feed->parseErrors->str); + + tmp = g_strdup_printf("%d", node->subscription->error); + xmlNewTextChild(feedNode, NULL, "error", tmp); + g_free(tmp); } xmlDocPtr @@ -297,7 +301,6 @@ feed_enrich_item (subscriptionPtr subscription, itemPtr item) update_execute_request (subscription, request, feed_enrich_item_cb, GUINT_TO_POINTER (item->id), FEED_REQ_NO_FEED); } - /* implementation of subscription type interface */ static void @@ -310,53 +313,48 @@ feed_process_update_result (subscriptionPtr subscription, const struct updateRes debug_enter ("feed_process_update_result"); - if (result->data) { - /* parse the new downloaded feed into feed and itemSet */ - ctxt = feed_create_parser_ctxt (); - ctxt->feed = feed; - ctxt->data = result->data; - ctxt->dataLength = result->size; - ctxt->subscription = subscription; - - /* try to parse the feed */ - if (!feed_parse (ctxt)) { - /* No feed found, display an error */ - node->available = FALSE; - - g_string_prepend (feed->parseErrors, _("

Could not detect the type of this feed! Please check if the source really points to a resource provided in one of the supported syndication formats!

" - "XML Parser Output:
")); - g_string_append (feed->parseErrors, "
"); - } else if (!ctxt->feed->fhp) { - /* There's a feed but no handler. This means autodiscovery - * found a feed, but we still need to download it. - * An update should be in progress that will process it */ - } else { - /* Feed found, process it */ - itemSetPtr itemSet; - - node->available = TRUE; - - /* merge the resulting items into the node's item set */ - itemSet = node_get_itemset (node); - node->newCount = itemset_merge_items (itemSet, ctxt->items, ctxt->feed->valid, ctxt->feed->markAsRead); - if (node->newCount) - itemlist_merge_itemset (itemSet); - itemset_free (itemSet); - - /* restore user defined properties if necessary */ - if ((flags & FEED_REQ_RESET_TITLE) && ctxt->title) - node_set_title (node, ctxt->title); - - if (flags > 0) - db_subscription_update (subscription); - } + ctxt = feed_parser_ctxt_new (subscription, result->data, result->size); - feed_free_parser_ctxt (ctxt); - } else { + /* try to parse the feed */ + if (!feed_parse (ctxt)) { + /* No feed found, display an error */ node->available = FALSE; + + } else if (!ctxt->feed->fhp) { + /* There's a feed but no handler. This means autodiscovery + * found a feed, but we still need to download it. + * An update should be in progress that will process it */ + } else { + /* Feed found, process it */ + itemSetPtr itemSet; + + node->available = TRUE; + + /* merge the resulting items into the node's item set */ + itemSet = node_get_itemset (node); + node->newCount = itemset_merge_items (itemSet, ctxt->items, ctxt->feed->valid, ctxt->feed->markAsRead); + if (node->newCount) + itemlist_merge_itemset (itemSet); + itemset_free (itemSet); + + /* restore user defined properties if necessary */ + if ((flags & FEED_REQ_RESET_TITLE) && ctxt->title) + node_set_title (node, ctxt->title); + + // FIXME: this duplicates the db_subscription_update() in subscription.c + if (flags > 0) + db_subscription_update (subscription); } - feed_list_view_update_node (node->id); + feed_parser_ctxt_free (ctxt); + + // FIXME: this should not be here, but in subscription.c + if (FETCH_ERROR_NONE != subscription->error) { + node->available = FALSE; + liferea_shell_set_status_bar (_("\"%s\" is not available"), node_get_title (node)); + } else { + liferea_shell_set_status_bar (_("\"%s\" updated..."), node_get_title (node)); + } debug_exit ("feed_process_update_result"); } diff --git a/src/feed.h b/src/feed.h index d5c0f9834..bd3645828 100644 --- a/src/feed.h +++ b/src/feed.h @@ -1,13 +1,13 @@ /** * @file feed.h common feed handling interface - * - * Copyright (C) 2003-2017 Lars Windolf + * + * Copyright (C) 2003-2021 Lars Windolf * Copyright (C) 2004-2006 Nathan J. Conrad * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -31,15 +31,15 @@ /* * The feed concept in Liferea comprises several standalone concepts: * - * 1.) A "feed" is an XML document to be parsed + * 1.) A "feed" is an XML/XML-like document to be parsed * (-> see feed_parser.h) * - * 2.) A "feed" is a node in the feed list. + * 2.) A "feed" is a node type that is visible in the feed list. * - * 3.) A "feed" is a way of updating a subscription. + * 3.) A "feed" is a subscription type: a way of updating. * - * The feed.h interface provides default methods for 2.) and 3.) that - * are used per-default but might be overwritten by node source, node + * The feed.h interface provides default methods for 2.) and 3.) that + * are used per-default but might be overwritten by node source, node * type or subscription type specific implementations. */ @@ -49,10 +49,10 @@ typedef struct feed { /* feed cache state properties */ gint cacheLimit; /**< Amount of cache to save: See the cache_limit enum */ - + /* feed parsing state */ gboolean valid; /**< FALSE if there was an error in xml_parse_feed() */ - GString *parseErrors; /**< textual description of parsing errors */ + GString *parseErrors; /**< Detailed textual description of parsing errors (e.g. library error handler output) */ gint64 time; /**< Feeds modified date */ /* feed specific behaviour settings */ @@ -76,7 +76,7 @@ feedPtr feed_new(void); * @param feedNode XML node to add feed attributes to, * or NULL if a new XML document is to * be created - * + * * @returns a new XML document (if feedNode was NULL) */ xmlDocPtr feed_to_xml(nodePtr node, xmlNodePtr xml); @@ -84,7 +84,7 @@ xmlDocPtr feed_to_xml(nodePtr node, xmlNodePtr xml); // FIXME: doesn't seem to belong here (looks like a subscription type method) /** * Returns the feed-specific maximum cache size. - * If none is set it returns the global default + * If none is set it returns the global default * setting. * * @param node the feed node diff --git a/src/feed_parser.c b/src/feed_parser.c index 5a92f9f6a..70f23417b 100644 --- a/src/feed_parser.c +++ b/src/feed_parser.c @@ -1,7 +1,7 @@ /** * @file feed_parser.c parsing of different feed formats * - * Copyright (C) 2008-2020 Lars Windolf + * Copyright (C) 2008-2021 Lars Windolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -82,17 +82,22 @@ feed_type_str_to_fhp (const gchar *str) } feedParserCtxtPtr -feed_create_parser_ctxt (void) +feed_parser_ctxt_new (subscriptionPtr subscription, const gchar *data, gsize size) { feedParserCtxtPtr ctxt; ctxt = g_new0 (struct feedParserCtxt, 1); + ctxt->subscription = subscription; + ctxt->feed = (feedPtr)subscription->node->data; + ctxt->data = data; + ctxt->dataLength = size; ctxt->tmpdata = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + return ctxt; } void -feed_free_parser_ctxt (feedParserCtxtPtr ctxt) +feed_parser_ctxt_free (feedParserCtxtPtr ctxt) { if (ctxt) { /* Don't free the itemset! */ @@ -107,8 +112,6 @@ feed_free_parser_ctxt (feedParserCtxtPtr ctxt) * tries to download it. If it finds a valid feed source it parses * this source instead into the given feed parsing context. It also * replaces the HTTP URI with the found feed source. - * - * Mutates ctxt->feed->parseErrors */ static gboolean feed_parser_auto_discover (feedParserCtxtPtr ctxt) @@ -116,11 +119,6 @@ feed_parser_auto_discover (feedParserCtxtPtr ctxt) gchar *source = NULL; GSList *links; - if (ctxt->feed->parseErrors) - g_string_truncate (ctxt->feed->parseErrors, 0); - else - ctxt->feed->parseErrors = g_string_new(NULL); - debug2 (DEBUG_UPDATE, "Starting feed auto discovery (%s) redirects=%d", subscription_get_source (ctxt->subscription), ctxt->subscription->autoDiscoveryTries); links = html_auto_discover_feed (ctxt->data, subscription_get_source (ctxt->subscription)); @@ -142,7 +140,6 @@ feed_parser_auto_discover (feedParserCtxtPtr ctxt) } debug0 (DEBUG_UPDATE, "No feed link found!"); - g_string_append (ctxt->feed->parseErrors, _("The URL you want Liferea to subscribe to points to a webpage and the auto discovery found no feeds on this page. Maybe this webpage just does not support feed auto discovery.")); return FALSE; } @@ -195,11 +192,12 @@ feed_parse (feedParserCtxtPtr ctxt) /* 1.) try to parse downloaded data as XML */ do { if (NULL == (xmlDoc = xml_parse_feed (ctxt))) { - //g_string_append_printf (ctxt->feed->parseErrors, _("XML error while reading feed! Feed \"%s\" could not be loaded!"), subscription_get_source (ctxt->subscription)); + ctxt->subscription->error = FETCH_ERROR_XML; break; } if (NULL == (xmlNode = xmlDocGetRootElement (xmlDoc))) { + ctxt->subscription->error = FETCH_ERROR_XML; g_string_append (ctxt->feed->parseErrors, _("Empty document!")); break; } @@ -257,17 +255,12 @@ feed_parse (feedParserCtxtPtr ctxt) if (xmlDoc) xmlFreeDoc (xmlDoc); - /* 4.) We give up and inform the user */ + /* 4.) Update subscription error status */ if (!success && !autoDiscovery) { - /* test if we have a HTML page */ + /* Fuzzy test for HTML document */ if ((strstr (ctxt->data, "") || strstr (ctxt->data, "") || - strstr (ctxt->data, "data, "feed->parseErrors, _("Source points to HTML document.")); - } else { - debug0 (DEBUG_UPDATE, "neither a known feed type nor a HTML document!"); - g_string_append (ctxt->feed->parseErrors, _("Could not determine the feed type.")); - } + strstr (ctxt->data, "data, "subscription->error = FETCH_ERROR_DISCOVER; } else { if (ctxt->feed->fhp) { debug1 (DEBUG_UPDATE, "discovered feed format: %s", feed_type_fhp_to_str (ctxt->feed->fhp)); @@ -278,6 +271,7 @@ feed_parse (feedParserCtxtPtr ctxt) succeed. Still our auto-discovery was successful. */ } success = TRUE; + ctxt->subscription->error = FETCH_ERROR_NONE; } debug_exit ("feed_parse"); diff --git a/src/feed_parser.h b/src/feed_parser.h index c3b3ba6ba..a6a1d0b4d 100644 --- a/src/feed_parser.h +++ b/src/feed_parser.h @@ -1,7 +1,7 @@ /** * @file feed_parser.h parsing of different feed formats * - * Copyright (C) 2008 Lars Windolf + * Copyright (C) 2008-2021 Lars Windolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,11 +36,10 @@ typedef struct feedParserCtxt { gchar *title; /**< resulting feed/channel title */ - gchar *data; /**< data buffer to parse */ + const gchar *data; /**< data buffer to parse */ gsize dataLength; /**< length of the data buffer */ } *feedParserCtxtPtr; - /** * Function type which parses the given feed data. * @@ -70,9 +69,13 @@ typedef struct feedHandler { /** * Creates a new feed parsing context. * + * @subscription the feed's subscription + * @data the feed data to parse + * @size size of feed data + * * @returns a new feed parsing context */ -feedParserCtxtPtr feed_create_parser_ctxt (void); +feedParserCtxtPtr feed_parser_ctxt_new (subscriptionPtr subscription, const gchar *data, gsize size); /** * Frees the given parser context. Note: it does @@ -80,7 +83,7 @@ feedParserCtxtPtr feed_create_parser_ctxt (void); * * @param ctxt the feed parsing context */ -void feed_free_parser_ctxt (feedParserCtxtPtr ctxt); +void feed_parser_ctxt_free (feedParserCtxtPtr ctxt); /** * Lookup a feed type string from the feed type id. diff --git a/src/net.c b/src/net.c index abb82313e..08aea93f8 100644 --- a/src/net.c +++ b/src/net.c @@ -1,7 +1,7 @@ /** * @file net.c HTTP network access using libsoup * - * Copyright (C) 2007-2015 Lars Windolf + * Copyright (C) 2007-2021 Lars Windolf * Copyright (C) 2009 Emilio Pozuelo Monfort * * This program is free software; you can redistribute it and/or modify @@ -60,7 +60,7 @@ network_process_redirect_callback (SoupMessage *msg, gpointer user_data) if (SOUP_URI_IS_VALID (newuri) && ! soup_uri_equal (newuri, soup_message_get_uri (msg))) { debug2 (DEBUG_NET, "\"%s\" permanently redirects to new location \"%s\"", soup_uri_to_string (soup_message_get_uri (msg), FALSE), soup_uri_to_string (newuri, FALSE)); - job->result->returncode = msg->status_code; + job->result->httpstatus = msg->status_code; job->result->source = soup_uri_to_string (newuri, FALSE); } } @@ -78,12 +78,7 @@ network_process_callback (SoupSession *session, SoupMessage *msg, gpointer user_ gint age; job->result->source = soup_uri_to_string (soup_message_get_uri(msg), FALSE); - if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { - job->result->returncode = msg->status_code; - job->result->httpstatus = 0; - } else { - job->result->httpstatus = msg->status_code; - } + job->result->httpstatus = msg->status_code; /* keep some request headers for revalidated responses */ revalidated = (304 == job->result->httpstatus); @@ -435,10 +430,9 @@ network_set_proxy (ProxyDetectMode mode, gchar *host, guint port, gchar *user, g } const char * -network_strerror (gint netstatus, gint httpstatus) +network_strerror (gint status) { const gchar *tmp = NULL; - int status = netstatus?netstatus:httpstatus; switch (status) { /* Some libsoup transport errors */ @@ -462,7 +456,7 @@ network_strerror (gint netstatus, gint httpstatus) case SOUP_STATUS_NOT_ACCEPTABLE: tmp = _("Not Acceptable"); break; case SOUP_STATUS_PROXY_UNAUTHORIZED: tmp = _("Proxy authentication required"); break; case SOUP_STATUS_REQUEST_TIMEOUT: tmp = _("Request timed out"); break; - case SOUP_STATUS_GONE: tmp = _("Gone. Resource doesn't exist. Please unsubscribe!"); break; + case SOUP_STATUS_GONE: tmp = _("The webserver indicates this feed is discontinued. It's no longer available. Liferea won't update it anymore but you can still access the cached headlines."); break; } if (!tmp) { diff --git a/src/net.h b/src/net.h index 4cfa25d92..9a5b964bf 100644 --- a/src/net.h +++ b/src/net.h @@ -1,7 +1,7 @@ /** * @file net.h HTTP network access interface * - * Copyright (C) 2007-2009 Lars Windolf + * Copyright (C) 2007-2021 Lars Windolf * Copyright (C) 2009 Emilio Pozuelo Monfort * * This program is free software; you can redistribute it and/or modify @@ -101,11 +101,10 @@ void network_process_request (const updateJobPtr job); /** * Returns explanation string for the given network error code. * - * @param netstatus network error status - * @param httpstatus HTTP status code + * @param status libsoup status code * * @returns explanation string */ -const char * network_strerror (gint netstatus, gint httpstatus); +const char * network_strerror (gint status); #endif diff --git a/src/render.c b/src/render.c index 8048eabed..c03879381 100644 --- a/src/render.c +++ b/src/render.c @@ -405,7 +405,18 @@ render_xml (xmlDocPtr doc, const gchar *xsltName, renderParamPtr paramSet) return NULL; } - /* for debugging use: xsltSaveResultToFile(stdout, resDoc, xslt); */ + /* + for XLST input debugging use: + + xmlChar *buffer; + gint buffersize; + xmlDocDumpFormatMemory(doc, &buffer, &buffersize, 1); + printf("%s", (char *) buffer); + + for XSLT output debugging use: + + xsltSaveResultToFile(stdout, resDoc, xslt); + */ /* save results into return string */ buf = xmlAllocOutputBuffer (NULL); diff --git a/src/subscription.c b/src/subscription.c index 97747c296..15c16b9e5 100644 --- a/src/subscription.c +++ b/src/subscription.c @@ -1,7 +1,7 @@ /** * @file subscription.c common subscription handling * - * Copyright (C) 2003-2020 Lars Windolf + * Copyright (C) 2003-2021 Lars Windolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -136,17 +136,13 @@ subscription_reset_update_counter (subscriptionPtr subscription, guint64 *now) * * @param subscription the subscription * @param httpstatus the new HTTP status code - * @param resultcode the update result code * @param filterError filter error string (or NULL) */ static void subscription_update_error_status (subscriptionPtr subscription, gint httpstatus, - gint resultcode, gchar *filterError) { - gboolean errorFound = FALSE; - if (subscription->filterError) g_free (subscription->filterError); if (subscription->httpError) @@ -155,22 +151,21 @@ subscription_update_error_status (subscriptionPtr subscription, g_free (subscription->updateError); subscription->filterError = g_strdup (filterError); - subscription->updateError = NULL; + subscription->updateError = NULL; // FIXME: this might not be very useful! subscription->httpError = NULL; subscription->httpErrorCode = httpstatus; - if (((httpstatus >= 200) && (httpstatus < 400)) && /* HTTP codes starting with 2 and 3 mean no error */ - (NULL == subscription->filterError)) - return; + /* Note: the httpstatus we get here is a libsoup status code + which is either a HTTP status code or a libsoup status. + https://developer.gnome.org/libsoup/unstable/libsoup-2.4-soup-status.html - if ((200 != httpstatus) || (resultcode != 0)) { - subscription->httpError = g_strdup (network_strerror (resultcode, httpstatus)); - errorFound = TRUE; - } + Therefore we know if it is between 200 an 399 all is fine. - /* if none of the above error descriptions matched... */ - if (!errorFound) - subscription->updateError = g_strdup (_("There was a problem while reading this subscription. Please check the URL and console output.")); + Otherwise we build a message according to the libsoup doc + */ + + if (!((httpstatus >= 200) && (httpstatus < 400))) + subscription->httpError = g_strdup (network_strerror (httpstatus)); } static void @@ -179,12 +174,6 @@ subscription_process_update_result (const struct updateResult * const result, gp subscriptionPtr subscription = (subscriptionPtr)user_data; nodePtr node = subscription->node; gboolean processing = FALSE; - guint64 now; - gint next_update = 0; - gint update_time_sources = 0; - gint maxage = -1; - gint syn_update = -1; - gint ttl = subscription->updateState->timeToLive = -1; guint count, maxcount; gchar *statusbar; @@ -193,21 +182,33 @@ subscription_process_update_result (const struct updateResult * const result, gp g_assert (subscription->updateJob); /* update the subscription URL on permanent redirects */ - if ((301 == result->returncode || 308 == result->returncode) && result->source && !g_str_equal (result->source, subscription->updateJob->request->source)) { + if ((301 == result->httpstatus || 308 == result->httpstatus) && result->source && !g_str_equal (result->source, subscription->updateJob->request->source)) { debug2 (DEBUG_UPDATE, "The URL of \"%s\" has changed permanently and was updated to \"%s\"", node_get_title(node), result->source); subscription_set_source (subscription, result->source); - statusbar = g_strdup_printf (_("The URL of \"%s\" has changed permanently and was updated"), node_get_title(node)); - } - - if (401 == result->httpstatus) { /* unauthorized */ - auth_dialog_new (subscription, flags); - } else if (410 == result->httpstatus) { /* gone */ - subscription->discontinued = TRUE; - node->available = TRUE; - statusbar = g_strdup_printf (_("\"%s\" is discontinued. Liferea won't updated it anymore!"), node_get_title (node)); + statusbar = g_strdup_printf (_("The URL of \"%s\" has changed permanently and was updated"), node_get_title(node)); + } + + /* consider everything that prevents processing the data we got */ + if (result->httpstatus >= 400 || !result->data) { + /* Default */ + subscription->error = FETCH_ERROR_NET; + node->available = FALSE; + + /* Special handling */ + if (401 == result->httpstatus) { /* unauthorized */ + subscription->error = FETCH_ERROR_AUTH; + auth_dialog_new (subscription, flags); + } + if (410 == result->httpstatus) { /* gone */ + subscription->discontinued = TRUE; + statusbar = g_strdup_printf (_("\"%s\" is discontinued. Liferea won't updated it anymore!"), node_get_title (node)); + } } else if (304 == result->httpstatus) { node->available = TRUE; statusbar = g_strdup_printf (_("\"%s\" has not changed since last update"), node_get_title(node)); + } else if (result->filterErrors) { + node->available = FALSE; + subscription->error = FETCH_ERROR_NET; } else { processing = TRUE; } @@ -220,7 +221,7 @@ subscription_process_update_result (const struct updateResult * const result, gp liferea_shell_set_status_bar (_("Updating (%d / %d) ..."), maxcount - count, maxcount); g_free (statusbar); - subscription_update_error_status (subscription, result->httpstatus, result->returncode, result->filterErrors); + subscription_update_error_status (subscription, result->httpstatus, result->filterErrors); subscription->updateJob = NULL; @@ -228,12 +229,11 @@ subscription_process_update_result (const struct updateResult * const result, gp if (processing) SUBSCRIPTION_TYPE (subscription)->process_update_result (subscription, result, flags); - /* 3. call favicon updating after subscription processing + /* 3. call favicon updating only after subscription processing to ensure we have valid baseUrl for feed nodes... check creation date and update favicon if older than one month */ - now = g_get_real_time(); - if (now > (subscription->updateState->lastFaviconPoll + ONE_MONTH_MICROSECONDS)) + if (g_get_real_time() > (subscription->updateState->lastFaviconPoll + ONE_MONTH_MICROSECONDS)) subscription_icon_update (subscription); /* 4. generic postprocessing */ @@ -242,16 +242,19 @@ subscription_process_update_result (const struct updateResult * const result, gp update_state_set_etag (subscription->updateState, update_state_get_etag (result->updateState)); subscription->updateState->lastPoll = g_get_real_time(); - // FIXME: use new-items signal in itemview class + // FIXME: use signal here itemview_update_node_info (subscription->node); itemview_update (); db_subscription_update (subscription); db_node_update (subscription->node); + feedlist_node_was_updated (node); + feed_list_view_update_node (node->id); // FIXME: This should be dropped once the "node-updated" signal is consumed + if (processing && subscription->node->newCount > 0) { + // FIXME: use new-items signal in itemview class feedlist_new_items (node->newCount); - feedlist_node_was_updated (node); } } diff --git a/src/subscription.h b/src/subscription.h index abd1b5d28..323a3571f 100644 --- a/src/subscription.h +++ b/src/subscription.h @@ -1,7 +1,7 @@ /** * @file subscription.h common subscription handling interface * - * Copyright (C) 2003-2012 Lars Windolf + * Copyright (C) 2003-2021 Lars Windolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,6 +40,16 @@ enum feed_request_flags { FEED_REQ_NO_FEED = (1<<4) /**< Requesting something not a feed (just for statistics) */ }; +/** Subscription fetching error types */ +typedef enum fetchError { + FETCH_ERROR_NONE = 0, + FETCH_ERROR_AUTH = 1 << 0, + FETCH_ERROR_NET = 1 << 1, + FETCH_ERROR_DISCOVER = 1 << 2, + FETCH_ERROR_XML = 1 << 3 + /* when adding stuff here, extend xstl/feed.xml.in also! */ +} fetchError; + /** Common structure to hold all information about a single subscription. */ typedef struct subscription { nodePtr node; /**< the feed list node the subscription is attached to */ @@ -55,6 +65,7 @@ typedef struct subscription { GSList *metadata; /**< metadata list assigned to this subscription */ + fetchError error; /**< Fetch error code (used for user-facing UI to differentiate subscription update processing phases) */ gchar *updateError; /**< textual description of processing errors */ gchar *httpError; /**< textual description of HTTP protocol errors */ gint httpErrorCode; /**< last HTTP error code */ diff --git a/src/update.c b/src/update.c index 120b64561..d188e45db 100644 --- a/src/update.c +++ b/src/update.c @@ -28,8 +28,6 @@ #include #include -#include - #include #include #if !defined (G_OS_WIN32) || defined (HAVE_SYS_WAIT_H) @@ -333,7 +331,7 @@ update_job_free (updateJobPtr job) /* filter idea (and some of the code) was taken from Snownews */ static gchar * -update_exec_filter_cmd (gchar *cmd, gchar *data, gchar **errorOutput, size_t *size) +update_exec_filter_cmd (updateJobPtr job) { int fd, status; gchar *command; @@ -341,47 +339,48 @@ update_exec_filter_cmd (gchar *cmd, gchar *data, gchar **errorOutput, size_t *si char *tmpfilename; char *out = NULL; FILE *file, *p; + size_t size = 0; - *errorOutput = NULL; tmpfilename = g_build_filename (tmpdir, "liferea-XXXXXX", NULL); - fd = g_mkstemp(tmpfilename); + fd = g_mkstemp (tmpfilename); - if(fd == -1) { - debug1(DEBUG_UPDATE, "Error opening temp file %s to use for filtering!", tmpfilename); - *errorOutput = g_strdup_printf(_("Error opening temp file %s to use for filtering!"), tmpfilename); - g_free(tmpfilename); + if (fd == -1) { + debug1 (DEBUG_UPDATE, "Error opening temp file %s to use for filtering!", tmpfilename); + job->result->filterErrors = g_strdup_printf (_("Error opening temp file %s to use for filtering!"), tmpfilename); + g_free (tmpfilename); return NULL; } - file = fdopen(fd, "w"); - fwrite(data, strlen(data), 1, file); - fclose(file); + file = fdopen (fd, "w"); + fwrite (job->result->data, strlen (job->result->data), 1, file); + fclose (file); - *size = 0; - command = g_strdup_printf("%s < %s", cmd, tmpfilename); - p = popen(command, "r"); - g_free(command); - if(NULL != p) { - while(!feof(p) && !ferror(p)) { + command = g_strdup_printf("%s < %s", job->request->filtercmd, tmpfilename); + p = popen (command, "r"); + if (NULL != p) { + while (!feof (p) && !ferror (p)) { size_t len; - out = g_realloc(out, *size+1025); - len = fread(&out[*size], 1, 1024, p); - if(len > 0) - *size += len; + out = g_realloc (out, size + 1025); + len = fread (&out[size], 1, 1024, p); + if (len > 0) + size += len; } - status = pclose(p); - if(!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { - *errorOutput = g_strdup_printf(_("%s exited with status %d"), - cmd, WEXITSTATUS(status)); - *size = 0; + status = pclose (p); + if (!(WIFEXITED (status) && WEXITSTATUS (status) == 0)) { + debug2 (DEBUG_UPDATE, "%s exited with status %d!", command, WEXITSTATUS(status)); + job->result->filterErrors = g_strdup_printf (_("%s exited with status %d"), command, WEXITSTATUS(status)); + size = 0; } - out[*size] = '\0'; + if (out) + out[size] = '\0'; } else { - g_warning(_("Error: Could not open pipe \"%s\""), command); - *errorOutput = g_strdup_printf(_("Error: Could not open pipe \"%s\""), command); + g_warning (_("Error: Could not open pipe \"%s\""), command); + job->result->filterErrors = g_strdup_printf (_("Error: Could not open pipe \"%s\""), command); } + /* Clean up. */ + g_free (command); unlink (tmpfilename); g_free (tmpfilename); return out; @@ -448,23 +447,20 @@ static void update_apply_filter (updateJobPtr job) { gchar *filterResult; - size_t len = 0; g_assert (NULL == job->result->filterErrors); /* we allow two types of filters: XSLT stylesheets and arbitrary commands */ if ((strlen (job->request->filtercmd) > 4) && - (0 == strcmp (".xsl", job->request->filtercmd + strlen (job->request->filtercmd) - 4))) { + (0 == strcmp (".xsl", job->request->filtercmd + strlen (job->request->filtercmd) - 4))) filterResult = update_apply_xslt (job); - len = strlen (filterResult); - } else { - filterResult = update_exec_filter_cmd (job->request->filtercmd, job->result->data, &(job->result->filterErrors), &len); - } + else + filterResult = update_exec_filter_cmd (job); if (filterResult) { g_free (job->result->data); job->result->data = filterResult; - job->result->size = len; + job->result->size = strlen(filterResult); } } diff --git a/src/update.h b/src/update.h index 5a433380a..8e557e27e 100644 --- a/src/update.h +++ b/src/update.h @@ -110,7 +110,6 @@ typedef struct updateResult { gchar *source; /**< Location of the downloaded document, in case of redirects different from the one given along with the update request */ - int returncode; /**< Download status (0=success, otherwise error) */ int httpstatus; /**< HTTP status. Set to 200 for any valid command, file access, etc.... Set to 0 for unknown */ gchar *data; /**< Downloaded data */ size_t size; /**< Size of downloaded data */ diff --git a/src/xml.c b/src/xml.c index ed2fb884d..e60a0e225 100644 --- a/src/xml.c +++ b/src/xml.c @@ -415,13 +415,13 @@ xml_buffer_parse_error (void *ctxt, const gchar * msg, ...) g_free (newmsg); newmsg = tmp; - g_string_append_printf(errors->msg, "
%s
\n", newmsg); + g_string_append_printf(errors->msg, "%s\n", newmsg); } g_free(newmsg); } if (MAX_PARSE_ERROR_LINES == errors->errorCount) - g_string_append_printf (errors->msg, "
%s", _("[There were more errors. Output was truncated!]")); + g_string_append (errors->msg, _("[There were more errors. Output was truncated!]")); } static xmlDocPtr entities = NULL; @@ -530,7 +530,6 @@ liferea_xml_errorSAXFunc (void * ctx, const char * msg,...) { va_list valist; gchar *parser_error = NULL; - va_start(valist,msg); parser_error = g_strdup_vprintf (msg, valist); va_end(valist); @@ -538,9 +537,8 @@ liferea_xml_errorSAXFunc (void * ctx, const char * msg,...) g_free (parser_error); } - xmlDocPtr -xml_parse (gchar *data, size_t length, errorCtxtPtr errCtx) +xml_parse (const gchar *data, size_t length, errorCtxtPtr errCtx) { xmlParserCtxtPtr ctxt; xmlDocPtr doc; diff --git a/src/xml.h b/src/xml.h index 8d4c9b65f..529fa33be 100644 --- a/src/xml.h +++ b/src/xml.h @@ -181,10 +181,7 @@ typedef struct errorCtxt { } *errorCtxtPtr; /** - * Common function to create a XML DOM object from a given XML buffer. - * - * The function returns a XML document pointer or NULL - * if the document could not be read. + * Common function to create a XML DOM object from a given string * * @param data XML document buffer * @param length length of buffer @@ -192,7 +189,7 @@ typedef struct errorCtxt { * * @return XML document */ -xmlDocPtr xml_parse (gchar *data, size_t length, errorCtxtPtr errors); +xmlDocPtr xml_parse (const gchar *data, size_t length, errorCtxtPtr errors); /** * Common function to create a XML DOM object from a given diff --git a/xslt/feed.xml.in b/xslt/feed.xml.in index 5e63ae61d..cb68d2dcb 100644 --- a/xslt/feed.xml.in +++ b/xslt/feed.xml.in @@ -4,7 +4,7 @@ /** * Rendering stylesheet for Liferea (feed description view) * - * Copyright (C) 2006-2019 Lars Windolf + * Copyright (C) 2006-2021 Lars Windolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,69 +39,6 @@ - - -
- - -
- <_span>This feed is discontinued. It's no longer available. Liferea won't update it anymore but you can still access the cached headlines. -
-
- - -
- -
-
- - -
- <_span>The last update of this subscription failed!
HTTP error code : -
-
- - -
- <_span>There were errors while parsing this feed! - - - ( - <_span>Parser Error Details - ) - - - - <_span>Details
- - - -
<_span>You may want to contact the author/webmaster of the feed about this! -
-
-
- - -
- <_span>There were errors while filtering this feed! - - - ( - <_span>Filter Error Details - ) - - - - <_span>Details
- - -
-
-
- -
-
- @@ -157,6 +94,88 @@
+ + +
+ + <_span>There was a problem when fetching this subscription! +
    +
  • + + + + <_span>1. Authentication +
  • +
  • + + + + <_span>2. Download +
  • +
  • + + + + <_span>3. Feed Discovery +
  • +
  • + + + + <_span>4. Parsing +
  • +
+ + + <_span>Details: + + + +

<_span>Authentication failed. Please check the credentials and try again!

+
+ + + +

+ + HTTP : + + +

+
+ + +

+ <_span>There was an error when downloading the feed source: +

+

+
+ + +

+ <_span>There was an error when running the feed filter command: +

+

+
+
+ + +

<_span>The source does not point directly to a feed or a webpage with a link to a feed!

+
+ + +

<_span>Sorry, the feed could not be parsed!

+ +
+

<_span>You may want to contact the author/webmaster of the feed about this!

+
+
+
+
+ +
+
+
@@ -174,6 +193,16 @@ + + + + + + + + + +