From 7b80f1f639341f9711d9b28b45de2939f1b55728 Mon Sep 17 00:00:00 2001 From: Andrej Kacian Date: Mon, 2 Jun 2014 00:24:21 +0200 Subject: [PATCH] New RSSyl replacing old one. --- configure.ac | 20 +- po/POTFILES.in | 13 +- src/plugins/rssyl/Makefile.am | 30 +- src/plugins/rssyl/feed.c | 1869 ----------------- src/plugins/rssyl/feed.h | 94 - src/plugins/rssyl/feedprops.c | 433 ---- src/plugins/rssyl/feedprops.h | 26 - src/plugins/rssyl/libfeed/Makefile.am | 22 + src/plugins/rssyl/{ => libfeed}/date.c | 86 +- src/plugins/rssyl/{ => libfeed}/date.h | 1 + src/plugins/rssyl/libfeed/feed.c | 356 ++++ src/plugins/rssyl/libfeed/feed.h | 114 + src/plugins/rssyl/libfeed/feeditem.c | 372 ++++ src/plugins/rssyl/libfeed/feeditem.h | 114 + src/plugins/rssyl/libfeed/feeditemenclosure.c | 100 + src/plugins/rssyl/libfeed/feeditemenclosure.h | 43 + src/plugins/rssyl/libfeed/parser.c | 359 ++++ src/plugins/rssyl/libfeed/parser.h | 45 + src/plugins/rssyl/libfeed/parser_atom10.c | 230 ++ src/plugins/rssyl/libfeed/parser_atom10.h | 34 + src/plugins/rssyl/libfeed/parser_opml.c | 131 ++ src/plugins/rssyl/libfeed/parser_opml.h | 23 + src/plugins/rssyl/libfeed/parser_rdf.c | 144 ++ src/plugins/rssyl/libfeed/parser_rdf.h | 32 + src/plugins/rssyl/libfeed/parser_rss20.c | 178 ++ src/plugins/rssyl/libfeed/parser_rss20.h | 26 + src/plugins/rssyl/old_feeds.c | 167 ++ src/plugins/rssyl/old_feeds.h | 22 + src/plugins/rssyl/{opml.c => opml_export.c} | 156 +- src/plugins/rssyl/{opml.h => opml_export.h} | 1 - src/plugins/rssyl/opml_import.c | 110 + src/plugins/rssyl/opml_import.h | 15 + src/plugins/rssyl/parse822.c | 352 ++++ src/plugins/rssyl/parse822.h | 19 + src/plugins/rssyl/parsers.c | 535 ----- src/plugins/rssyl/parsers.h | 13 - src/plugins/rssyl/plugin.c | 22 +- src/plugins/rssyl/rssyl.c | 584 +++-- src/plugins/rssyl/rssyl.h | 74 +- src/plugins/rssyl/rssyl_add_item.c | 540 +++++ src/plugins/rssyl/rssyl_add_item.h | 6 + src/plugins/rssyl/rssyl_cb_gtk.c | 154 -- src/plugins/rssyl/rssyl_cb_gtk.h | 19 - src/plugins/rssyl/rssyl_cb_menu.c | 382 ++-- src/plugins/rssyl/rssyl_cb_menu.h | 11 +- src/plugins/rssyl/rssyl_deleted.c | 354 ++++ src/plugins/rssyl/rssyl_deleted.h | 14 + src/plugins/rssyl/rssyl_feed.c | 183 ++ src/plugins/rssyl/rssyl_feed.h | 27 + src/plugins/rssyl/rssyl_feed_props.c | 566 +++++ src/plugins/rssyl/rssyl_feed_props.h | 24 + src/plugins/rssyl/rssyl_gtk.c | 613 +----- src/plugins/rssyl/rssyl_gtk.h | 20 +- src/plugins/rssyl/rssyl_parse_feed.c | 201 ++ src/plugins/rssyl/rssyl_parse_feed.h | 16 + src/plugins/rssyl/rssyl_prefs.c | 227 +- src/plugins/rssyl/rssyl_prefs.h | 21 +- src/plugins/rssyl/rssyl_subscribe.c | 151 ++ src/plugins/rssyl/rssyl_subscribe.h | 6 + src/plugins/rssyl/rssyl_subscribe_gtk.c | 109 + src/plugins/rssyl/rssyl_subscribe_gtk.h | 15 + src/plugins/rssyl/rssyl_update_comments.c | 139 ++ src/plugins/rssyl/rssyl_update_comments.h | 12 + src/plugins/rssyl/rssyl_update_feed.c | 303 +++ src/plugins/rssyl/rssyl_update_feed.h | 19 + src/plugins/rssyl/rssyl_update_format.c | 278 +++ src/plugins/rssyl/rssyl_update_format.h | 6 + src/plugins/rssyl/strreplace.c | 127 -- src/plugins/rssyl/strreplace.h | 8 - src/plugins/rssyl/strutils.c | 289 +++ src/plugins/rssyl/strutils.h | 19 + 71 files changed, 7269 insertions(+), 4555 deletions(-) delete mode 100644 src/plugins/rssyl/feed.c delete mode 100644 src/plugins/rssyl/feed.h delete mode 100644 src/plugins/rssyl/feedprops.c delete mode 100644 src/plugins/rssyl/feedprops.h create mode 100644 src/plugins/rssyl/libfeed/Makefile.am rename src/plugins/rssyl/{ => libfeed}/date.c (76%) rename src/plugins/rssyl/{ => libfeed}/date.h (82%) create mode 100644 src/plugins/rssyl/libfeed/feed.c create mode 100644 src/plugins/rssyl/libfeed/feed.h create mode 100644 src/plugins/rssyl/libfeed/feeditem.c create mode 100644 src/plugins/rssyl/libfeed/feeditem.h create mode 100644 src/plugins/rssyl/libfeed/feeditemenclosure.c create mode 100644 src/plugins/rssyl/libfeed/feeditemenclosure.h create mode 100644 src/plugins/rssyl/libfeed/parser.c create mode 100644 src/plugins/rssyl/libfeed/parser.h create mode 100644 src/plugins/rssyl/libfeed/parser_atom10.c create mode 100644 src/plugins/rssyl/libfeed/parser_atom10.h create mode 100644 src/plugins/rssyl/libfeed/parser_opml.c create mode 100644 src/plugins/rssyl/libfeed/parser_opml.h create mode 100644 src/plugins/rssyl/libfeed/parser_rdf.c create mode 100644 src/plugins/rssyl/libfeed/parser_rdf.h create mode 100644 src/plugins/rssyl/libfeed/parser_rss20.c create mode 100644 src/plugins/rssyl/libfeed/parser_rss20.h create mode 100644 src/plugins/rssyl/old_feeds.c create mode 100644 src/plugins/rssyl/old_feeds.h rename src/plugins/rssyl/{opml.c => opml_export.c} (50%) rename src/plugins/rssyl/{opml.h => opml_export.h} (64%) create mode 100644 src/plugins/rssyl/opml_import.c create mode 100644 src/plugins/rssyl/opml_import.h create mode 100644 src/plugins/rssyl/parse822.c create mode 100644 src/plugins/rssyl/parse822.h delete mode 100644 src/plugins/rssyl/parsers.c delete mode 100644 src/plugins/rssyl/parsers.h create mode 100644 src/plugins/rssyl/rssyl_add_item.c create mode 100644 src/plugins/rssyl/rssyl_add_item.h delete mode 100644 src/plugins/rssyl/rssyl_cb_gtk.c delete mode 100644 src/plugins/rssyl/rssyl_cb_gtk.h create mode 100644 src/plugins/rssyl/rssyl_deleted.c create mode 100644 src/plugins/rssyl/rssyl_deleted.h create mode 100644 src/plugins/rssyl/rssyl_feed.c create mode 100644 src/plugins/rssyl/rssyl_feed.h create mode 100644 src/plugins/rssyl/rssyl_feed_props.c create mode 100644 src/plugins/rssyl/rssyl_feed_props.h create mode 100644 src/plugins/rssyl/rssyl_parse_feed.c create mode 100644 src/plugins/rssyl/rssyl_parse_feed.h create mode 100644 src/plugins/rssyl/rssyl_subscribe.c create mode 100644 src/plugins/rssyl/rssyl_subscribe.h create mode 100644 src/plugins/rssyl/rssyl_subscribe_gtk.c create mode 100644 src/plugins/rssyl/rssyl_subscribe_gtk.h create mode 100644 src/plugins/rssyl/rssyl_update_comments.c create mode 100644 src/plugins/rssyl/rssyl_update_comments.h create mode 100644 src/plugins/rssyl/rssyl_update_feed.c create mode 100644 src/plugins/rssyl/rssyl_update_feed.h create mode 100644 src/plugins/rssyl/rssyl_update_format.c create mode 100644 src/plugins/rssyl/rssyl_update_format.h delete mode 100644 src/plugins/rssyl/strreplace.c delete mode 100644 src/plugins/rssyl/strreplace.h create mode 100644 src/plugins/rssyl/strutils.c create mode 100644 src/plugins/rssyl/strutils.h diff --git a/configure.ac b/configure.ac index 8b7c47a12..612b555c8 100644 --- a/configure.ac +++ b/configure.ac @@ -1056,7 +1056,7 @@ dnl PGP/Mime: pgpcore libgpgme dnl PGP/Inline: pgpcore libgpgme dnl S/Mime: pgpcore libgpgme dnl Python: Python -dnl RSSyl: libxml2 libcurl +dnl RSSyl: expat libcurl dnl SpamReport: libcurl dnl vCalendar: libcurl @@ -1065,10 +1065,15 @@ PKG_CHECK_MODULES(CURL, libcurl, HAVE_CURL=yes, HAVE_CURL=no) AC_SUBST(CURL_LIBS) AC_SUBST(CURL_CFLAGS) -dnl libxml2 ******************************************************************** -PKG_CHECK_MODULES(LIBXML, libxml-2.0, HAVE_LIBXML=yes, HAVE_LIBXML=no) -AC_SUBST(LIBXML_LIBS) -AC_SUBST(LIBXML_CFLAGS) +dnl expat ********************************************************************** +HAVE_EXPAT=no +AC_CHECK_HEADER(expat.h, [expat_header=yes], []) +AC_CHECK_LIB(expat, XML_ParserCreate, [expat_lib=yes], []) +if test x"$expat_header" = xyes -a x"$expat_lib"=xyes; then + HAVE_EXPAT=yes + AC_DEFINE(HAVE_EXPAT, 1, [Define if expat is available]) + EXPAT_LIBS="-lexpat" +fi dnl webkit ********************************************************************* PKG_CHECK_MODULES(WEBKIT, webkit-1.0 >= 1.1.14, HAVE_WEBKIT=yes, HAVE_WEBKIT=no) @@ -1722,8 +1727,8 @@ AC_MSG_CHECKING([whether to build rssyl plugin]) if test x"$enable_rssyl_plugin" != xno; then dependencies_missing="" - if test x"$HAVE_LIBXML" = xno; then - dependencies_missing="libxml2 $dependencies_missing" + if test x"$HAVE_EXPAT" = xno; then + dependencies_missing="expat $dependencies_missing" fi if test x"$HAVE_CURL" = xno; then dependencies_missing="libcurl $dependencies_missing" @@ -1924,6 +1929,7 @@ src/plugins/pgpcore/Makefile src/plugins/pgpmime/Makefile src/plugins/pgpinline/Makefile src/plugins/rssyl/Makefile +src/plugins/rssyl/libfeed/Makefile src/plugins/smime/Makefile src/plugins/spamassassin/Makefile src/plugins/spam_report/Makefile diff --git a/po/POTFILES.in b/po/POTFILES.in index 3ebe982e1..31cd537c7 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -164,14 +164,23 @@ src/plugins/python/claws-mail-python.metainfo.xml.in src/plugins/python/composewindowtype.c src/plugins/python/python_plugin.c src/plugins/rssyl/claws-mail-rssyl.metainfo.xml.in -src/plugins/rssyl/feed.c -src/plugins/rssyl/feed.h +src/plugins/rssyl/old_feeds.c +src/plugins/rssyl/opml_import.c src/plugins/rssyl/plugin.c +src/plugins/rssyl/rssyl_add_item.c src/plugins/rssyl/rssyl.c src/plugins/rssyl/rssyl_cb_menu.c +src/plugins/rssyl/rssyl_feed.c +src/plugins/rssyl/rssyl_feed.h +src/plugins/rssyl/rssyl_feed_props.c src/plugins/rssyl/rssyl_gtk.c src/plugins/rssyl/rssyl.h src/plugins/rssyl/rssyl_prefs.c +src/plugins/rssyl/rssyl_subscribe.c +src/plugins/rssyl/rssyl_subscribe_gtk.c +src/plugins/rssyl/rssyl_update_comments.c +src/plugins/rssyl/rssyl_update_feed.c +src/plugins/rssyl/rssyl_update_format.c src/plugins/smime/claws-mail-smime.metainfo.xml.in src/plugins/smime/plugin.c src/plugins/smime/smime.c diff --git a/src/plugins/rssyl/Makefile.am b/src/plugins/rssyl/Makefile.am index 9eaf5742e..949efd323 100644 --- a/src/plugins/rssyl/Makefile.am +++ b/src/plugins/rssyl/Makefile.am @@ -2,6 +2,8 @@ # This file is part of Claws Mail package. # See COPYING file for license details. +SUBDIRS = libfeed + appdata_in_files = claws-mail-rssyl.metainfo.xml.in appdatadir=$(datadir)/appdata appdata_DATA = $(appdata_in_files:.xml.in=.xml) @@ -68,29 +70,35 @@ rssyl_la_LDFLAGS = \ -avoid-version -module \ $(GTK_LIBS) -rssyl_la_DEPENDENCIES = $(plugin_deps) +rssyl_la_DEPENDENCIES = $(plugin_deps) libfeed/libfeed.la rssyl_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \ - $(GTK_LIBS) $(LIBXML_LIBS) $(CURL_LIBS) + $(GTK_LIBS) $(CURL_LIBS) libfeed/libfeed.la rssyl_la_CPPFLAGS = \ $(IFLAGS) \ $(GLIB_CFLAGS) \ $(GTK_CFLAGS) \ - $(LIBXML_CFLAGS) \ $(CURL_CFLAGS) rssyl_la_SOURCES = \ - date.c date.h \ - feed.c feed.h \ - feedprops.c feedprops.h \ - opml.c opml.h \ - parsers.c parsers.h \ + old_feeds.c old_feeds.h \ + opml_export.c opml_export.h \ + opml_import.c opml_import.h \ + parse822.c parse822.h \ plugin.c \ rssyl.c rssyl.h \ - rssyl_cb_gtk.c rssyl_cb_gtk.h \ + rssyl_add_item.c rssyl_add_item.h \ rssyl_cb_menu.c rssyl_cb_menu.h \ + rssyl_deleted.c rssyl_deleted.h \ + rssyl_feed.c rssyl_feed.h \ + rssyl_feed_props.c rssyl_feed_props.h \ rssyl_gtk.c rssyl_gtk.h \ + rssyl_parse_feed.c rssyl_parse_feed.h \ rssyl_prefs.c rssyl_prefs.h \ - strreplace.c strreplace.h - + rssyl_update_comments.c rssyl_update_comments.h \ + rssyl_update_feed.c rssyl_update_feed.h \ + rssyl_update_format.c rssyl_update_format.h \ + rssyl_subscribe.c rssyl_subscribe.h \ + rssyl_subscribe_gtk.c rssyl_subscribe_gtk.h \ + strutils.c strutils.h diff --git a/src/plugins/rssyl/feed.c b/src/plugins/rssyl/feed.c deleted file mode 100644 index 8542bc7f6..000000000 --- a/src/plugins/rssyl/feed.c +++ /dev/null @@ -1,1869 +0,0 @@ -/* - * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client - * Copyright (C) 1999-2004 Hiroyuki Yamamoto - * This file (C) 2005-2008 Andrej Kacian and the Claws Mail team - * - * - various feed parsing functions - * - this file could use some sorting and/or splitting - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#include "claws-features.h" -#endif - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/claws.h" -#include "common/version.h" -#include "codeconv.h" -#include "procmsg.h" -#include "procheader.h" -#include "alertpanel.h" -#include "folder.h" -#include "mainwindow.h" -#include "statusbar.h" -#include "log.h" -#include "prefs_common.h" -#include "defs.h" -#include "inc.h" -#include "common/utils.h" -#include "main.h" - -#include "date.h" -#include "rssyl.h" -#include "rssyl_cb_gtk.h" -#include "feed.h" -#include "feedprops.h" -#include "strreplace.h" -#include "parsers.h" -#include "rssyl_prefs.h" - -static int rssyl_curl_progress_function(void *clientp, - double dltotal, double dlnow, double ultotal, double ulnow) -{ - if (claws_is_exiting()) { - debug_print("RSSyl: curl_progress_function bailing out, app is exiting\n"); - return 1; - } - - return 0; -} - -struct _RSSylThreadCtx { - const gchar *url; - time_t last_update; - gboolean not_modified; - gboolean defer_modified_check; - gboolean ready; - gchar *error; - gboolean ssl_verify_peer; -}; - -typedef struct _RSSylThreadCtx RSSylThreadCtx; - -static void *rssyl_fetch_feed_threaded(void *arg) -{ - RSSylThreadCtx *ctx = (RSSylThreadCtx *)arg; - CURL *eh = NULL; - CURLcode res; - time_t last_modified; - gchar *time_str = NULL; - long response_code; - -#ifndef G_OS_WIN32 - gchar *template = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, RSSYL_TMP_TEMPLATE, NULL); - int fd = mkstemp(template); -#else - gchar *template = get_tmp_file(); - int fd = open(template, (O_CREAT|O_RDWR|O_BINARY), (S_IRUSR|S_IWUSR)); -#endif - FILE *f; - -#ifdef USE_PTHREAD - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); -#endif - - if (fd == -1) { - perror("mkstemp"); - ctx->ready = TRUE; - g_free(template); - return NULL; - } - - f = fdopen(fd, "w"); - if (f == NULL) { - perror("fdopen"); - ctx->error = g_strdup(_("Cannot open temporary file")); - claws_unlink(template); - g_free(template); - ctx->ready = TRUE; - return NULL; - } - - eh = curl_easy_init(); - - if (eh == NULL) { - g_warning("can't init curl"); - ctx->error = g_strdup(_("Cannot init libCURL")); - fclose(f); - claws_unlink(template); - g_free(template); - ctx->ready = TRUE; - return NULL; - } - - debug_print("TEMPLATE: %s\n", template); - - curl_easy_setopt(eh, CURLOPT_URL, ctx->url); - curl_easy_setopt(eh, CURLOPT_NOPROGRESS, 0); - curl_easy_setopt(eh, CURLOPT_PROGRESSFUNCTION, rssyl_curl_progress_function); -#if LIBCURL_VERSION_NUM < 0x071000 - curl_easy_setopt(eh, CURLOPT_MUTE, 1); -#endif - curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, NULL); - curl_easy_setopt(eh, CURLOPT_WRITEDATA, f); - curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(eh, CURLOPT_MAXREDIRS, 3); - curl_easy_setopt(eh, CURLOPT_TIMEOUT, prefs_common_get_prefs()->io_timeout_secs); - curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(eh, CURLOPT_ENCODING, ""); -#if LIBCURL_VERSION_NUM >= 0x070a00 - if(ctx->ssl_verify_peer == FALSE) { - curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0); - } -#endif - curl_easy_setopt(eh, CURLOPT_USERAGENT, - "Claws Mail RSSyl plugin "VERSION - " (" PLUGINS_URI ")"); -#ifdef RSSYL_DEBUG - curl_easy_setopt(eh, CURLOPT_VERBOSE, 1); -#endif - curl_easy_setopt(eh, CURLOPT_COOKIEFILE, - rssyl_prefs_get()->cookies_path); - - if( !ctx->defer_modified_check ) { - if( ctx->last_update != -1 ) { - time_str = createRFC822Date(&ctx->last_update); - } - debug_print("RSSyl: last update %ld (%s)\n", - (long int)ctx->last_update, - (ctx->last_update != -1 ? time_str : "unknown") ); - g_free(time_str); - time_str = NULL; - if( ctx->last_update != -1 ) { - curl_easy_setopt(eh, CURLOPT_TIMECONDITION, - CURL_TIMECOND_IFMODSINCE); - curl_easy_setopt(eh, CURLOPT_TIMEVALUE, ctx->last_update); - } - } - - res = curl_easy_perform(eh); - - if (res != 0) { - if (res == CURLE_OPERATION_TIMEOUTED) { - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_TIMEOUT, ctx->url); - } else if (res == CURLE_ABORTED_BY_CALLBACK) { - log_print(LOG_PROTOCOL, RSSYL_LOG_EXITING); - } - ctx->error = g_strdup(curl_easy_strerror(res)); - ctx->ready = TRUE; - curl_easy_cleanup(eh); - fclose(f); - claws_unlink(template); - g_free(template); - return NULL; - } - curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &response_code); - - if( !ctx->defer_modified_check ) { - if( ctx->last_update != -1 ) { - curl_easy_getinfo(eh, CURLINFO_FILETIME, &last_modified); - - if( last_modified != -1 ) { - time_str = createRFC822Date(&last_modified); - } - debug_print("RSSyl: got status %ld, last mod %ld (%s)\n", - response_code, (long int) last_modified, - (last_modified != -1 ? time_str : "unknown") ); - g_free(time_str); - time_str = NULL; - } else { - debug_print("RSSyl: got status %ld\n", response_code); - } - } - - curl_easy_cleanup(eh); - - fclose(f); - - if( response_code >= 400 && response_code < 500 ) { - debug_print("RSSyl: got %ld\n", response_code); - switch(response_code) { - case 401: - ctx->error = g_strdup(_("401 (Authorisation required)")); - break; - case 403: - ctx->error = g_strdup(_("403 (Unauthorised)")); - break; - case 404: - ctx->error = g_strdup(_("404 (Not found)")); - break; - default: - ctx->error = g_strdup_printf(_("Error %ld"), response_code); - break; - } - ctx->ready = TRUE; - claws_unlink(template); - g_free(template); - return NULL; - } - - if( !ctx->defer_modified_check ) { - if( response_code == 304 ) { - debug_print("RSSyl: don't rely on server response 304, defer modified " - "check\n"); - claws_unlink(template); - g_free(template); - ctx->defer_modified_check = TRUE; - template = rssyl_fetch_feed_threaded(ctx); - return template; - } - } - ctx->ready = TRUE; - - return template; -} - -gchar *rssyl_feed_title_to_dir(const gchar *title) -{ -#ifndef G_OS_WIN32 - return rssyl_strreplace(title, "/", "\\"); -#else - gchar *patterns[] = { "/", "\\", ":", "*", "?" , "\"", "<", ">", "|" }; - gchar *sanitized = g_strdup(title); - int i; - - for (i = 0; i < sizeof(patterns)/sizeof(patterns[0]); i++) { - gchar *tmp = rssyl_strreplace(sanitized, patterns[i], "-"); - g_free(sanitized); - sanitized = tmp; - } - - return sanitized; -#endif -} - -/* rssyl_fetch_feed() - * - * This function utilizes libcurl's easy interface to fetch the feed, pre-parse - * it for title, create a directory based on the title. It returns a xmlDocPtr - * for libxml2 to parse further. - */ -xmlDocPtr rssyl_fetch_feed(const gchar *url, time_t last_update, gboolean ssl_verify_peer, gchar **title, gchar **error) { - gchar *xpath, *rootnode, *dir; - xmlDocPtr doc; - xmlNodePtr node, n, rnode; - xmlXPathContextPtr context; - xmlXPathObjectPtr result; - MainWindow *mainwin = mainwindow_get_mainwindow(); - RSSylThreadCtx *ctx = g_new0(RSSylThreadCtx, 1); - gchar *template = NULL; - gboolean defer_modified_check = FALSE; -#ifdef RSSYL_DEBUG - gchar *unixtime_str = NULL, *debugfname = NULL; -#endif /* RSSYL_DEBUG */ - -#ifdef USE_PTHREAD - pthread_t pt; - pthread_attr_t pta; -#endif - gchar *msg = NULL, *tmptitle = NULL; - gchar *content; - xmlErrorPtr xml_err; - - ctx->url = url; - ctx->ready = FALSE; - ctx->last_update = last_update; - ctx->not_modified = FALSE; - ctx->defer_modified_check = FALSE; - ctx->ssl_verify_peer = ssl_verify_peer; - - *title = NULL; - - g_return_val_if_fail(url != NULL, NULL); - - debug_print("RSSyl: XML - url is '%s'\n", url); - - msg = g_strdup_printf(_("Fetching '%s'..."), url); - STATUSBAR_PUSH(mainwin, msg ); - g_free(msg); - - GTK_EVENTS_FLUSH(); - -#ifdef USE_PTHREAD - if (pthread_attr_init(&pta) != 0 || - pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_JOINABLE) != 0 || - pthread_create(&pt, &pta, rssyl_fetch_feed_threaded, - (void *)ctx) != 0 ) { - /* Bummer, couldn't create thread. Continue non-threaded */ - template = rssyl_fetch_feed_threaded(ctx); - } else { - /* Thread created, let's wait until it finishes */ - debug_print("RSSyl: waiting for thread to finish\n"); - while( !ctx->ready ) { - claws_do_idle(); - } - - debug_print("RSSyl: thread finished\n"); - pthread_join(pt, (void *)&template); - } -#else - debug_print("RSSyl: no pthreads, run blocking fetch\n"); - template = rssyl_fetch_feed_threaded(ctx); -#endif - - defer_modified_check = ctx->defer_modified_check; - - if (error) - *error = ctx->error; - - g_free(ctx); - STATUSBAR_POP(mainwin); - - if( template == NULL ) { - debug_print("RSSyl: no feed to parse, returning\n"); - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_FETCH, url); - return NULL; - } - - /* Strip ugly \r\n endlines */ -#ifndef G_OS_WIN32 - file_strip_crs((gchar *)template); -#endif - debug_print("parsing %s\n", template); - doc = xmlParseFile(template); - - if( doc == NULL ) { - claws_unlink(template); - g_free(template); - xml_err = xmlGetLastError(); - if (xml_err) - debug_print("error %s\n", xml_err->message); - - g_warning("Couldn't fetch feed '%s', aborting.\n", url); - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_FETCH, url); - if (error && !(*error)) { - *error = g_strdup(_("Malformed feed")); - } - return NULL; - } - - node = xmlDocGetRootElement(doc); - rnode = node; - -#ifdef RSSYL_DEBUG - /* debug mode - get timestamp, add it to returned xmlDoc, and make a copy - * of the fetched feed file */ - tmptitle = rssyl_feed_title_to_dir(url); - unixtime_str = g_strdup_printf("%ld", time(NULL) ); - debugfname = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, ".", tmptitle, ".", unixtime_str, NULL); - - debug_print("Storing fetched feed in file '%s' for debug purposes.\n", - debugfname); - link(template, debugfname); - - debug_print("Adding 'fetched' property to root node: %s\n", unixtime_str); - xmlSetProp(rnode, "fetched", unixtime_str); - g_free(unixtime_str); - g_free(debugfname); - g_free(tmptitle); -#endif /* RSSYL_DEBUG */ - - claws_unlink(template); - g_free(template); - - debug_print("RSSyl: XML - root node is '%s'\n", node->name); - - rootnode = g_ascii_strdown(node->name, -1); - - if( !xmlStrcmp(rootnode, "rss") ) { - context = xmlXPathNewContext(doc); - xpath = g_strconcat("/", node->name, "/channel/title", NULL); - debug_print("RSSyl: XML - '%s'\n", xpath); - if( !(result = xmlXPathEvalExpression(xpath, context)) ) { - debug_print("RSSyl: XML - no result found for '%s'\n", xpath); - xmlXPathFreeContext(context); - g_free(rootnode); - g_free(xpath); - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PARSE, url); - return NULL; - } - - if( xmlXPathNodeSetIsEmpty(result->nodesetval) ) { - debug_print("RSSyl: XML - nodeset empty for '%s'\n", xpath); - g_free(rootnode); - g_free(xpath); - xmlXPathFreeObject(result); - xmlXPathFreeContext(context); - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PARSE, url); - return NULL; - } - g_free(xpath); - - xmlXPathFreeContext(context); - node = result->nodesetval->nodeTab[0]; - xmlXPathFreeObject(result); - content = xmlNodeGetContent(node); - debug_print("RSSyl: XML - title is '%s'\n", content ); - *title = g_strdup(content); - xmlFree(content); - debug_print("RSSyl: XML - our title is '%s'\n", *title); - - /* use the feed's pubDate to determine if it's modified */ - if( defer_modified_check ) { - time_t pub_date; - - context = xmlXPathNewContext(doc); - node = rnode; - xpath = g_strconcat("/", node->name, "/channel/pubDate", NULL); - debug_print("RSSyl: XML - '%s'\n", xpath); - if( !(result = xmlXPathEvalExpression(xpath, context)) ) { - debug_print("RSSyl: XML - no result found for '%s'\n", xpath); - xmlXPathFreeContext(context); - g_free(rootnode); - g_free(xpath); - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PARSE, url); - return NULL; - } - - if( xmlXPathNodeSetIsEmpty(result->nodesetval) ) { - debug_print("RSSyl: XML - nodeset empty for '%s', using current time\n", - xpath); - pub_date = time(NULL); - } else { - node = result->nodesetval->nodeTab[0]; - content = xmlNodeGetContent(node); - pub_date = procheader_date_parse(NULL, content, 0); - debug_print("RSSyl: XML - pubDate is '%s'\n", content); - xmlFree(content); - } - - xmlXPathFreeObject(result); - xmlXPathFreeContext(context); - g_free(xpath); - - /* check date validity and perform postponed modified check */ - if( pub_date > 0 ) { - gchar *time_str = NULL; - - time_str = createRFC822Date(&pub_date); - debug_print("RSSyl: XML - item date found: %ld (%s)\n", - (long int) pub_date, time_str ? time_str : "unknown"); - if( !time_str || ( pub_date < last_update && last_update > 0) ) { - if( !time_str) { - debug_print("RSSyl: XML - invalid item date\n"); - } else { - debug_print("RSSyl: XML - no update needed\n"); - } - g_free(time_str); - time_str = NULL; - g_free(rootnode); - return NULL; - } - g_free(time_str); - time_str = NULL; - } else { - debug_print("RSSyl: XML - item date not found\n"); - g_free(rootnode); - return NULL; - } - } - - } else if( !xmlStrcmp(rootnode, "rdf") ) { - node = node->children; - /* find "channel" */ - while( node && xmlStrcmp(node->name, "channel") ) - node = node->next; - /* now find "title" */ - for( n = node->children; n; n = n->next ) { - if( !xmlStrcmp(n->name, "title") ) { - content = xmlNodeGetContent(n); - *title = g_strdup(content); - xmlFree(content); - debug_print("RSSyl: XML - RDF our title is '%s'\n", *title); - } - } - } else if( !xmlStrcmp(rootnode, "feed") ) { - /* find "title" */ - for( n = node->children; n; n = n->next ) { - if( !xmlStrcmp(n->name, "title") ) { - content = xmlNodeGetContent(n); - *title = g_strdup(content); - xmlFree(content); - debug_print("RSSyl: XML - FEED our title is '%s'\n", *title); - } - } - } else { - log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_UNKNOWN, url); - g_free(rootnode); - return NULL; - } - - g_return_val_if_fail(*title != NULL, NULL); - - if (*title[0] == '\0') { - g_free(*title); - *title = g_strdup(url); - subst_for_shellsafe_filename(*title); - } - - tmptitle = rssyl_feed_title_to_dir(*title); - dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, tmptitle, NULL); - g_free(tmptitle); - - if( !is_dir_exist(dir) ) { - if( make_dir(dir) < 0 ) { - g_warning("couldn't create directory %s\n", dir); - g_free(rootnode); - g_free(dir); - return NULL; - } - } - - g_free(rootnode); - g_free(dir); - - return doc; -} - -typedef struct _RSSyl_HTMLSymbol RSSyl_HTMLSymbol; -struct _RSSyl_HTMLSymbol -{ - gchar *const key; - gchar *const val; -}; - -static RSSyl_HTMLSymbol symbol_list[] = { - { "<", "<" }, - { ">", ">" }, - { "&", "&" }, - { """, "\"" }, - { "‘", "'" }, - { "’", "'" }, - { "“", "\"" }, - { "”", "\"" }, - { " ", " " }, - { "™", "(TM)" }, - { "™", "(TM)" }, - { "'", "'" }, - { "…", "..." }, - { "—", "-" }, - { "", "\"" }, - { "", "\"" }, - { "", "" }, - { "", "" }, - { "", "" }, - { "", ""}, - { "", "" }, - { "", "" }, - { "", "" }, - { "", "" }, - { "", "" }, - { NULL, NULL }, -}; - -static gchar *rssyl_replace_html_symbols(const gchar *text) -{ - gchar *tmp = NULL, *wtext = g_strdup(text); - gint i; - - for( i = 0; symbol_list[i].key != NULL; i++ ) { - if( g_strstr_len(text, strlen(text), symbol_list[i].key) ) { - tmp = rssyl_strreplace(wtext, symbol_list[i].key, symbol_list[i].val); - wtext = g_strdup(tmp); - g_free(tmp); - } - } - - return wtext; -} - -gchar *rssyl_format_string(const gchar *str, gboolean replace_html, gboolean replace_returns) -{ - gchar *res = NULL; - gchar *tmp = NULL; - - g_return_val_if_fail(str != NULL, NULL); - - if (replace_html) - tmp = rssyl_replace_html_symbols(str); - else - tmp = g_strdup(str); - - res = rssyl_sanitize_string(tmp, replace_returns); - g_free(tmp); - - g_strstrip(res); - - return res; -} - -/* this function splits a string into an array of string, by - * returning an array of pointers to positions of the delimiter - * in the original string and replacing this delimiter with a - * NULL. It does not duplicate memory, hence you should only - * free the array and not its elements, and you should not - * free the original string before you're done with the array. - * maybe could be part of the core (utils.c). - */ -static gchar **strplit_no_copy(gchar *str, char delimiter) -{ - gchar **array = g_new(gchar *, 1); - int i = 0; - gchar *cur = str, *next; - - array[i] = cur; - i++; - while ((next = strchr(cur, delimiter)) != NULL) { - *(next) = '\0'; - array = g_realloc(array, (sizeof(gchar *)) * (i + 1)); - array[i] = next + 1; - cur = next + 1; - i++; - } - array = g_realloc(array, (sizeof(gchar *)) * (i + 1)); - array[i] = NULL; - return array; -} - -/* rssyl_parse_folder_item_file() - * - * Parse the RFC822-formatted feed item given by "path", and returns a - * pointer to a RSSylFeedItem struct, which contains all required data. - */ -static RSSylFeedItem *rssyl_parse_folder_item_file(gchar *dir_path, gchar *filename) -{ - gchar *contents, **lines, **line, **splid; - GError *error = NULL; - RSSylFeedItem *fitem; - gint i = 0; - gboolean parsing_headers, past_html_tag, past_endhtml_tag; - gboolean started_author = FALSE, started_subject = FALSE; - gboolean started_link = FALSE, started_clink = FALSE, started_plink = FALSE; - gchar *full_path = g_strconcat(dir_path, G_DIR_SEPARATOR_S, filename, NULL); - debug_print("RSSyl: parsing '%s'\n", full_path); - - g_file_get_contents(full_path, &contents, NULL, &error); - - if( error ) { - g_warning("GError: '%s'\n", error->message); - g_error_free(error); - error = NULL; - } - - if( contents ) { - lines = strplit_no_copy(contents, '\n'); - } else { - g_warning("Badly formatted file found, ignoring: '%s'\n", full_path); - g_free(contents); - return NULL; - } - - fitem = g_new0(RSSylFeedItem, 1); /* free that */ - fitem->date = 0; - fitem->date_published = 0; - fitem->link = NULL; - fitem->text = NULL; - fitem->id = NULL; - fitem->id_is_permalink = FALSE; - fitem->realpath = g_strdup(full_path); - - g_free(full_path); - - parsing_headers = TRUE; - past_html_tag = FALSE; - past_endhtml_tag = FALSE; - while(lines[i] ) { - if( parsing_headers && lines[i] && !strlen(lines[i]) && fitem->link ) { - parsing_headers = FALSE; - debug_print("RSSyl: finished parsing headers\n"); - } - - if( parsing_headers ) { - line = g_strsplit(lines[i], ": ", 2); - if( line[0] && line[1] && strlen(line[0]) && lines[i][0] != ' ') { - started_author = FALSE; - started_subject = FALSE; - started_link = FALSE; - started_clink = FALSE; - started_plink = FALSE; - - /* Author */ - if( !strcmp(line[0], "From") ) { - fitem->author = g_strdup(line[1]); - debug_print("RSSyl: got author '%s'\n", fitem->author); - started_author = TRUE; - } - - /* Date */ - if( !strcmp(line[0], "Date") ) { - fitem->date = procheader_date_parse(NULL, line[1], 0); - debug_print("RSSyl: got date \n" ); - } - - /* Title */ - if( !strcmp(line[0], "Subject") ) { - fitem->title = g_strdup(line[1]); - debug_print("RSSyl: got title '%s'\n", fitem->title); - started_subject = TRUE; - } - - /* Link */ - if( !strcmp(line[0], "X-RSSyl-URL") ) { - fitem->link = g_strdup(line[1]); - debug_print("RSSyl: got link '%s'\n", fitem->link); - started_link = TRUE; - } - - /* ID */ - if( !strcmp(line[0], "Message-ID") ) { - splid = g_strsplit_set(line[1], "<>", 3); - if( strlen(splid[1]) != 0 ) { - fitem->id = g_strdup(splid[1]); - debug_print("RSSyl: got id '%s'\n", fitem->id); - } - g_strfreev(splid); - } - - if( !strcmp(line[0], "X-RSSyl-Comments") ) { - fitem->comments_link = g_strdup(line[1]); - debug_print("RSSyl: got clink '%s'\n", fitem->comments_link); - started_clink = TRUE; - } - if( !strcmp(line[0], "X-RSSyl-Parent") ) { - fitem->parent_link = g_strdup(line[1]); - debug_print("RSSyl: got plink '%s'\n", fitem->parent_link); - started_plink = TRUE; - } - } else if (lines[i][0] == ' ') { - gchar *tmp = NULL; - /* continuation line */ - if (started_author) { - tmp = g_strdup_printf("%s %s", fitem->author, lines[i]+1); - g_free(fitem->author); - fitem->author = tmp; - debug_print("RSSyl: updated author to '%s'\n", fitem->author); - } else if (started_subject) { - tmp = g_strdup_printf("%s %s", fitem->title, lines[i]+1); - g_free(fitem->title); - fitem->title = tmp; - debug_print("RSSyl: updated title to '%s'\n", fitem->title); - } else if (started_link) { - tmp = g_strdup_printf("%s%s", fitem->link, lines[i]+1); - g_free(fitem->link); - fitem->link = tmp; - debug_print("RSSyl: updated link to '%s'\n", fitem->link); - } else if (started_clink) { - tmp = g_strdup_printf("%s%s", fitem->comments_link, lines[i]+1); - g_free(fitem->comments_link); - fitem->comments_link = tmp; - debug_print("RSSyl: updated comments_link to '%s'\n", fitem->comments_link); - } else if (started_plink) { - tmp = g_strdup_printf("%s%s", fitem->parent_link, lines[i]+1); - g_free(fitem->parent_link); - fitem->parent_link = tmp; - debug_print("RSSyl: updated comments_link to '%s'\n", fitem->parent_link); - } - } - g_strfreev(line); - } else { - if( !strcmp(lines[i], RSSYL_TEXT_START) ) { - debug_print("Leading html tag found at line %d\n", i); - past_html_tag = TRUE; - i++; - continue; - } - while( past_html_tag && !past_endhtml_tag && lines[i] ) { - if( !strcmp(lines[i], RSSYL_TEXT_END) ) { - debug_print("Trailing html tag found at line %d\n", i); - past_endhtml_tag = TRUE; - i++; - continue; - } - if( fitem->text != NULL ) { - gint e_len, n_len; - e_len = strlen(fitem->text); - n_len = strlen(lines[i]); - fitem->text = g_realloc(fitem->text, e_len + n_len + 2); - *(fitem->text+e_len) = '\n'; - strcpy(fitem->text+e_len+1, lines[i]); - *(fitem->text+e_len+n_len+1) = '\0'; - } else { - fitem->text = g_strdup(lines[i]); - } - i++; - } - - if( lines[i] == NULL ) - return fitem; - } - - i++; - } - g_free(lines); - g_free(contents); - return fitem; -} - -/* rssyl_free_feeditem() - * frees an RSSylFeedItem - */ -void rssyl_free_feeditem(RSSylFeedItem *item) -{ - if (!item) - return; - g_free(item->title); - item->title = NULL; - g_free(item->text); - item->text = NULL; - g_free(item->link); - item->link = NULL; - g_free(item->id); - item->id = NULL; - g_free(item->comments_link); - item->comments_link = NULL; - g_free(item->parent_link); - item->parent_link = NULL; - g_free(item->author); - item->author = NULL; - g_free(item->realpath); - item->realpath = NULL; - if( item->media != NULL ) { - g_free(item->media->url); - g_free(item->media->type); - g_free(item->media); - } - g_free(item); -} - -static void *rssyl_read_existing_thr(void *arg) -{ - RSSylParseCtx *ctx = (RSSylParseCtx *)arg; - RSSylFolderItem *ritem = ctx->ritem; - FolderItem *item = &ritem->item; - RSSylFeedItem *fitem = NULL; - DIR *dp; - struct dirent *d; - gint num; - gchar *path; - - debug_print("RSSyl: rssyl_read_existing_thr()\n"); - - path = folder_item_get_path(item); - if( !path ) { - debug_print("RSSyl: read_existing - path is NULL, bailing out\n"); - ctx->ready = TRUE; - return NULL; - } - - /* create a new GSList, throw away the old one */ - if( ritem->contents != NULL ) { - GSList *cur; - for (cur = ritem->contents; cur; cur = cur->next) { - RSSylFeedItem *olditem = (RSSylFeedItem *)cur->data; - rssyl_free_feeditem(olditem); - } - g_slist_free(ritem->contents); /* leak fix here */ - ritem->contents = NULL; - } - ritem->contents = g_slist_alloc(); - - if( change_dir(path) < 0 ) { - g_free(path); - return NULL; - } - - if( (dp = opendir(".")) == NULL ) { - FILE_OP_ERROR(item->path, "opendir"); - g_free(path); - return NULL; - } - - while( (d = readdir(dp)) != NULL ) { - if (claws_is_exiting()) { - closedir(dp); - g_free(path); - debug_print("RSSyl: read_existing is bailing out, app is exiting\n"); - ctx->ready = TRUE; - return NULL; - } - if( (num = to_number(d->d_name)) > 0 && dirent_is_regular_file(d) ) { - debug_print("RSSyl: starting to parse '%s'\n", d->d_name); - if( (fitem = rssyl_parse_folder_item_file(path, d->d_name)) != NULL ) { - debug_print("Appending '%s'\n", fitem->title); - ritem->contents = g_slist_prepend(ritem->contents, fitem); - } - } - } - closedir(dp); - g_free(path); - - ritem->contents = g_slist_reverse(ritem->contents); - - ctx->ready = TRUE; - - debug_print("RSSyl: rssyl_read_existing_thr() is returning\n"); - return NULL; -} - -/* rssyl_read_existing() - * - * Parse all existing folder items, storing their data in memory. Data is - * later used for checking for duplicate entries. - * Of course, actual work is done in a separate thread (if available) in - * rssyl_read_existing_thr(). - */ -void rssyl_read_existing(RSSylFolderItem *ritem) -{ - RSSylParseCtx *ctx = NULL; -#ifdef USE_PTHREAD - pthread_t pt; -#endif - - g_return_if_fail(ritem != NULL); - - ctx = g_new0(RSSylParseCtx, 1); - ctx->ritem = ritem; - ctx->ready = FALSE; - -#ifdef USE_PTHREAD - if( pthread_create(&pt, PTHREAD_CREATE_JOINABLE, rssyl_read_existing_thr, - (void *)ctx) != 0 ) { - /* Couldn't create thread, continue nonthreaded */ - rssyl_read_existing_thr(ctx); - } else { - debug_print("RSSyl: waiting for read_existing thread to finish\n"); - while( !ctx->ready ) { - claws_do_idle(); - } - - debug_print("RSSyl: read_existing thread finished\n"); - pthread_join(pt, NULL); - } -#else - debug_print("RSSyl: pthreads not available, running read_existing non-threaded\n"); - rssyl_read_existing_thr(ctx); -#endif - - g_free(ctx); -} - -/* rssyl_cb_feed_compare() - * - * Callback compare function called by glib2's g_slist_find_custom(). - */ -static gint rssyl_cb_feed_compare(const RSSylFeedItem *a, - const RSSylFeedItem *b) -{ - gboolean date_publ_eq = FALSE, date_eq = FALSE; - gboolean link_eq = FALSE, title_eq = FALSE; - gboolean no_link = FALSE, no_title = FALSE; - gchar *atit = NULL, *btit = NULL; - - if( a == NULL || b == NULL ) - return 1; - - /* ID should be unique. If it matches, we've found what we came for. */ - if( (a->id != NULL) && (b->id != NULL) ) { - if( strcmp(a->id, b->id) == 0 ) - return 0; - - /* If both IDs are present, but they do not match, we need - * to look elsewhere. */ - return 1; - } - - /* Ok, we have no ID to aid us. Let's have a look at item timestamps, - * item link and title. */ - if( (a->link != NULL) && (b->link != NULL) ) { - if( strcmp(a->link, b->link) == 0 ) - link_eq = TRUE; - } else - no_link = TRUE; - - if( (a->title != NULL) && (b->title != NULL) ) { - atit = conv_unmime_header(a->title, CS_UTF_8, FALSE); - btit = conv_unmime_header(b->title, CS_UTF_8, FALSE); - if( strcmp(atit, btit) == 0 ) - title_eq = TRUE; - g_free(atit); - g_free(btit); - } else - no_title = TRUE; - - /* If there's no 'published' timestamp for the item, we can only judge - * by item link and title - 'modified' timestamp can have changed if the - * item was updated recently. */ - if( b->date_published <= 0 && b->date <= 0) { - if( link_eq && (title_eq || no_title) ) - return 0; - } - - if( ((a->date_published > 0) && (b->date_published > 0) && - (a->date_published == b->date_published)) ) { - date_publ_eq = TRUE; - } - - if( ((a->date > 0) && (b->date > 0) && - (a->date == b->date)) ) { - date_eq = TRUE; - } - - /* If 'published' time and item link match, it is reasonable to assume - * it's this item. */ - if( (link_eq || no_link) && (date_publ_eq || date_eq) ) - return 0; - - /* Last ditch effort - if everything else is missing, at least titles - * should match. */ - if( no_link && title_eq ) - return 0; - - /* We don't know this item. */ - return 1; -} - -enum { - ITEM_UNCHANGED, - ITEM_CHANGED_TEXTONLY, - ITEM_CHANGED -}; - -static gint rssyl_feed_item_changed(RSSylFeedItem *old_item, RSSylFeedItem *new_item) -{ - /* if both have title ... */ - if( old_item->title && new_item->title ) { - gchar *old = conv_unmime_header(old_item->title, CS_UTF_8, FALSE); - gchar *new = conv_unmime_header(new_item->title, CS_UTF_8, FALSE); - if( strcmp(old, new) ) { /* ... compare "unmimed" titles */ - g_free(old); - g_free(new); - debug_print("RSSyl: item titles differ\n"); - return ITEM_CHANGED; - } - g_free(old); - g_free(new); - } else { - /* if atleast one has a title, they differ */ - if( old_item->title || new_item->title ) { - debug_print("RSSyl: +/- title\n"); - return ITEM_CHANGED; - } - } - - /* if both have author ... */ - if( old_item->author && new_item->author ) { - gchar *old = conv_unmime_header(old_item->author, CS_UTF_8, TRUE); - gchar *new = conv_unmime_header(new_item->author, CS_UTF_8, TRUE); - if( strcmp(old, new) ) { /* ... compare "unmimed" authors */ - g_free(old); - g_free(new); - debug_print("RSSyl: item authors differ\n"); - return ITEM_CHANGED; - } - g_free(old); - g_free(new); - } else { - /* if atleast one has author, they differ */ - if( old_item->author || new_item->author ) { - debug_print("RSSyl: +/- author\n"); - return ITEM_CHANGED; - } - } - - /* if both have text ... */ - if( old_item->text && new_item->text ) { - if( strcmp(old_item->text, new_item->text) ) { /* ... compare texts */ - debug_print("RSSyl: item texts differ\n"); - return ITEM_CHANGED_TEXTONLY; - } - } else { - /* if atleast one has some text, they differ */ - if( old_item->text || new_item->text ) { - debug_print("RSSyl: +/- text\n"); - if ( !old_item->text ) - debug_print("RSSyl: old_item has no text\n"); - else - debug_print("RSSyl: new_item has no text\n"); - return ITEM_CHANGED_TEXTONLY; - } - } - - /* they don't seem to differ */ - return ITEM_UNCHANGED; -} - -/* rssyl_feed_item_exists() - * - * Returns 1 if a feed item already exists locally, 2 if there's a changed - * item with link that already belongs to existing item, 3 if only item's - * text has changed, 0 if item is new. - */ -enum { - EXISTS_NEW, - EXISTS_UNCHANGED, - EXISTS_CHANGED, - EXISTS_CHANGED_TEXTONLY -}; - -static guint rssyl_feed_item_exists(RSSylFolderItem *ritem, - RSSylFeedItem *fitem, RSSylFeedItem **oldfitem) -{ - gint changed; - GSList *item = NULL; - RSSylFeedItem *efitem = NULL; - g_return_val_if_fail(ritem != NULL, FALSE); - g_return_val_if_fail(fitem != NULL, FALSE); - - if( ritem->contents == NULL || g_slist_length(ritem->contents) == 0 ) - return 0; - - if( (item = g_slist_find_custom(ritem->contents, - (gconstpointer)fitem, (GCompareFunc)rssyl_cb_feed_compare)) ) { - efitem = (RSSylFeedItem *)item->data; - if( (changed = rssyl_feed_item_changed(efitem, fitem)) > EXISTS_NEW ) { - *oldfitem = efitem; - if (changed == ITEM_CHANGED_TEXTONLY) - return EXISTS_CHANGED_TEXTONLY; - else - return EXISTS_CHANGED; - } - return EXISTS_UNCHANGED; - } - - return EXISTS_NEW; -} - -void rssyl_parse_feed(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent) -{ - xmlNodePtr node; - gchar *rootnode; - MainWindow *mainwin = mainwindow_get_mainwindow(); - gint count; - gchar *msg; - - if (doc == NULL) - return; - - rssyl_read_existing(ritem); - - if (claws_is_exiting()) { - debug_print("RSSyl: parse_feed bailing out, app is exiting\n"); - return; - } - - node = xmlDocGetRootElement(doc); - - debug_print("RSSyl: XML - root node is '%s'\n", node->name); - rootnode = g_ascii_strdown(node->name, -1); - - msg = g_strdup_printf(_("Refreshing feed '%s'..."), - ritem->item.name); - STATUSBAR_PUSH(mainwin, msg ); - g_free(msg); - GTK_EVENTS_FLUSH(); - - folder_item_update_freeze(); - - /* we decide what parser to call, depending on what the root node is */ - if( !strcmp(rootnode, "rss") ) { - debug_print("RSSyl: XML - calling rssyl_parse_rss()\n"); - count = rssyl_parse_rss(doc, ritem, parent); - } else if( !strcmp(rootnode, "rdf") ) { - debug_print("RSSyl: XML - calling rssyl_parse_rdf()\n"); - if (ritem->fetch_comments) { - log_error(LOG_PROTOCOL, _("RSSyl: Fetching comments is not supported for RDF feeds. " - "Cannot fetch comments of '%s'"), ritem->item.name); - ritem->fetch_comments = FALSE; - } - count = rssyl_parse_rdf(doc, ritem, parent); - } else if( !strcmp(rootnode, "feed") ) { - debug_print("RSSyl: XML - calling rssyl_parse_atom()\n"); - count = rssyl_parse_atom(doc, ritem, parent); - } else { - alertpanel_error(_("This feed format is not supported yet.")); - count = 0; - } - - if (!parent) - ritem->last_count = count; - - folder_item_scan(&ritem->item); - folder_item_update_thaw(); - - STATUSBAR_POP(mainwin); - - g_free(rootnode); -} - -gboolean rssyl_add_feed_item(RSSylFolderItem *ritem, RSSylFeedItem *fitem) -{ - MsgFlags *flags; - gchar *template, *tmpurl, *tmpid; - gchar tmp[10240]; - gint d = -1, fd, dif = 0; - FILE *f; - RSSylFeedItem *oldfitem = NULL; - gchar *meta_charset = NULL, *url_html = NULL; - gboolean err = FALSE; - - g_return_val_if_fail(ritem != NULL, FALSE); - g_return_val_if_fail(ritem->item.path != NULL, FALSE); - g_return_val_if_fail(fitem != NULL, FALSE); - - if( !fitem->author ) - fitem->author = g_strdup(_("N/A")); - - /* Skip if the item already exists */ - dif = rssyl_feed_item_exists(ritem, fitem, &oldfitem); - if( dif == 1 ) { - debug_print("RSSyl: This item already exists, skipping...\n"); - return FALSE; - } - if( dif >= 2 && oldfitem != NULL ) { - debug_print("RSSyl: Item changed, removing old one and adding new...\n"); - ritem->contents = g_slist_remove(ritem->contents, oldfitem); - g_remove(oldfitem->realpath); - rssyl_free_feeditem(oldfitem); - oldfitem = NULL; - } - - /* Adjust some fields */ - if( fitem->date <= 0 ) - fitem->date = time(NULL); - - debug_print("RSSyl: Adding item '%s' (%d)\n", fitem->title, dif); - - ritem->contents = g_slist_append(ritem->contents, fitem); - - flags = g_new(MsgFlags, 1); -#ifndef G_OS_WIN32 - template = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, RSSYL_TMP_TEMPLATE, NULL); - fd = mkstemp(template); -#else - template = get_tmp_file(); - fd = open(template, (O_CREAT|O_RDWR|O_BINARY), (S_IRUSR|S_IWUSR)); -#endif - - f = fdopen(fd, "w"); - g_return_val_if_fail(f != NULL, FALSE); - - if( fitem->date != 0 ) { - gchar *tmpdate = createRFC822Date(&fitem->date); - err |= (fprintf(f, "Date: %s\n", tmpdate ) < 0); - g_free(tmpdate); - } - - if( fitem->author ) { - if (g_utf8_validate(fitem->author, -1, NULL)) { - conv_encode_header_full(tmp, 10239, fitem->author, - strlen("From: "), TRUE, CS_UTF_8); - err |= (fprintf(f, "From: %s\n", tmp) < 0); - } else - err |= (fprintf(f, "From: %s\n", fitem->author) < 0); - } - - if( fitem->title ) { - if (g_utf8_validate(fitem->title, -1, NULL)) { - conv_encode_header_full(tmp, 10239, fitem->title, - strlen("Subject: "), FALSE, CS_UTF_8); - err |= (fprintf(f, "Subject: %s\n", tmp) < 0); - } else - err |= (fprintf(f, "Subject: %s\n", fitem->title) < 0); - } - - if( (tmpurl = fitem->link) == NULL ) { - if( fitem->id != NULL && fitem->id_is_permalink ) - tmpurl = fitem->id; - } - if( tmpurl != NULL ) - err |= (fprintf(f, "X-RSSyl-URL: %s\n", tmpurl) < 0); - - if( (tmpid = fitem->id) == NULL ) - tmpid = fitem->link; - if( tmpid != NULL ) - err |= (fprintf(f, "Message-ID: <%s>\n", tmpid) < 0); - - if( fitem->comments_link ) { - err |= (fprintf(f, "X-RSSyl-Comments: %s\n", fitem->comments_link) < 0); - } - if( fitem->parent_link) { - err |= (fprintf(f, "X-RSSyl-Parent: %s\n", fitem->parent_link) < 0); - err |= (fprintf(f, "References: <%s>\n", fitem->parent_link) < 0); - } - -#ifdef RSSYL_DEBUG - if( fitem->debug_fetched != -1 ) { - err |= (fprintf(f, "X-RSSyl-Debug-Fetched: %ld\n", fitem->debug_fetched) < 0); - } -#endif /* RSSYL_DEBUG */ - - if (fitem->text && g_utf8_validate(fitem->text, -1, NULL)) { - /* if it passes UTF-8 validation, specify it. */ - err |= (fprintf(f, "Content-Type: text/html; charset=UTF-8\n\n") < 0); - meta_charset = g_strdup(""); - } else { - /* make sure Claws Mail displays it as html */ - err |= (fprintf(f, "Content-Type: text/html\n\n") < 0); - } - - if( tmpurl ) - url_html = g_strdup_printf("

URL: %s

\n
\n", - tmpurl, tmpurl); - - err |= (fprintf(f, "" - "%s\n" - "\n" - "\n" - "%s" - RSSYL_TEXT_START"\n" - "%s%s" - RSSYL_TEXT_END"\n\n", - - meta_charset ? meta_charset:"", - fitem->link, - url_html?url_html:"", - (fitem->text ? fitem->text : ""), - (fitem->text ? "\n" : "") ) < 0 ); - - g_free(meta_charset); - g_free(url_html); - if( fitem->media ) { - if( fitem->media->size > 0 ) { - tmpid = g_strdup_printf(ngettext("%ld byte", "%ld bytes", - fitem->media->size), fitem->media->size); - } else { - tmpid = g_strdup(_("size unknown")); - } - - fprintf(f, "

Attached media file [%s] (%s)

\n", - fitem->media->url, fitem->media->type, tmpid); - - g_free(tmpid); - } - - if( fitem->media ) - err |= (fprintf(f, - "

Attached media file [%s] (%ld bytes)

\n", - fitem->media->url, fitem->media->type, fitem->media->size) < 0); - - err |= (fprintf(f, "\n") < 0); - - err |= (fclose(f) == EOF); - - if (!err) { - g_return_val_if_fail(template != NULL, FALSE); - d = folder_item_add_msg(&ritem->item, template, flags, TRUE); - } - g_free(template); - - if (ritem->silent_update == 2 - || (ritem->silent_update == 1 && dif == EXISTS_CHANGED_TEXTONLY)) - procmsg_msginfo_unset_flags( - folder_item_get_msginfo((FolderItem *)ritem, d), MSG_NEW | MSG_UNREAD, 0); - else - - debug_print("RSSyl: folder_item_add_msg(): %d, err %d\n", d, err); - - return err ? FALSE:TRUE; -} - -MsgInfo *rssyl_parse_feed_item_to_msginfo(gchar *file, MsgFlags flags, - gboolean a, gboolean b, FolderItem *item) -{ - MsgInfo *msginfo; - - g_return_val_if_fail(item != NULL, NULL); - - msginfo = procheader_parse_file(file, flags, a, b); - if (msginfo) - msginfo->folder = item; - - return msginfo; -} - -void rssyl_remove_feed_cache(FolderItem *item) -{ - gchar *path; - DIR *dp; - struct dirent *d; - gint num = 0; - - g_return_if_fail(item != NULL); - - debug_print("Removing local cache for '%s'\n", item->name); - - path = folder_item_get_path(item); - g_return_if_fail(path != NULL); - if( change_dir(path) < 0 ) { - g_free(path); - return; - } - - debug_print("Emptying '%s'\n", path); - - if( (dp = opendir(".")) == NULL ) { - FILE_OP_ERROR(item->path, "opendir"); - return; - } - - while( (d = readdir(dp)) != NULL ) { - g_remove(d->d_name); - num++; - } - closedir(dp); - - debug_print("Removed %d files\n", num); - - g_remove(path); - g_free(path); -} - -void rssyl_update_comments(RSSylFolderItem *ritem) -{ - FolderItem *item = &ritem->item; - RSSylFeedItem *fitem = NULL; - DIR *dp; - struct dirent *d; - gint num; - gchar *path; - - g_return_if_fail(ritem != NULL); - - if (ritem->fetch_comments == FALSE) - return; - - path = folder_item_get_path(item); - g_return_if_fail(path != NULL); - if( change_dir(path) < 0 ) { - g_free(path); - return; - } - - if( (dp = opendir(".")) == NULL ) { - FILE_OP_ERROR(item->path, "opendir"); - g_free(path); - return; - } - - while( (d = readdir(dp)) != NULL ) { - if (claws_is_exiting()) { - g_free(path); - closedir(dp); - debug_print("RSSyl: update_comments bailing out, app is exiting\n"); - return; - } - - if( (num = to_number(d->d_name)) > 0 && dirent_is_regular_file(d) ) { - debug_print("RSSyl: starting to parse '%s'\n", d->d_name); - if( (fitem = rssyl_parse_folder_item_file(path, d->d_name)) != NULL ) { - xmlDocPtr doc; - gchar *title; - if (fitem->comments_link && fitem->id && - (ritem->fetch_comments_for == -1 || - time(NULL) - fitem->date <= ritem->fetch_comments_for*86400)) { - debug_print("RSSyl: fetching comments '%s'\n", fitem->comments_link); - doc = rssyl_fetch_feed(fitem->comments_link, ritem->item.mtime, - ritem->ssl_verify_peer, &title, NULL); - rssyl_parse_feed(doc, ritem, fitem->id); - xmlFreeDoc(doc); - g_free(title); - } - rssyl_free_feeditem(fitem); - } - } - } - closedir(dp); - g_free(path); - - debug_print("RSSyl: rssyl_update_comments() is returning\n"); -} - -void rssyl_update_feed(RSSylFolderItem *ritem) -{ - gchar *title = NULL, *dir = NULL, *error = NULL, *dir2, *tmp; - xmlDocPtr doc = NULL; - - g_return_if_fail(ritem != NULL); - - if( !ritem->url ) - rssyl_get_feed_props(ritem); - g_return_if_fail(ritem->url != NULL); - - log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATING, ritem->url); - - doc = rssyl_fetch_feed(ritem->url, ritem->item.mtime, ritem->ssl_verify_peer, &title, &error); - - if (claws_is_exiting()) { - debug_print("RSSyl: Claws-Mail is exiting, aborting feed parsing\n"); - log_print(LOG_PROTOCOL, RSSYL_LOG_EXITING); - if (error) - g_free(error); - if (doc) - xmlFreeDoc(doc); - g_free(title); - g_free(dir); - return; - } - - if (error) { - log_error(LOG_PROTOCOL, _("RSSyl: Cannot update feed %s:\n%s\n"), ritem->url, error); - } - g_free(error); - - if (doc && title) { - tmp = rssyl_feed_title_to_dir(title); - dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, tmp, NULL); - g_free(tmp); - if( strcmp(title, ritem->official_name) ) { - tmp = rssyl_feed_title_to_dir((&ritem->item)->name); - dir2 = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, tmp, - NULL); - g_free(tmp); - if( g_rename(dir2, dir) == -1 ) { - g_warning("couldn't rename directory '%s'\n", dir2); - g_free(dir); - g_free(dir2); - g_free(title); - xmlFreeDoc(doc); - return; - } - g_free(dir2); - - rssyl_props_update_name(ritem, title); - - g_free((&ritem->item)->name); - (&ritem->item)->name = g_strdup(title); - g_free(ritem->official_name); - ritem->official_name = g_strdup(title); - folder_item_rename(&ritem->item, title); - rssyl_store_feed_props(ritem); - } - - rssyl_parse_feed(doc, ritem, NULL); - - if (claws_is_exiting()) { - debug_print("RSSyl: Claws-Mail is exiting, aborting feed parsing\n"); - log_print(LOG_PROTOCOL, RSSYL_LOG_EXITING); - if (error) - g_free(error); - if (doc) - xmlFreeDoc(doc); - g_free(title); - g_free(dir); - return; - } - - rssyl_expire_items(ritem); - } - - if (claws_is_exiting()) { - g_free(title); - g_free(dir); - if (doc) - xmlFreeDoc(doc); - return; - } - - if( ritem->fetch_comments == TRUE) - rssyl_update_comments(ritem); - - ritem->item.mtime = time(NULL); - debug_print("setting %s mtime to %ld\n", ritem->item.name, (long int)time(NULL)); - - if (doc) - xmlFreeDoc(doc); - g_free(title); - g_free(dir); - - log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATED, ritem->url); -} - -void rssyl_start_refresh_timeout(RSSylFolderItem *ritem) -{ - RSSylRefreshCtx *ctx; - guint source_id; - RSSylPrefs *rsprefs = NULL; - - g_return_if_fail(ritem != NULL); - - if( ritem->default_refresh_interval ) { - rsprefs = rssyl_prefs_get(); - ritem->refresh_interval = rsprefs->refresh; - } - - /* Do not start refreshing if the interval is set to 0 */ - if( ritem->refresh_interval == 0 ) - return; - - ctx = g_new0(RSSylRefreshCtx, 1); - ctx->ritem = ritem; - - source_id = g_timeout_add(ritem->refresh_interval * 60 * 1000, - (GSourceFunc)rssyl_refresh_timeout_cb, ctx ); - ritem->refresh_id = source_id; - ctx->id = source_id; - - debug_print("RSSyl: start_refresh_timeout - %d min (id %d)\n", - ritem->refresh_interval, ctx->id); -} - -static void rssyl_find_feed_by_url_func(FolderItem *item, gpointer data) -{ - RSSylFolderItem *ritem; - RSSylFindByUrlCtx *ctx = (RSSylFindByUrlCtx *)data; - - /* If we've already found a feed with desired URL, don't do anything */ - if( ctx->ritem != NULL ) - return; - - /* Only check rssyl folders */ - if( !IS_RSSYL_FOLDER_ITEM(item) ) - return; - - /* Don't bother with the root folder */ - if( folder_item_parent(item) == NULL ) - return; - - ritem = (RSSylFolderItem *)item; - - if( ritem->url && ctx->url && !strcmp(ritem->url, ctx->url) ) - ctx->ritem = ritem; -} - -static RSSylFolderItem *rssyl_find_feed_by_url(gchar *url) -{ - RSSylFindByUrlCtx *ctx = NULL; - RSSylFolderItem *ritem = NULL; - - g_return_val_if_fail(url != NULL, NULL); - - ctx = g_new0(RSSylFindByUrlCtx, 1); - ctx->url = url; - ctx->ritem = NULL; - - folder_func_to_all_folders( - (FolderItemFunc)rssyl_find_feed_by_url_func, ctx); - - if( ctx->ritem != NULL ) - ritem = ctx->ritem; - - g_free(ctx); - - return ritem; -} - -FolderItem *rssyl_subscribe_new_feed(FolderItem *parent, const gchar *url, - gboolean verbose) -{ - gchar *title = NULL; - xmlDocPtr doc; - FolderItem *new_item; - RSSylFolderItem *ritem; - gchar *myurl = NULL; - gchar *error = NULL; - - g_return_val_if_fail(parent != NULL, NULL); - g_return_val_if_fail(url != NULL, NULL); - - if (!strncmp(url, "feed://", 7)) - myurl = g_strdup(url+7); - else if (!strncmp(url, "feed:", 5)) - myurl = g_strdup(url+5); - else - myurl = g_strdup(url); - - myurl = g_strchomp(myurl); - - if( rssyl_find_feed_by_url(myurl) != NULL ) { - if (verbose) - alertpanel_error(_("You are already subscribed to this feed.")); - g_free(myurl); - return NULL; - } - - main_window_cursor_wait(mainwindow_get_mainwindow()); - doc = rssyl_fetch_feed(myurl, -1, rssyl_prefs_get()->ssl_verify_peer, &title, &error); - main_window_cursor_normal(mainwindow_get_mainwindow()); - if( !doc || !title ) { - if (verbose) { - gchar *tmp; - tmp = g_markup_printf_escaped(_("Couldn't fetch URL '%s':\n%s"), - myurl, error ? error:_("Unknown error")); - alertpanel_error("%s", tmp); - g_free(tmp); - } else - log_error(LOG_PROTOCOL, _("Couldn't fetch URL '%s':\n%s\n"), myurl, error ? error:_("Unknown error")); - g_free(myurl); - g_free(error); - g_free(title); - if (doc) - xmlFreeDoc(doc); - return NULL; - } - - g_free(error); - - new_item = folder_create_folder(parent, title); - if( !new_item ) { - if (verbose) { - gchar *tmp; - tmp = g_markup_printf_escaped("%s", title); - alertpanel_error(_("Can't subscribe feed '%s'."), title); - g_free(tmp); - } - g_free(myurl); - xmlFreeDoc(doc); - return NULL; - } - - ritem = (RSSylFolderItem *)new_item; - ritem->url = myurl; - - ritem->default_refresh_interval = TRUE; - ritem->default_expired_num = TRUE; - - rssyl_store_feed_props(ritem); - - folder_write_list(); - - rssyl_parse_feed(doc, ritem, NULL); - xmlFreeDoc(doc); - - rssyl_expire_items(ritem); - - /* TODO: allow user to enable this when adding the feed */ - if( ritem->fetch_comments ) - rssyl_update_comments(ritem); - - /* update official_title */ - rssyl_store_feed_props(ritem); - rssyl_start_refresh_timeout(ritem); - - folder_item_scan(new_item); - return new_item; -} - -static gint rssyl_expire_sort_func(RSSylFeedItem *a, RSSylFeedItem *b) -{ - if( !a || !b ) - return 0; - - return (b->date - a->date); -} - -void rssyl_expire_items(RSSylFolderItem *ritem) -{ - int num; - RSSylFeedItem *fitem; - GSList *i; - - g_return_if_fail(ritem != NULL); - - rssyl_read_existing(ritem); - - g_return_if_fail(ritem->contents != NULL); - - num = ritem->expired_num; - if( num == -1 || - (num > (g_slist_length(ritem->contents) - ritem->last_count)) ) - return; - - debug_print("RSSyl: rssyl_expire_items()\n"); - - ritem->contents = g_slist_sort(ritem->contents, - (GCompareFunc)rssyl_expire_sort_func); - - debug_print("RSSyl: finished sorting\n"); - - while( (i = g_slist_nth(ritem->contents, ritem->last_count + num + 1)) ) { - fitem = (RSSylFeedItem *)i->data; - debug_print("RSSyl: expiring '%s'\n", fitem->realpath); - g_remove(fitem->realpath); - rssyl_free_feeditem(fitem); - fitem = NULL; - ritem->contents = g_slist_remove(ritem->contents, i->data); - } - - folder_item_scan(&ritem->item); - - debug_print("RSSyl: finished expiring\n"); -} - - -void rssyl_refresh_all_func(FolderItem *item, gpointer data) -{ - RSSylFolderItem *ritem = (RSSylFolderItem *)item; - /* Only try to refresh our feed folders */ - if( !IS_RSSYL_FOLDER_ITEM(item) ) - return; - - /* Don't try to refresh the root folder */ - if( folder_item_parent(item) == NULL ) - return; - /* Don't try to update normal folders */ - if (ritem->url == NULL) - return; - rssyl_update_feed((RSSylFolderItem *)item); -} - -void rssyl_refresh_all_feeds(void) -{ - if (prefs_common_get_prefs()->work_offline && - !inc_offline_should_override(TRUE, - ngettext("Claws Mail needs network access in order " - "to update the feed.", - "Claws Mail needs network access in order " - "to update the feeds.", 2))) { - return; - } - - folder_func_to_all_folders((FolderItemFunc)rssyl_refresh_all_func, NULL); -} diff --git a/src/plugins/rssyl/feed.h b/src/plugins/rssyl/feed.h deleted file mode 100644 index 1d66b7dc5..000000000 --- a/src/plugins/rssyl/feed.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef __FEED_H -#define __FEED_H - -#include - -#include "procmsg.h" - -#include "rssyl.h" - -#define RSSYL_TMP_TEMPLATE "curltmpXXXXXX" - -#define RSSYL_XPATH_ROOT "/rssyl" -#define RSSYL_XPATH_TITLE RSSYL_XPATH_ROOT"/title" -#define RSSYL_XPATH_LINK RSSYL_XPATH_ROOT"/link" -#define RSSYL_XPATH_TEXT RSSYL_XPATH_ROOT"/text" - -#define RSSYL_TEXT_START "" -#define RSSYL_TEXT_END "" - -#define RSSYL_LOG_ERROR_TIMEOUT _("Time out connecting to URL %s\n") -#define RSSYL_LOG_ERROR_FETCH _("Couldn't fetch URL %s\n") -#define RSSYL_LOG_ERROR_PARSE _("Error parsing feed from URL %s\n") -#define RSSYL_LOG_ERROR_UNKNOWN _("Unsupported feed type at URL %s\n") - -#define RSSYL_LOG_UPDATING _("RSSyl: Updating feed %s\n") -#define RSSYL_LOG_UPDATED _("RSSyl: Feed update finished: %s\n") -#define RSSYL_LOG_EXITING _("RSSyl: Feed update aborted, application is exiting.\n") - -struct _RSSylFeedItemMedia { - gchar *url; - gchar *type; - gulong size; -}; - -typedef struct _RSSylFeedItemMedia RSSylFeedItemMedia; - -struct _RSSylFeedItem { - gchar *title; - gchar *text; - gchar *link; - gchar *parent_link; - gchar *comments_link; - gchar *author; - gchar *id; - gboolean id_is_permalink; - - RSSylFeedItemMedia *media; - -#ifdef RSSYL_DEBUG - long int debug_fetched; -#endif /* RSSYL_DEBUG */ - - gchar *realpath; - time_t date; - time_t date_published; -}; - -typedef struct _RSSylFeedItem RSSylFeedItem; - -struct _RSSylFindByUrlCtx { - gchar *url; - RSSylFolderItem *ritem; -}; - -typedef struct _RSSylFindByUrlCtx RSSylFindByUrlCtx; - -struct _RSSylParseCtx { - RSSylFolderItem *ritem; - gboolean ready; -}; - -typedef struct _RSSylParseCtx RSSylParseCtx; - -xmlDocPtr rssyl_fetch_feed(const gchar *url, time_t last_update, gboolean ssl_verify_peer, gchar **title, gchar **error); -void rssyl_parse_feed(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent); -gboolean rssyl_add_feed_item(RSSylFolderItem *ritem, RSSylFeedItem *fitem); -MsgInfo *rssyl_parse_feed_item_to_msginfo(gchar *file, MsgFlags flags, - gboolean a, gboolean b, FolderItem *item); -void rssyl_remove_feed_cache(FolderItem *item); -void rssyl_update_feed(RSSylFolderItem *ritem); -void rssyl_read_existing(RSSylFolderItem *ritem); - -void rssyl_start_refresh_timeout(RSSylFolderItem *ritem); -void rssyl_expire_items(RSSylFolderItem *ritem); - -FolderItem *rssyl_subscribe_new_feed(FolderItem *parent, const gchar *url, gboolean verbose); -void rssyl_free_feeditem(RSSylFeedItem *item); -gchar *rssyl_format_string(const gchar *str, gboolean replace_html, gboolean replace_returns); - -void rssyl_refresh_all_func(FolderItem *item, gpointer data); -void rssyl_refresh_all_feeds(void); -gchar *rssyl_feed_title_to_dir(const gchar *title); - -#endif /* __FEED_H */ diff --git a/src/plugins/rssyl/feedprops.c b/src/plugins/rssyl/feedprops.c deleted file mode 100644 index d52bde520..000000000 --- a/src/plugins/rssyl/feedprops.c +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client - * Copyright (C) 1999-2004 Hiroyuki Yamamoto - * This file (C) 2005 Andrej Kacian - * - * - functions for handling feeds.xml file - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include - -#include -#include - -#include "folder.h" - -#include "feed.h" -#include "feedprops.h" -#include "rssyl.h" -#include "rssyl_prefs.h" - -static gchar *rssyl_get_props_path(void) -{ - return g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, RSSYL_PROPS_FILE, NULL); -} - - -/* rssyl_store_feed_props() - * - * Stores feed properties into feeds.xml file in the rcdir. Updates if already - * stored for this feed. - */ -void rssyl_store_feed_props(RSSylFolderItem *ritem) -{ - gchar *path; - xmlDocPtr doc; - xmlNodePtr node, rootnode; - xmlXPathObjectPtr result; - xmlXPathContextPtr context; - FolderItem *item = &ritem->item; - gboolean found = FALSE, def_ri, def_ex; - gint i; - - g_return_if_fail(ritem != NULL); - g_return_if_fail(ritem->url != NULL); - - def_ri = ritem->default_refresh_interval; - if( def_ri ) - ritem->refresh_interval = rssyl_prefs_get()->refresh; - - def_ex = ritem->default_expired_num; - if( def_ex ) - ritem->expired_num = rssyl_prefs_get()->expired; - - path = rssyl_get_props_path(); - - if( !(doc = xmlParseFile(path)) ) { - debug_print("RSSyl: file %s doesn't exist, creating it\n", path); - doc = xmlNewDoc("1.0"); - - rootnode = xmlNewNode(NULL, "feeds"); - xmlDocSetRootElement(doc, rootnode); - } else { - rootnode = xmlDocGetRootElement(doc); - } - - context = xmlXPathNewContext(doc); - if( !(result = xmlXPathEvalExpression(RSSYL_PROPS_XPATH, context)) ) { - debug_print("RSSyl: XML - no result found for %s\n", RSSYL_PROPS_XPATH); - xmlXPathFreeContext(context); - } else { - for( i = 0; i < result->nodesetval->nodeNr; i++ ) { - gchar *tmp, *t_prop = NULL; - node = result->nodesetval->nodeTab[i]; - tmp = xmlGetProp(node, RSSYL_PROP_NAME); - - if( !strcmp(tmp, item->name) ) { - debug_print("RSSyl: XML - updating node for '%s'\n", item->name); - - xmlSetProp(node, RSSYL_PROP_NAME, item->name); - xmlSetProp(node, RSSYL_PROP_OFFICIAL_NAME, - ritem->official_name?ritem->official_name:item->name); - xmlSetProp(node, RSSYL_PROP_URL, ritem->url); - - xmlSetProp(node, RSSYL_PROP_DEF_REFRESH, (def_ri ? "1" : "0") ); - if( !def_ri ) { - t_prop = g_strdup_printf("%d", ritem->refresh_interval); - xmlSetProp(node, RSSYL_PROP_REFRESH, - t_prop ); - g_free(t_prop); - } - - xmlSetProp(node, RSSYL_PROP_DEF_EXPIRED, (def_ex ? "1" : "0") ); - if( !def_ex ) { - t_prop = g_strdup_printf("%d", ritem->expired_num); - xmlSetProp(node, RSSYL_PROP_EXPIRED, - t_prop ); - g_free(t_prop); - } - xmlSetProp(node, RSSYL_PROP_FETCH_COMMENTS, - (ritem->fetch_comments ? "1" : "0")); - t_prop = g_strdup_printf("%d", ritem->fetch_comments_for); - xmlSetProp(node, RSSYL_PROP_FETCH_COMMENTS_FOR, t_prop); - g_free(t_prop); - - t_prop = g_strdup_printf("%d", ritem->silent_update); - xmlSetProp(node, RSSYL_PROP_SILENT_UPDATE, t_prop); - g_free(t_prop); - - t_prop = g_strdup_printf("%d", ritem->ssl_verify_peer); - xmlSetProp(node, RSSYL_PROP_SSL_VERIFY_PEER, t_prop); - g_free(t_prop); - - found = TRUE; - } - xmlFree(tmp); - } - } - - xmlXPathFreeContext(context); - xmlXPathFreeObject(result); - - /* node for our feed doesn't exist, let's make one */ - if( !found ) { - debug_print("RSSyl: XML - creating node for '%s', storing URL '%s'\n", - item->name, ritem->url); - node = xmlNewTextChild(rootnode, NULL, "feed", NULL); - xmlSetProp(node, RSSYL_PROP_NAME, item->name); - xmlSetProp(node, RSSYL_PROP_OFFICIAL_NAME, - ritem->official_name?ritem->official_name:item->name); - xmlSetProp(node, RSSYL_PROP_URL, ritem->url); - xmlSetProp(node, RSSYL_PROP_DEF_REFRESH, (def_ri ? "1" : "0") ); - if( !def_ri ) - xmlSetProp(node, RSSYL_PROP_REFRESH, - g_strdup_printf("%d", ritem->refresh_interval) ); - xmlSetProp(node, RSSYL_PROP_DEF_EXPIRED, (def_ex ? "1" : "0") ); - if( !def_ex ) - xmlSetProp(node, RSSYL_PROP_EXPIRED, - g_strdup_printf("%d", ritem->expired_num) ); - } - - xmlSaveFormatFile(path, doc, 1); - xmlFreeDoc(doc); - g_free(path); -} - -/* rssyl_get_feed_props() - * - * Retrieves props for feed from feeds.xml file in the rcdir. - */ -void rssyl_get_feed_props(RSSylFolderItem *ritem) -{ - gchar *path, *tmp = NULL; - xmlDocPtr doc; - xmlNodePtr node; - xmlXPathObjectPtr result; - xmlXPathContextPtr context; - FolderItem *item = &ritem->item; - gint i, tmpi; - gboolean force_update = FALSE; - RSSylPrefs *rsprefs = NULL; - - g_return_if_fail(ritem != NULL); - - if( ritem->url ) { - g_free(ritem->url); - ritem->url = NULL; - } - - ritem->default_refresh_interval = TRUE; - ritem->refresh_interval = rssyl_prefs_get()->refresh; - ritem->expired_num = rssyl_prefs_get()->expired; - - path = rssyl_get_props_path(); - - doc = xmlParseFile(path); - g_return_if_fail(doc != NULL); - - context = xmlXPathNewContext(doc); - if( !(result = xmlXPathEvalExpression(RSSYL_PROPS_XPATH, context)) ) { - debug_print("RSSyl: XML - no result found for %s\n", RSSYL_PROPS_XPATH); - xmlXPathFreeContext(context); - } else { - for( i = 0; i < result->nodesetval->nodeNr; i++ ) { - gchar *property = NULL; - node = result->nodesetval->nodeTab[i]; - property = xmlGetProp(node, RSSYL_PROP_NAME); - if( !strcmp(property, item->name) ) { - /* official name */ - tmp = xmlGetProp(node, RSSYL_PROP_OFFICIAL_NAME); - ritem->official_name = (tmp ? g_strdup(tmp) : g_strdup(item->name)); - if (tmp == NULL) - force_update = TRUE; - xmlFree(tmp); - tmp = NULL; - - /* URL */ - tmp = xmlGetProp(node, RSSYL_PROP_URL); - ritem->url = (tmp ? g_strdup(tmp) : NULL); - xmlFree(tmp); - tmp = NULL; - - tmp = xmlGetProp(node, RSSYL_PROP_DEF_REFRESH); - tmpi = 0; - if( tmp ) - tmpi = atoi(tmp); - ritem->default_refresh_interval = (tmpi ? TRUE : FALSE ); - xmlFree(tmp); - tmp = NULL; - - /* refresh_interval */ - tmp = xmlGetProp(node, RSSYL_PROP_REFRESH); - if( ritem->default_refresh_interval ) - ritem->refresh_interval = rssyl_prefs_get()->refresh; - else { - tmpi = -1; - if( tmp ) - tmpi = atoi(tmp); - - ritem->refresh_interval = - (tmpi != -1 ? tmpi : rssyl_prefs_get()->refresh); - } - - xmlFree(tmp); - tmp = NULL; - - /* expired_num */ - tmp = xmlGetProp(node, RSSYL_PROP_DEF_EXPIRED); - tmpi = 0; - if( tmp ) { - tmpi = atoi(tmp); - ritem->default_expired_num = tmpi; - } - xmlFree(tmp); - tmp = NULL; - - /* fetch_comments */ - tmp = xmlGetProp(node, RSSYL_PROP_FETCH_COMMENTS); - tmpi = 0; - if( tmp ) { - tmpi = atoi(tmp); - ritem->fetch_comments = tmpi; - } - xmlFree(tmp); - tmp = NULL; - - /* fetch_comments_for */ - tmp = xmlGetProp(node, RSSYL_PROP_FETCH_COMMENTS_FOR); - tmpi = 0; - if( tmp ) { - tmpi = atoi(tmp); - ritem->fetch_comments_for = tmpi; - } - xmlFree(tmp); - tmp = NULL; - - /* silent_update */ - tmp = xmlGetProp(node, RSSYL_PROP_SILENT_UPDATE); - tmpi = 0; - if( tmp ) { - tmpi = atoi(tmp); - ritem->silent_update = tmpi; - } - xmlFree(tmp); - tmp = NULL; - - /* ssl_verify_peer */ - tmp = xmlGetProp(node, RSSYL_PROP_SSL_VERIFY_PEER); - tmpi = 0; - if( tmp ) { - tmpi = atoi(tmp); - ritem->ssl_verify_peer = tmpi; - } - xmlFree(tmp); - tmp = NULL; - - tmp = xmlGetProp(node, RSSYL_PROP_EXPIRED); - - if( ritem->default_expired_num ) - ritem->expired_num = rssyl_prefs_get()->expired; - else { - tmpi = -2; - if( tmp ) - tmpi = atoi(tmp); - - ritem->expired_num = (tmpi != -2 ? tmpi : rssyl_prefs_get()->expired); - } - - xmlFree(tmp); - tmp = NULL; - - debug_print("RSSyl: XML - props for '%s' loaded\n", item->name); - - /* Start automatic refresh timer, if necessary */ - if( ritem->refresh_id == 0 ) { - /* Check if user wants the default for this feed */ - if( ritem->default_refresh_interval ) { - rsprefs = rssyl_prefs_get(); - ritem->refresh_interval = rsprefs->refresh; - } - - /* Start the timer, if determined interval is >0 */ - if( ritem->refresh_interval >= 0 ) - rssyl_start_refresh_timeout(ritem); - } - } - xmlFree(property); - } - } - - xmlXPathFreeObject(result); - xmlXPathFreeContext(context); - xmlFreeDoc(doc); - g_free(path); - if (force_update) - rssyl_store_feed_props(ritem); -} - -void rssyl_remove_feed_props(RSSylFolderItem *ritem) -{ - gchar *path; - xmlDocPtr doc; - xmlNodePtr node; - xmlXPathObjectPtr result; - xmlXPathContextPtr context; - FolderItem *item = &ritem->item; - gint i; - - g_return_if_fail(ritem != NULL); - - path = rssyl_get_props_path(); - - doc = xmlParseFile(path); - g_return_if_fail(doc != NULL); - - context = xmlXPathNewContext(doc); - if( !(result = xmlXPathEvalExpression(RSSYL_PROPS_XPATH, context)) ) { - debug_print("RSSyl: XML - no result found for %s\n", RSSYL_PROPS_XPATH); - xmlXPathFreeContext(context); - } else { - for( i = 0; i < result->nodesetval->nodeNr; i++ ) { - gchar *tmp; - node = result->nodesetval->nodeTab[i]; - tmp = xmlGetProp(node, RSSYL_PROP_NAME); - if( !strcmp(tmp, item->name) ) { - debug_print("RSSyl: XML - found node for '%s', removing\n", item->name); - xmlUnlinkNode(node); - } - xmlFree(tmp); - } - } - - xmlXPathFreeObject(result); - xmlXPathFreeContext(context); - - xmlSaveFormatFile(path, doc, 1); - - xmlFreeDoc(doc); - g_free(path); -} - -void rssyl_props_update_name(RSSylFolderItem *ritem, gchar *new_name) -{ - gchar *path; - xmlDocPtr doc; - xmlNodePtr node, rootnode; - xmlXPathObjectPtr result; - xmlXPathContextPtr context; - FolderItem *item = &ritem->item; - gboolean found = FALSE; - gint i; - - g_return_if_fail(ritem != NULL); - g_return_if_fail(ritem->url != NULL); - - path = rssyl_get_props_path(); - - if( !(doc = xmlParseFile(path)) ) { - debug_print("RSSyl: file %s doesn't exist, creating it\n", path); - doc = xmlNewDoc("1.0"); - - rootnode = xmlNewNode(NULL, "feeds"); - xmlDocSetRootElement(doc, rootnode); - } else { - rootnode = xmlDocGetRootElement(doc); - } - - context = xmlXPathNewContext(doc); - if( !(result = xmlXPathEvalExpression(RSSYL_PROPS_XPATH, context)) ) { - debug_print("RSSyl: XML - no result found for %s\n", RSSYL_PROPS_XPATH); - xmlXPathFreeContext(context); - } else { - for( i = 0; i < result->nodesetval->nodeNr; i++ ) { - gchar *tmp; - node = result->nodesetval->nodeTab[i]; - tmp = xmlGetProp(node, RSSYL_PROP_NAME); - if( !strcmp(tmp, item->name) ) { - debug_print("RSSyl: XML - updating node for '%s'\n", item->name); - xmlSetProp(node, "name", new_name); - found = TRUE; - } - xmlFree(tmp); - } - } - - xmlXPathFreeContext(context); - xmlXPathFreeObject(result); - - if( !found ) - debug_print("couldn't find feed\n"); - - xmlSaveFormatFile(path, doc, 1); - xmlFreeDoc(doc); - g_free(path); -} diff --git a/src/plugins/rssyl/feedprops.h b/src/plugins/rssyl/feedprops.h deleted file mode 100644 index f4a3b27e2..000000000 --- a/src/plugins/rssyl/feedprops.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef __FEEDPROPS_H -#define __FEEDPROPS_H - -#include "rssyl.h" - -#define RSSYL_PROPS_FILE "feeds.xml" -#define RSSYL_PROPS_XPATH "/feeds/feed" - -#define RSSYL_PROP_URL "url" -#define RSSYL_PROP_NAME "name" -#define RSSYL_PROP_OFFICIAL_NAME "official_name" -#define RSSYL_PROP_DEF_REFRESH "default_refresh_interval" -#define RSSYL_PROP_REFRESH "refresh_interval" -#define RSSYL_PROP_DEF_EXPIRED "default_expired_num" -#define RSSYL_PROP_EXPIRED "expired_num" -#define RSSYL_PROP_FETCH_COMMENTS "fetch_comments" -#define RSSYL_PROP_FETCH_COMMENTS_FOR "fetch_comments_for" -#define RSSYL_PROP_SILENT_UPDATE "silent_update" -#define RSSYL_PROP_SSL_VERIFY_PEER "ssl_verify_peer" - -void rssyl_store_feed_props(RSSylFolderItem *ritem); -void rssyl_get_feed_props(RSSylFolderItem *ritem); -void rssyl_remove_feed_props(RSSylFolderItem *ritem); -void rssyl_props_update_name(RSSylFolderItem *ritem, gchar *new_name); - -#endif /* __FEEDPROPS_H */ diff --git a/src/plugins/rssyl/libfeed/Makefile.am b/src/plugins/rssyl/libfeed/Makefile.am new file mode 100644 index 000000000..227b25ea3 --- /dev/null +++ b/src/plugins/rssyl/libfeed/Makefile.am @@ -0,0 +1,22 @@ +noinst_LTLIBRARIES = libfeed.la + +INCLUDES = \ + -I$(top_srcdir)/src \ + -I. + +libfeed_la_CPPFLAGS = \ + -Wall \ + $(CLAWS_MAIL_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GTK_CFLAGS) + +libfeed_la_SOURCES = \ + date.c date.h \ + feed.c feed.h \ + feeditem.c feeditem.h \ + feeditemenclosure.c feeditemenclosure.h \ + parser.c parser.h \ + parser_atom10.c parser_atom10.h \ + parser_opml.c parser_opml.h \ + parser_rdf.c parser_rdf.h \ + parser_rss20.c parser_rss20.h diff --git a/src/plugins/rssyl/date.c b/src/plugins/rssyl/libfeed/date.c similarity index 76% rename from src/plugins/rssyl/date.c rename to src/plugins/rssyl/libfeed/date.c index 4145f52db..2b8ffff5f 100644 --- a/src/plugins/rssyl/date.c +++ b/src/plugins/rssyl/libfeed/date.c @@ -33,15 +33,7 @@ #endif /* this is needed for strptime() */ -#if !defined (__FreeBSD__) -#define _XOPEN_SOURCE 600 /* glibc2 needs this */ -#else -#define _XOPEN_SOURCE -#endif - -#ifdef USE_PTHREAD -#include -#endif +#define _XOPEN_SOURCE /* glibc2 needs this */ #include #include @@ -50,46 +42,17 @@ #include #include -#include "procheader.h" - /* converts a ISO 8601 time string to a time_t value */ time_t parseISO8601Date(gchar *date) { struct tm tm; time_t t, t2, offset = 0; gboolean success = FALSE; -#ifdef G_OS_WIN32 - gchar *tmp = g_strdup(date); - gint result, year, month, day, hour, minute, second; -#else gchar *pos; -#endif + g_assert(date != NULL); memset(&tm, 0, sizeof(struct tm)); -#ifdef G_OS_WIN32 - g_strstrip(tmp); - result = sscanf((const char *)date, "%d-%d-%dT%d:%d:%d", - &year, &month, &day, &hour, &minute, &second); - if (result < 6) - second = 0; - if (result < 5) - minute = 0; - if (result < 4) - hour = 0; - if (result >= 3) { - tm.tm_sec = second; - tm.tm_min = minute; - tm.tm_hour = hour; - tm.tm_mday = day; - tm.tm_mon = month - 1; - tm.tm_year = year - 1900; - tm.tm_wday = 0; - tm.tm_yday = 0; - tm.tm_isdst = -1; - success = TRUE; - } -#else /* we expect at least something like "2003-08-07T15:28:19" and don't require the second fractions and the timezone info @@ -97,7 +60,7 @@ time_t parseISO8601Date(gchar *date) { */ /* full specified variant */ - if(NULL != (pos = strptime((const char *)date, "%t%Y-%m-%dT%H:%M%t", &tm))) { + if(NULL != (pos = strptime((const char *)date, "%Y-%m-%dT%H:%M:%SZ", &tm))) { /* Parse seconds */ if (*pos == ':') pos++; @@ -127,13 +90,12 @@ time_t parseISO8601Date(gchar *date) { } else if(NULL != strptime((const char *)date, "%t%Y-%m-%d", &tm)) success = TRUE; /* there were others combinations too... */ -#endif + if(TRUE == success) { if((time_t)(-1) != (t = mktime(&tm))) { - struct tm buft; /* Correct for the local timezone*/ t = t - offset; - t2 = mktime(gmtime_r(&t, &buft)); + t2 = mktime(gmtime(&t)); t = t - (t2 - t); return t; @@ -152,16 +114,34 @@ gchar *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" gchar *createRFC822Date(const time_t *time) { struct tm *tm; - struct tm buft; -#ifdef G_OS_WIN32 - if (*time < 0) { - time_t t = 1; - tm = gmtime_r(&t, &buft); - } else -#endif - { - tm = gmtime_r(time, &buft); /* No need to free because it is statically allocated */ - } + + tm = gmtime(time); /* No need to free because it is statically allocated */ return g_strdup_printf("%s, %2d %s %4d %02d:%02d:%02d GMT", dayofweek[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], 1900 + tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec); } + +time_t parseRFC822Date(gchar *date) +{ + struct tm t; + memset(&t, 0, sizeof(struct tm)); + const char *c = setlocale(LC_TIME, NULL); + + /* Adjust the LC_TIME locale to standard C in order for strptime() + * to work reliably. */ + if (c != NULL) + setlocale(LC_TIME, "C"); + + if (!strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) && + !strptime(date, "%a, %d %b %Y %H:%M %Z", &t)) { + g_warning("Invalid RFC822 date!\n"); + if (c != NULL) + setlocale(LC_TIME, c); + return 0; + } + + /* Restore the original LC_TIME locale. */ + if (c != NULL) + setlocale(LC_TIME, c); + + return mktime(&t); +} diff --git a/src/plugins/rssyl/date.h b/src/plugins/rssyl/libfeed/date.h similarity index 82% rename from src/plugins/rssyl/date.h rename to src/plugins/rssyl/libfeed/date.h index 27c135321..c8ea1a619 100644 --- a/src/plugins/rssyl/date.h +++ b/src/plugins/rssyl/libfeed/date.h @@ -6,5 +6,6 @@ time_t parseISO8601Date(gchar *date); gchar *createRFC822Date(const time_t *time); +time_t parseRFC822Date(gchar *date); #endif /* __DATE_H */ diff --git a/src/plugins/rssyl/libfeed/feed.c b/src/plugins/rssyl/libfeed/feed.c new file mode 100644 index 000000000..7719278e8 --- /dev/null +++ b/src/plugins/rssyl/libfeed/feed.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define __USE_GNU + +#include +#include +#include +#include + +#include "feed.h" +#include "parser.h" + +/* feed_new() + * Initializes new Feed struct, setting its url and a default timeout. */ +Feed *feed_new(gchar *url) +{ + Feed *feed = NULL; + + g_return_val_if_fail(url != NULL, NULL); + + feed = malloc( sizeof(Feed) ); + g_return_val_if_fail(feed != NULL, NULL); + + feed->timeout = FEED_DEFAULT_TIMEOUT; + feed->url = g_strdup(url); + feed->title = NULL; + feed->description = NULL; + feed->language = NULL; + feed->author = NULL; + feed->generator = NULL; + feed->items = NULL; + + feed->fetcherr = NULL; + feed->cookies_path = NULL; + + return feed; +} + +static void _free_items(gpointer item, gpointer nada) +{ + feed_item_free(item); +} + +void feed_free(Feed *feed) +{ + if( feed == NULL ) + return; /* Return silently, without printing a glib error. */ + + g_free(feed->url); + g_free(feed->title); + g_free(feed->description); + g_free(feed->language); + g_free(feed->author); + g_free(feed->generator); + g_free(feed->fetcherr); + g_free(feed->cookies_path); + + if( feed->items != NULL ) { + g_slist_foreach(feed->items, _free_items, NULL); + g_slist_free(feed->items); + } + + g_free(feed); + feed = NULL; +} + +void feed_free_items(Feed *feed) +{ + if( feed == NULL ) + return; + + if( feed->items != NULL ) { + g_slist_foreach(feed->items, _free_items, NULL); + g_slist_free(feed->items); + feed->items = NULL; + } +} + +/* Timeout */ +void feed_set_timeout(Feed *feed, guint timeout) +{ + g_return_if_fail(feed != NULL); + feed->timeout = timeout; +} + +guint feed_get_timeout(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, 0); + return feed->timeout; +} + +/* URL */ +void feed_set_url(Feed *feed, gchar *url) +{ + g_return_if_fail(feed != NULL); + g_return_if_fail(url != NULL); + + if( feed->url != NULL ) { + g_free(feed->url); + feed->url = NULL; + } + + feed->url = g_strdup(url); +} + +gchar *feed_get_url(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->url; +} + +/* Title */ +gchar *feed_get_title(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->title; +} + +void feed_set_title(Feed *feed, gchar *new_title) +{ + g_return_if_fail(feed != NULL); + g_return_if_fail(new_title != NULL); + + if (feed->title != NULL) { + g_free(feed->title); + feed->title = NULL; + } + + feed->title = g_strdup(new_title); +} + +/* Description */ +gchar *feed_get_description(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->description; +} + +/* Language */ +gchar *feed_get_language(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->language; +} + +/* Author */ +gchar *feed_get_author(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->author; +} + +/* Generator */ +gchar *feed_get_generator(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->generator; +} + +/* Fetch error (if not NULL, supplied by libcurl) */ +gchar *feed_get_fetcherror(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->fetcherr; +} + +/* Returns number of items currently in the feed. */ +gint feed_n_items(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, -1); + + if( feed->items == NULL ) /* No items here. */ + return 0; + + return g_slist_length(feed->items); +} + +/* Returns nth item from feed. */ +FeedItem *feed_nth_item(Feed *feed, guint n) +{ + g_return_val_if_fail(feed != NULL, NULL); + + return g_slist_nth_data(feed->items, n); +} + +/* feed_update() + * Takes initialized feed with url set, fetches the feed from this url, + * updates rest of Feed struct members and returns HTTP response code + * we got from url's server. */ +guint feed_update(Feed *feed, time_t last_update) +{ + CURL *eh = NULL; + CURLcode res; + FeedParserCtx *feed_ctx = NULL; + glong response_code = 0; + + g_return_val_if_fail(feed != NULL, FEED_ERR_NOFEED); + g_return_val_if_fail(feed->url != NULL, FEED_ERR_NOURL); + + /* Init curl before anything else. */ + eh = curl_easy_init(); + + g_return_val_if_fail(eh != NULL, FEED_ERR_INIT); + + /* Curl initialized, create parser context now. */ + feed_ctx = malloc( sizeof(FeedParserCtx) ); + + feed_ctx->parser = XML_ParserCreate(NULL); + feed_ctx->depth = 0; + feed_ctx->str = NULL; + feed_ctx->feed = feed; + feed_ctx->location = 0; + feed_ctx->curitem = NULL; + feed_ctx->id_is_permalink = TRUE; + + feed_ctx->name = NULL; + feed_ctx->mail = NULL; + + /* Set initial expat handlers, which will take care of choosing + * correct parser later. */ + feed_parser_set_expat_handlers(feed_ctx); + + curl_easy_setopt(eh, CURLOPT_URL, feed->url); + curl_easy_setopt(eh, CURLOPT_NOPROGRESS, 1); +#ifdef CURLOPT_MUTE + curl_easy_setopt(eh, CURLOPT_MUTE, 1); +#endif + curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, feed_writefunc); + curl_easy_setopt(eh, CURLOPT_WRITEDATA, feed_ctx); + curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(eh, CURLOPT_MAXREDIRS, 3); + curl_easy_setopt(eh, CURLOPT_TIMEOUT, feed->timeout); + curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(eh, CURLOPT_ENCODING, ""); + curl_easy_setopt(eh, CURLOPT_USERAGENT, "libfeed 0.1"); + + /* Use HTTP's If-Modified-Since feature, if application provided + * the timestamp of last update. */ + if( last_update != -1 ) { + curl_easy_setopt(eh, CURLOPT_TIMECONDITION, + CURL_TIMECOND_IFMODSINCE); + curl_easy_setopt(eh, CURLOPT_TIMEVALUE, last_update); + } + +#if LIBCURL_VERSION_NUM >= 0x070a00 + if (feed->ssl_verify_peer == FALSE) { + curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0); + } +#endif + + if(feed->cookies_path != NULL) + curl_easy_setopt(eh, CURLOPT_COOKIEFILE, feed->cookies_path); + + res = curl_easy_perform(eh); + XML_Parse(feed_ctx->parser, "", 0, TRUE); + + if( res != CURLE_OK ) { + feed->fetcherr = g_strdup(curl_easy_strerror(res)); + response_code = FEED_ERR_FETCH; + } else + curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &response_code); + + curl_easy_cleanup(eh); + + /* Cleanup, we should be done. */ + XML_ParserFree(feed_ctx->parser); + g_free(feed_ctx->name); + g_free(feed_ctx->mail); + g_free(feed_ctx); + + return response_code; +} + +void feed_foreach_item(Feed *feed, GFunc func, gpointer data) +{ + g_return_if_fail(feed != NULL); + g_return_if_fail(feed->items != NULL); + + g_slist_foreach(feed->items, func, data); +} + +gboolean feed_prepend_item(Feed *feed, FeedItem *item) +{ + g_return_val_if_fail(feed != NULL, FALSE); + g_return_val_if_fail(item != NULL, FALSE); + + feed->items = g_slist_prepend(feed->items, item); + return TRUE; +} + +gboolean feed_append_item(Feed *feed, FeedItem *item) +{ + g_return_val_if_fail(feed != NULL, FALSE); + g_return_val_if_fail(item != NULL, FALSE); + + feed->items = g_slist_append(feed->items, item); + return TRUE; +} + +gboolean feed_insert_item(Feed *feed, FeedItem *item, gint pos) +{ + g_return_val_if_fail(feed != NULL, FALSE); + g_return_val_if_fail(item != NULL, FALSE); + g_return_val_if_fail(pos < 0, FALSE); + + feed->items = g_slist_insert(feed->items, item, pos); + return TRUE; +} + +gchar *feed_get_cookies_path(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, NULL); + return feed->cookies_path; +} + +void feed_set_cookies_path(Feed *feed, gchar *path) +{ + g_return_if_fail(feed != NULL); + + if( feed->cookies_path != NULL ) { + g_free(feed->cookies_path); + feed->cookies_path = NULL; + } + + feed->cookies_path = (path != NULL ? g_strdup(path) : NULL); +} + +gboolean feed_get_ssl_verify_peer(Feed *feed) +{ + g_return_val_if_fail(feed != NULL, FALSE); + return feed->ssl_verify_peer; +} + +void feed_set_ssl_verify_peer(Feed *feed, gboolean ssl_verify_peer) +{ + g_return_if_fail(feed != NULL); + feed->ssl_verify_peer = ssl_verify_peer; +} diff --git a/src/plugins/rssyl/libfeed/feed.h b/src/plugins/rssyl/libfeed/feed.h new file mode 100644 index 000000000..dc8847d08 --- /dev/null +++ b/src/plugins/rssyl/libfeed/feed.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __FEED_H +#define __FEED_H + +#include +#include + +#define FEED_DEFAULT_TIMEOUT 20 /* In seconds */ + +/* ---------------- Structures */ + +typedef struct _Feed Feed; +typedef struct _FeedItem FeedItem; +typedef struct _FeedParserCtx FeedParserCtx; + +struct _Feed { + gchar *url; + gchar *title; + gchar *description; + gchar *language; + gchar *author; + gchar *generator; + time_t date; + + guint timeout; + gchar *fetcherr; + gchar *cookies_path; + gboolean ssl_verify_peer; + + GSList *items; +}; + +struct _FeedParserCtx { + XML_Parser parser; + guint depth; + guint location; + GString *str; + gchar *name; + gchar *mail; + gboolean id_is_permalink; + + Feed *feed; + FeedItem *curitem; +}; + +typedef enum { + FEED_ERR_NOFEED, + FEED_ERR_NOURL, + FEED_ERR_INIT, + FEED_ERR_FETCH +} FeedErrCodes; + +/* ---------------- Prototypes */ + +Feed *feed_new(gchar *url); +void feed_free(Feed *feed); + +void feed_free_items(Feed *feed); + +void feed_set_timeout(Feed *feed, guint timeout); +guint feed_get_timeout(Feed *feed); + +void feed_set_url(Feed *feed, gchar *url); +gchar *feed_get_url(Feed *feed); + +gchar *feed_get_title(Feed *feed); +void feed_set_title(Feed *feed, gchar *new_title); + +gchar *feed_get_description(Feed *feed); +gchar *feed_get_language(Feed *feed); +gchar *feed_get_author(Feed *feed); +gchar *feed_get_generator(Feed *feed); +gchar *feed_get_fetcherror(Feed *feed); + +gchar *feed_get_cookies_path(Feed *feed); +void feed_set_cookies_path(Feed *feed, gchar *path); + +gboolean feed_get_ssl_verify_peer(Feed *feed); +void feed_set_ssl_verify_peer(Feed *feed, gboolean ssl_verify_peer); + +gint feed_n_items(Feed *feed); +FeedItem *feed_nth_item(Feed *feed, guint n); + +void feed_foreach_item(Feed *feed, GFunc func, gpointer data); + +gboolean feed_prepend_item(Feed *feed, FeedItem *item); +gboolean feed_append_item(Feed *feed, FeedItem *item); +gboolean feed_insert_item(Feed *feed, FeedItem *item, gint pos); + +guint feed_update(Feed *feed, time_t last_update); + +#define FILL(n) g_free(n); n = g_strdup(text); + +#include "feeditem.h" + +#endif /* __FEED_H */ diff --git a/src/plugins/rssyl/libfeed/feeditem.c b/src/plugins/rssyl/libfeed/feeditem.c new file mode 100644 index 000000000..d04831b2a --- /dev/null +++ b/src/plugins/rssyl/libfeed/feeditem.c @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define __USE_GNU + +#include +#include +#include +#include + +#include "feed.h" +#include "feeditem.h" +#include "feeditemenclosure.h" +#include "parser.h" + +/* feed_item_new() + * Initializes a new empty FeedItem struct, setting its parent feed, + * if supplied. */ +FeedItem *feed_item_new(Feed *feed) +{ + FeedItem *item = NULL; + + item = malloc( sizeof(FeedItem) ); + item->url = NULL; + item->title = NULL; + item->title_format = 0; + item->summary = NULL; + item->text = NULL; + item->author = NULL; + item->id = NULL; + item->comments_url = NULL; + item->parent_id = NULL; + item->enclosure = NULL; + + item->sourcetitle = NULL; + item->sourceid = NULL; + item->sourcedate = -1; + + item->id_is_permalink = FALSE; + item->xhtml_content = FALSE; + + item->date_published = -1; + item->date_modified = -1; + + if( feed != NULL ) + item->feed = feed; + + item->data = NULL; + + return item; +} + +void feed_item_free(FeedItem *item) +{ + if( item == NULL ) + return; + + g_free(item->url); + g_free(item->title); + g_free(item->summary); + g_free(item->text); + g_free(item->author); + g_free(item->id); + g_free(item->data); + g_free(item->comments_url); + g_free(item->parent_id); + g_free(item->enclosure); + + g_free(item->sourcetitle); + g_free(item->sourceid); + + g_free(item); + item = NULL; +} + +Feed *feed_item_get_feed(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->feed; +} + +/* URL */ +gchar *feed_item_get_url(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->url; +} + +void feed_item_set_url(FeedItem *item, const gchar *url) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(url != NULL); + + g_free(item->url); + item->url = g_strdup(url); +} + +/* Title */ +gchar *feed_item_get_title(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->title; +} + +void feed_item_set_title(FeedItem *item, const gchar *title) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(title != NULL); + + g_free(item->title); + item->title = g_strdup(title); +} + +/* Title format */ +gint feed_item_get_title_format(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, -1); + return item->title_format; +} + +void feed_item_set_title_format(FeedItem *item, gint format) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(format >= 0 && format <= FEED_ITEM_TITLE_UNKNOWN); + + item->title_format = format; +} + +/* Summary */ +gchar *feed_item_get_summary(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->summary; +} + +void feed_item_set_summary(FeedItem *item, const gchar *summary) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(summary != NULL); + + g_free(item->summary); + item->summary = g_strdup(summary); +} + +/* Text */ +gchar *feed_item_get_text(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->text; +} + +void feed_item_set_text(FeedItem *item, const gchar *text) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(text != NULL); + + g_free(item->text); + item->text = g_strdup(text); +} + +/* Author */ +gchar *feed_item_get_author(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->author; +} + +void feed_item_set_author(FeedItem *item, const gchar *author) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(author != NULL); + + g_free(item->author); + item->author = g_strdup(author); +} + +/* ID */ +gchar *feed_item_get_id(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->id; +} + +void feed_item_set_id(FeedItem *item, const gchar *id) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(id != NULL); + + g_free(item->id); + item->id = g_strdup(id); +} + +/* Comments URL */ +gchar *feed_item_get_comments_url(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->comments_url; +} + +void feed_item_set_comments_url(FeedItem *item, const gchar *url) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(url != NULL); + + g_free(item->comments_url); + item->comments_url = g_strdup(url); +} + +/* Comments URL */ +gchar *feed_item_get_parent_id(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->parent_id; +} + +void feed_item_set_parent_id(FeedItem *item, const gchar *id) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(id != NULL); + + g_free(item->parent_id); + item->parent_id = g_strdup(id); +} + +/* Media enclosure */ +FeedItemEnclosure *feed_item_get_enclosure(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->enclosure; +} + +void feed_item_set_enclosure(FeedItem *item, FeedItemEnclosure *enclosure) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(enclosure != NULL); + g_return_if_fail(enclosure->url != NULL); + g_return_if_fail(enclosure->type != NULL); + + feed_item_enclosure_free(item->enclosure); + item->enclosure = enclosure; +} + +/* Source title (Atom only) */ +gchar *feed_item_get_sourcetitle(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->sourcetitle; +} + +void feed_item_set_sourcetitle(FeedItem *item, const gchar *sourcetitle) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(sourcetitle != NULL); + + g_free(item->sourcetitle); + item->sourcetitle = g_strdup(sourcetitle); +} + +/* Source ID (Atom only) */ +gchar *feed_item_get_sourceid(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, NULL); + return item->sourceid; +} + +void feed_item_set_sourceid(FeedItem *item, const gchar *sourceid) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(sourceid != NULL); + + g_free(item->sourceid); + item->sourceid = g_strdup(sourceid); +} + +/* Source date (Atom only) */ +time_t feed_item_get_sourcedate(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, -1); + return item->sourcedate; +} + +void feed_item_set_sourcedate(FeedItem *item, time_t date) +{ + g_return_if_fail(item != NULL); + item->sourcedate = date; +} + +/* Date published */ +time_t feed_item_get_date_published(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, -1); + return item->date_published; +} + +void feed_item_set_date_published(FeedItem *item, time_t date) +{ + g_return_if_fail(item != NULL); + item->date_published = date; +} + +/* Date modified */ +time_t feed_item_get_date_modified(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, -1); + return item->date_modified; +} + +void feed_item_set_date_modified(FeedItem *item, time_t date) +{ + g_return_if_fail(item != NULL); + item->date_modified = date; +} + +FeedItem *feed_item_copy(FeedItem *item) +{ + FeedItem *nitem; + + g_return_val_if_fail(item != NULL, NULL); + + nitem = feed_item_new(NULL); + + nitem->url = g_strdup(item->url); + nitem->title = g_strdup(item->title); + nitem->summary = g_strdup(item->summary); + nitem->text = g_strdup(item->text); + nitem->author = g_strdup(item->author); + nitem->id = g_strdup(item->id); + nitem->comments_url = g_strdup(item->comments_url); + nitem->parent_id = g_strdup(item->parent_id); + + nitem->enclosure = g_memdup(item->enclosure, sizeof(FeedItemEnclosure)); + + nitem->date_published = item->date_published; + nitem->date_modified = item->date_modified; + + nitem->id_is_permalink = item->id_is_permalink; + nitem->xhtml_content = item->xhtml_content; + + nitem->data = g_memdup(item->data, sizeof(item->data)); + + return nitem; +} + +gboolean feed_item_id_is_permalink(FeedItem *item) +{ + g_return_val_if_fail(item != NULL, FALSE); + + return item->id_is_permalink; +} + +void feed_item_set_id_permalink(FeedItem *item, gboolean permalink) +{ + g_return_if_fail(item != NULL); + + item->id_is_permalink = permalink; +} diff --git a/src/plugins/rssyl/libfeed/feeditem.h b/src/plugins/rssyl/libfeed/feeditem.h new file mode 100644 index 000000000..9125af453 --- /dev/null +++ b/src/plugins/rssyl/libfeed/feeditem.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __FEEDITEM_H +#define __FEEDITEM_H + +#include "feed.h" +#include "feeditemenclosure.h" + +struct _FeedItem { + gchar *url; + gchar *title; + gint title_format; + gchar *summary; + gchar *text; + gchar *author; + gchar *id; + gchar *comments_url; + gchar *parent_id; + + gchar *sourceid; + gchar *sourcetitle; + time_t sourcedate; + + gboolean id_is_permalink; + gboolean xhtml_content; + + FeedItemEnclosure *enclosure; + + time_t date_published; + time_t date_modified; + + Feed *feed; /* feed we belong to */ + + gpointer data; /* application-specific data */ +}; + +#define FEED_ITEM_TITLE_TEXT 0 +#define FEED_ITEM_TITLE_HTML 1 +#define FEED_ITEM_TITLE_XHTML 2 +#define FEED_ITEM_TITLE_UNKNOWN 3 + +FeedItem *feed_item_new(Feed *feed); +void feed_item_free(FeedItem *item); + +Feed *feed_item_get_feed(FeedItem *item); + +gchar *feed_item_get_url(FeedItem *item); +void feed_item_set_url(FeedItem *item, const gchar *url); + +gchar *feed_item_get_title(FeedItem *item); +void feed_item_set_title(FeedItem *item, const gchar *title); + +gint feed_item_get_title_format(FeedItem *item); +void feed_item_set_title_format(FeedItem *item, gint format); + +gchar *feed_item_get_summary(FeedItem *item); +void feed_item_set_summary(FeedItem *item, const gchar *summary); + +gchar *feed_item_get_text(FeedItem *item); +void feed_item_set_text(FeedItem *item, const gchar *text); + +gchar *feed_item_get_author(FeedItem *item); +void feed_item_set_author(FeedItem *item, const gchar *author); + +gchar *feed_item_get_id(FeedItem *item); +void feed_item_set_id(FeedItem *item, const gchar *id); + +gchar *feed_item_get_comments_url(FeedItem *item); +void feed_item_set_comments_url(FeedItem *item, const gchar *url); + +gchar *feed_item_get_parent_id(FeedItem *item); +void feed_item_set_parent_id(FeedItem *item, const gchar *id); + +FeedItemEnclosure *feed_item_get_enclosure(FeedItem *item); +void feed_item_set_enclosure(FeedItem *item, FeedItemEnclosure *enclosure); + +gchar *feed_item_get_sourcetitle(FeedItem *item); +void feed_item_set_sourcetitle(FeedItem *item, const gchar *sourcetitle); + +gchar *feed_item_get_sourceid(FeedItem *item); +void feed_item_set_sourceid(FeedItem *item, const gchar *sourceid); + +time_t feed_item_get_sourcedate(FeedItem *item); +void feed_item_set_sourcedate(FeedItem *item, time_t date); + +time_t feed_item_get_date_published(FeedItem *item); +void feed_item_set_date_published(FeedItem *item, time_t date); + +time_t feed_item_get_date_modified(FeedItem *item); +void feed_item_set_date_modified(FeedItem *item, time_t date); + +FeedItem *feed_item_copy(FeedItem *item); + +gboolean feed_item_id_is_permalink(FeedItem *item); +void feed_item_set_id_permalink(FeedItem *item, gboolean permalink); + +#endif /* __FEEDITEM_H */ diff --git a/src/plugins/rssyl/libfeed/feeditemenclosure.c b/src/plugins/rssyl/libfeed/feeditemenclosure.c new file mode 100644 index 000000000..183cfb5e1 --- /dev/null +++ b/src/plugins/rssyl/libfeed/feeditemenclosure.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define __USE_GNU + +#include +#include + +#include "feeditemenclosure.h" + +FeedItemEnclosure *feed_item_enclosure_new(gchar *url, gchar *type, gulong size) +{ + FeedItemEnclosure *enclosure = NULL; + + g_return_val_if_fail(url != NULL, NULL); + g_return_val_if_fail(type != NULL, NULL); + g_return_val_if_fail(size > 0, NULL); + + enclosure = malloc( sizeof(FeedItemEnclosure) ); + enclosure->url = g_strdup(url); + enclosure->type = g_strdup(type); + enclosure->size = size; + + return enclosure; +} + +void feed_item_enclosure_free(FeedItemEnclosure *enclosure) +{ + if( enclosure == NULL ) + return; + + g_free(enclosure->url); + g_free(enclosure->type); + + g_free(enclosure); + enclosure = NULL; +} + +/* URL */ +gchar *feed_item_enclosure_get_url(FeedItemEnclosure *enclosure) +{ + g_return_val_if_fail(enclosure != NULL, NULL); + return enclosure->url; +} + +void feed_item_enclosure_set_url(FeedItemEnclosure *enclosure, gchar *url) +{ + g_return_if_fail(enclosure != NULL); + g_return_if_fail(url != NULL); + + g_free(enclosure->url); + enclosure->url = g_strdup(url); +} + +/* Type */ +gchar *feed_item_enclosure_get_type(FeedItemEnclosure *enclosure) +{ + g_return_val_if_fail(enclosure != NULL, NULL); + return enclosure->type; +} + +void feed_item_enclosure_set_type(FeedItemEnclosure *enclosure, gchar *type) +{ + g_return_if_fail(enclosure != NULL); + g_return_if_fail(type != NULL); + + g_free(enclosure->type); + enclosure->type = g_strdup(type); +} + +/* Size */ +gulong feed_item_enclosure_get_size(FeedItemEnclosure *enclosure) +{ + g_return_val_if_fail(enclosure != NULL, -1); + return enclosure->size; +} + +void feed_item_enclosure_set_size(FeedItemEnclosure *enclosure, gulong size) +{ + g_return_if_fail(enclosure != NULL); + g_return_if_fail(size > 0); + + enclosure->size = size; +} diff --git a/src/plugins/rssyl/libfeed/feeditemenclosure.h b/src/plugins/rssyl/libfeed/feeditemenclosure.h new file mode 100644 index 000000000..ba4d12765 --- /dev/null +++ b/src/plugins/rssyl/libfeed/feeditemenclosure.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __FEEDITEMENCLOSURE_H +#define __FEEDITEMENCLOSURE_H + +struct _FeedItemEnclosure { + gchar *url; + gchar *type; + gulong size; +}; + +typedef struct _FeedItemEnclosure FeedItemEnclosure; + +FeedItemEnclosure *feed_item_enclosure_new(gchar *url, gchar *type, gulong size); +void feed_item_enclosure_free(FeedItemEnclosure *enclosure); + +gchar *feed_item_enclosure_get_url(FeedItemEnclosure *enclosure); +void feed_item_enclosure_set_url(FeedItemEnclosure *enclosure, gchar *url); + +gchar *feed_item_enclosure_get_type(FeedItemEnclosure *enclosure); +void feed_item_enclosure_set_type(FeedItemEnclosure *enclosure, gchar *type); + +gulong feed_item_enclosure_get_size(FeedItemEnclosure *enclosure); +void feed_item_enclosure_set_size(FeedItemEnclosure *enclosure, gulong size); + +#endif /* __FEEDITEMENCLOSURE_H */ diff --git a/src/plugins/rssyl/libfeed/parser.c b/src/plugins/rssyl/libfeed/parser.c new file mode 100644 index 000000000..1ebc41202 --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "feed.h" + +#include "parser.h" + +static void _handler_set(XML_Parser parser, guint type) +{ + if( parser == NULL ) + return; + + switch(type) { + case FEED_TYPE_RSS_20: + XML_SetElementHandler(parser, + feed_parser_rss20_start, + feed_parser_rss20_end); + break; + + case FEED_TYPE_RDF: + XML_SetElementHandler(parser, + feed_parser_rdf_start, + feed_parser_rdf_end); + break; + + case FEED_TYPE_ATOM_10: + XML_SetElementHandler(parser, + feed_parser_atom10_start, + feed_parser_atom10_end); + break; + } +} + +static void _elparse_start_chooser(void *data, + const gchar *el, const gchar **attr) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + guint feedtype = FEED_TYPE_NONE; + gchar *version; + + if( ctx->depth == 0 ) { + + /* RSS 2.0 detected */ + if( !strcmp(el, "rss") ) { + feedtype = FEED_TYPE_RSS_20; + } else if( !strcmp(el, "rdf:RDF") ) { + feedtype = FEED_TYPE_RDF; + } else if( !strcmp(el, "feed") ) { + + /* ATOM feed detected, let's check version */ + version = feed_parser_get_attribute_value(attr, "xmlns"); + if( !strcmp(version, "http://www.w3.org/2005/Atom") || + !strcmp(version, "https://www.w3.org/2005/Atom") ) + feedtype = FEED_TYPE_ATOM_10; + else + feedtype = FEED_TYPE_ATOM_03; + } + } + + _handler_set(ctx->parser, feedtype); + + ctx->depth++; +} + +static void _elparse_end_dummy(void *data, const gchar *el) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + + if( ctx->str != NULL ) { + g_string_free(ctx->str, TRUE); + ctx->str = NULL; + } + + ctx->depth--; +} + +void libfeed_expat_chparse(void *data, const gchar *s, gint len) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + gchar *buf = NULL; + gint i, xblank = 1; + + buf = malloc(len+1); + strncpy(buf, s, len); + buf[len] = '\0'; + + /* check if the string is blank, ... */ + for( i = 0; i < strlen(buf); i++ ) + if( !isspace(buf[i]) ) + xblank = 0; + + /* ...because we do not want the blanks if we're just starting new GString */ + if( xblank > 0 && ctx->str == NULL ) { + g_free(buf); + return; + } + + if( ctx->str == NULL ) { + ctx->str = g_string_sized_new(len + 1); + } + + g_string_append(ctx->str, buf); + g_free(buf); +} + + +void feed_parser_set_expat_handlers(FeedParserCtx *ctx) +{ + XML_SetUserData(ctx->parser, (void *)ctx); + + XML_SetElementHandler(ctx->parser, + _elparse_start_chooser, + _elparse_end_dummy); + + XML_SetCharacterDataHandler(ctx->parser, + libfeed_expat_chparse); + + XML_SetUnknownEncodingHandler(ctx->parser, feed_parser_unknown_encoding_handler, + NULL); +} + +size_t feed_writefunc(void *ptr, size_t size, size_t nmemb, void *data) +{ + gint len = size * nmemb; + FeedParserCtx *ctx = (FeedParserCtx *)data; + gint status, err; + + status = XML_Parse(ctx->parser, ptr, len, FALSE); + + if( status == XML_STATUS_ERROR ) { + err = XML_GetErrorCode(ctx->parser); + printf("\nExpat: --- %s\n\n", XML_ErrorString(err)); + } + + return len; +} + +gchar *feed_parser_get_attribute_value(const gchar **attr, const gchar *name) +{ + guint i; + + if( attr == NULL && name == NULL ) + return NULL; + + for( i = 0; attr[i] != NULL && attr[i+1] != NULL; i += 2 ) { + if( !strcmp( attr[i], name) ) + return (gchar *)attr[i+1]; + } + + /* We haven't found anything. */ + return NULL; +} + +#define CHARSIZEUTF32 4 + +enum { + LEP_ICONV_OK, + LEP_ICONV_FAILED, + LEP_ICONV_ILSEQ, + LEP_ICONV_INVAL, + LEP_ICONV_UNKNOWN +}; + +static gint giconv_utf32_char(GIConv cd, const gchar *inbuf, size_t insize, + guint32 *p_value) +{ +#ifdef HAVE_ICONV + size_t outsize; + guchar outbuf[CHARSIZEUTF32]; + gchar *outbufp; + gint r, errno; + + outsize = sizeof(outbuf); + outbufp = (gchar *)outbuf; +#ifdef HAVE_ICONV_PROTO_CONST + r = g_iconv(cd, (const gchar **)&inbuf, &insize, + &outbufp, &outsize); +#else + r = g_iconv(cd, (gchar **)&inbuf, &insize, + &outbufp, &outsize); +#endif + if( r == -1 ) { + g_iconv(cd, 0, 0, 0, 0); + switch(errno) { + case EILSEQ: + return LEP_ICONV_ILSEQ; + case EINVAL: + return LEP_ICONV_INVAL; + default: + return LEP_ICONV_UNKNOWN; + } + } else { + guint32 value; + guint i; + + if( (insize > 0) || (outsize > 0) ) + return LEP_ICONV_FAILED; + + value = 0; + for( i = 0; i < sizeof(outbuf); i++ ) { + value = (value << 8) + outbuf[i]; + } + *p_value = value; + return LEP_ICONV_OK; + } +#else + return LEP_ICONV_FAILED; +#endif +} + +static gint feed_parser_setup_unknown_encoding(const gchar *charset, + XML_Encoding *info) +{ + GIConv cd; + gint flag, r; + gchar buf[4]; + guint i, j, k; + guint32 value; + + cd = g_iconv_open("UTF-32BE", charset); + if( cd == (GIConv) -1 ) + return -1; + + flag = 0; + for( i = 0; i < 256; i++ ) { + /* first char */ + buf[0] = i; + info->map[i] = 0; + r = giconv_utf32_char(cd, buf, 1, &value); + if( r == LEP_ICONV_OK) { + info->map[i] = value; + } else if( r != LEP_ICONV_INVAL ) { + } else { + for( j = 0; j < 256; j++ ) { + /* second char */ + buf[1] = j; + r = giconv_utf32_char(cd, buf, 2, &value); + if( r == LEP_ICONV_OK ) { + flag = 1; + info->map[i] = -2; + } else if( r != LEP_ICONV_INVAL ) { + } else { + for( k = 0; k < 256; k++ ) { + /* third char */ + buf[2] = k; + r = giconv_utf32_char(cd, buf, 3, &value); + if( r == LEP_ICONV_OK) { + info->map[i] = -3; + } + } + } + } + } + } + + g_iconv_close(cd); + + return flag; +} + +struct FeedParserUnknownEncoding { + gchar *charset; + GIConv cd; +}; + +static gint feed_parser_unknown_encoding_convert(void *data, const gchar *s) +{ + gint r; + struct FeedParserUnknownEncoding *enc_data; + size_t insize; + guint32 value; + + enc_data = data; + insize = 4; + + if( s == NULL ) + return -1; + + r = giconv_utf32_char(enc_data->cd, s, insize, &value); + if( r != LEP_ICONV_OK ) + return -1; + + return 0; +} + +static void feed_parser_unknown_encoding_data_free(void *data) +{ + struct FeedParserUnknownEncoding *enc_data; + + enc_data = data; + free(enc_data->charset); + g_iconv_close(enc_data->cd); + free(enc_data); +} + +int feed_parser_unknown_encoding_handler(void *encdata, const XML_Char *name, + XML_Encoding *info) +{ + GIConv cd; + struct FeedParserUnknownEncoding *data; + int result; + + result = feed_parser_setup_unknown_encoding(name, info); + if( result == 0 ) { + info->data = NULL; + info->convert = NULL; + info->release = NULL; + return XML_STATUS_OK; + } + + cd = g_iconv_open("UTF-32BE", name); + if( cd == (GIConv)-1 ) + return XML_STATUS_ERROR; + + data = malloc( sizeof(*data) ); + if( data == NULL ) { + g_iconv_close(cd); + return XML_STATUS_ERROR; + } + + data->charset = strdup(name); + if( data->charset == NULL ) { + free(data); + g_iconv_close(cd); + return XML_STATUS_ERROR; + } + + data->cd = cd; + info->data = data; + info->convert = feed_parser_unknown_encoding_convert; + info->release = feed_parser_unknown_encoding_data_free; + + return XML_STATUS_OK; +} diff --git a/src/plugins/rssyl/libfeed/parser.h b/src/plugins/rssyl/libfeed/parser.h new file mode 100644 index 000000000..f8cbb9ae7 --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __PARSER_H + +#include "feed.h" +#include "parser_rss20.h" +#include "parser_rdf.h" +#include "parser_atom10.h" + +void libfeed_expat_chparse(void *data, const gchar *s, gint len); +void feed_parser_set_expat_handlers(FeedParserCtx *ctx); +size_t feed_writefunc(void *ptr, size_t size, size_t nmemb, void *stream); +gchar *feed_parser_get_attribute_value(const gchar **attr, const gchar *name); + +int feed_parser_unknown_encoding_handler(void *encdata, const XML_Char *name, + XML_Encoding *info); + + +enum { + FEED_TYPE_NONE, + FEED_TYPE_RDF, + FEED_TYPE_RSS_20, + FEED_TYPE_ATOM_03, + FEED_TYPE_ATOM_10, + FEED_TYPE_OPML +} FeedTypes; + +#endif /* __PARSER_H */ diff --git a/src/plugins/rssyl/libfeed/parser_atom10.c b/src/plugins/rssyl/libfeed/parser_atom10.c new file mode 100644 index 000000000..59fba1c5c --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_atom10.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#define __USE_GNU + +#include +#include +#include +#include + +#include "feed.h" +#include "feeditem.h" +#include "date.h" +#include "parser.h" +#include "parser_atom10.h" + +void feed_parser_atom10_start(void *data, const gchar *el, const gchar **attr) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + gchar *a = NULL; + + if( ctx->depth == 1 ) { + + if( !strcmp(el, "entry") ) { + /* Start of new feed item found. + * Create a new FeedItem, freeing the one we already have, if any. */ + if( ctx->curitem != NULL ) + feed_item_free(ctx->curitem); + ctx->curitem = feed_item_new(ctx->feed); + ctx->location = FEED_LOC_ATOM10_ENTRY; + } else if( !strcmp(el, "author") ) { + /* Start of author info for the feed found. + * Set correct location. */ + ctx->location = FEED_LOC_ATOM10_AUTHOR; + } else ctx->location = FEED_LOC_ATOM10_NONE; + + } else if( ctx->depth == 2 ) { + + if( !strcmp(el, "author") ) { + /* Start of author info for current feed item. + * Set correct location. */ + ctx->location = FEED_LOC_ATOM10_AUTHOR; + } else if( !strcmp(el, "link") ) { + /* Capture item URL, from the "url" XML attribute. */ + if (ctx->curitem && ctx->location == FEED_LOC_ATOM10_ENTRY) + ctx->curitem->url = g_strdup(feed_parser_get_attribute_value(attr, "href")); + } else if( !strcmp(el, "source") ) { + ctx->location = FEED_LOC_ATOM10_SOURCE; + } else ctx->location = FEED_LOC_ATOM10_ENTRY; + + if( !strcmp(el, "title") ) { + a = feed_parser_get_attribute_value(attr, "type"); + if( !a || !strcmp(a, "text") ) + ctx->curitem->title_format = FEED_ITEM_TITLE_TEXT; + else if( !strcmp(a, "html") ) + ctx->curitem->title_format = FEED_ITEM_TITLE_HTML; + else if( !strcmp(a, "xhtml") ) + ctx->curitem->title_format = FEED_ITEM_TITLE_XHTML; + else + ctx->curitem->title_format = FEED_ITEM_TITLE_UNKNOWN; + } else if (!strcmp(el, "content") ) { + a = feed_parser_get_attribute_value(attr, "type"); + if (a && !strcmp(a, "xhtml")) { + ctx->curitem->xhtml_content = TRUE; + ctx->location = FEED_LOC_ATOM10_CONTENT; + } + } + } + + ctx->depth++; +} + +void feed_parser_atom10_end(void *data, const gchar *el) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + Feed *feed = ctx->feed; + gchar *text = NULL; + + if( ctx->str != NULL ) + text = ctx->str->str; + + ctx->depth--; + + switch( ctx->depth ) { + + case 0: + + if( !strcmp(el, "feed") ) { + /* We have finished parsing the feed, reverse the list + * so it's not upside down. */ + feed->items = g_slist_reverse(ctx->feed->items); + } + + break; + + case 1: + + /* decide if we just received , so we can + * add a complete item to feed */ + if( !strcmp(el, "entry") ) { + + /* append the complete feed item */ + if( ctx->curitem->id && ctx->curitem->title + && ctx->curitem->date_modified ) { + feed->items = + g_slist_prepend(feed->items, (gpointer)ctx->curitem); + } + + /* since it's in the linked list, lose this pointer */ + ctx->curitem = NULL; + + } else if( !strcmp(el, "title") ) { /* so it wasn't end of item */ + FILL(feed->title) + } else if( !strcmp(el, "summary" ) ) { + FILL(feed->description) + } else if( !strcmp(el, "updated" ) ) { + feed->date = parseISO8601Date(text); + } + /* FIXME: add more later */ + + break; + + case 2: + + if( ctx->curitem == NULL ) + break; + + switch(ctx->location) { + + /* We're in feed/entry */ + case FEED_LOC_ATOM10_ENTRY: + if( !strcmp(el, "title") ) { + FILL(ctx->curitem->title) + } else if( !strcmp(el, "summary") ) { + FILL(ctx->curitem->summary) + } else if( !strcmp(el, "content") ) { + if (!ctx->curitem->xhtml_content) + FILL(ctx->curitem->text); + } else if( !strcmp(el, "id") ) { + FILL(ctx->curitem->id); + feed_item_set_id_permalink(ctx->curitem, TRUE); + } else if( !strcmp(el, "published") ) { + ctx->curitem->date_published = parseISO8601Date(text); + } else if( !strcmp(el, "updated") ) { + ctx->curitem->date_modified = parseISO8601Date(text); + } + + break; + + /* We're in feed/author or about to leave feed/entry/author */ + case FEED_LOC_ATOM10_AUTHOR: + if( !strcmp(el, "author" ) ) { + /* We just finished parsing */ + ctx->curitem->author = g_strdup_printf("%s%s%s%s%s", + ctx->name ? ctx->name : "", + ctx->name && ctx->mail ? " <" : ctx->mail ? "<" : "", + ctx->mail ? ctx->mail : "", + ctx->mail ? ">" : "", + !ctx->name && !ctx->mail ? "N/A" : ""); + ctx->location = FEED_LOC_ATOM10_ENTRY; + } else if( !strcmp(el, "name") ) { + FILL(feed->author); + } + + break; + } + + break; + + case 3: + + if( ctx->curitem == NULL ) + break; + + switch(ctx->location) { + + /* We're in feed/entry/author */ + case FEED_LOC_ATOM10_AUTHOR: + if( !strcmp(el, "name") ) { + FILL(ctx->name); + } else if( !strcmp(el, "email") ) { + FILL(ctx->mail); + } + + break; + + /* We're in feed/entry/source */ + case FEED_LOC_ATOM10_SOURCE: + if( !strcmp(el, "title" ) ) { + FILL(ctx->curitem->sourcetitle); + } else if( !strcmp(el, "id" ) ) { + FILL(ctx->curitem->sourceid); + } else if( !strcmp(el, "updated" ) ) { + ctx->curitem->sourcedate = parseISO8601Date(text); + } + + break; + + case FEED_LOC_ATOM10_CONTENT: + if (!strcmp(el, "div") && ctx->curitem->xhtml_content) + FILL(ctx->curitem->text); + break; + + } + + + break; + } + + if( ctx->str != NULL ) { + g_string_free(ctx->str, TRUE); + ctx->str = NULL; + } + ctx->str = NULL; +} diff --git a/src/plugins/rssyl/libfeed/parser_atom10.h b/src/plugins/rssyl/libfeed/parser_atom10.h new file mode 100644 index 000000000..85e95b4c9 --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_atom10.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __PARSER_ATOM03_H +#define __PARSER_ATOM03_H + +void feed_parser_atom10_start(void *data, const char *el, const char **attr); +void feed_parser_atom10_end(void *data, const char *el); + +enum { + FEED_LOC_ATOM10_NONE, + FEED_LOC_ATOM10_ENTRY, + FEED_LOC_ATOM10_AUTHOR, + FEED_LOC_ATOM10_SOURCE, + FEED_LOC_ATOM10_CONTENT +} FeedAtom10Locations; + +#endif /* __PARSER_ATOM03_H */ diff --git a/src/plugins/rssyl/libfeed/parser_opml.c b/src/plugins/rssyl/libfeed/parser_opml.c new file mode 100644 index 000000000..09e34aa49 --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_opml.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include + +#include "feed.h" + +#include "parser.h" +#include "parser_opml.h" + +static void _opml_parser_start(void *data, const gchar *el, const gchar **attr) +{ + OPMLProcessCtx *ctx = (OPMLProcessCtx *)data; + gchar *title = NULL, *type = NULL, *url = NULL, *tmp = NULL; + + if( ctx->body_reached ) { + if( ctx->depth >= 2 && !strcmp(el, "outline") ) { + title = feed_parser_get_attribute_value(attr, "title"); + type = feed_parser_get_attribute_value(attr, "type"); + if( type != NULL && strcmp(type, "folder") ) { + url = feed_parser_get_attribute_value(attr, "xmlUrl"); + + if( url != NULL ) { + if( !strncmp(url, "feed://", 7) ) + tmp = g_strdup(url+7); + else if( !strncmp(url, "feed:", 5) ) + tmp = g_strdup(url+5); + + if( tmp != NULL ) { + g_free(url); + url = tmp; + } + } + } + + if( ctx->user_function != NULL ) { + ctx->user_function(title, url, ctx->depth, ctx->user_data); + } + } + } + + if( ctx->depth == 1 ) { + if( !strcmp(el, "body") ) { + ctx->body_reached = TRUE; + } + } + + ctx->depth++; +} + +static void _opml_parser_end(void *data, const gchar *el) +{ + OPMLProcessCtx *ctx = (OPMLProcessCtx *)data; + + ctx->depth--; +} + +void opml_process(gchar *path, OPMLProcessFunc function, gpointer data) +{ + OPMLProcessCtx *ctx = NULL; + gchar *contents = NULL; + GError *error = NULL; + gint status, err; + + /* Initialize our context */ + ctx = malloc( sizeof(OPMLProcessCtx) ); + ctx->parser = XML_ParserCreate(NULL); + ctx->depth = 0; + ctx->str = NULL; + ctx->user_function = function; + ctx->body_reached = FALSE; + ctx->user_data = data; + + /* Set expat parser handlers */ + XML_SetUserData(ctx->parser, (void *)ctx); + XML_SetElementHandler(ctx->parser, + _opml_parser_start, + _opml_parser_end); + XML_SetCharacterDataHandler(ctx->parser, libfeed_expat_chparse); + XML_SetUnknownEncodingHandler(ctx->parser, + feed_parser_unknown_encoding_handler, NULL); + + g_file_get_contents(path, &contents, NULL, &error); + + if( error || !contents ) + return; + +/* + lines = g_strsplit(contents, '\n', 0); + + while( lines[i] ) { + status = XML_Parse(ctx->parser, lines[i], strlen(lines[i]), FALSE); + if( status == XML_STATUS_ERROR ) { + err = XML_GetErrorCode(ctx->parser); + sprintf(stderr, "\nExpat: --- %s\n\n", XML_ErrorString(err)); + } + } +*/ + + status = XML_Parse(ctx->parser, contents, strlen(contents), FALSE); + err = XML_GetErrorCode(ctx->parser); + fprintf(stderr, "\nExpat: --- %s (%s)\n\n", XML_ErrorString(err), + (status == XML_STATUS_OK ? "OK" : "NOT OK")); + + XML_Parse(ctx->parser, "", 0, TRUE); + + XML_ParserFree(ctx->parser); + g_free(ctx); +} diff --git a/src/plugins/rssyl/libfeed/parser_opml.h b/src/plugins/rssyl/libfeed/parser_opml.h new file mode 100644 index 000000000..518b07236 --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_opml.h @@ -0,0 +1,23 @@ +#ifndef __PARSER_OPML +#define __PARSER_OPML + +#include + +typedef void (*OPMLProcessFunc) (gchar *title, gchar *url, gint depth, + gpointer data); + +struct _OPMLProcessCtx { + XML_Parser parser; + guint depth; + guint prevdepth; + GString *str; + OPMLProcessFunc user_function; + gboolean body_reached; + gpointer user_data; +}; + +typedef struct _OPMLProcessCtx OPMLProcessCtx; + +void opml_process(gchar *path, OPMLProcessFunc function, gpointer data); + +#endif /* __PARSER_OPML */ diff --git a/src/plugins/rssyl/libfeed/parser_rdf.c b/src/plugins/rssyl/libfeed/parser_rdf.c new file mode 100644 index 000000000..dc424c56e --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_rdf.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#define __USE_GNU + +#include +#include +#include + +#include "feed.h" +#include "date.h" +#include "parser_rdf.h" + +void feed_parser_rdf_start(void *data, const gchar *el, const gchar **attr) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + + if( ctx->depth == 1 ) { + if( !strcmp(el, "channel") ) { + ctx->location = FEED_LOC_RDF_CHANNEL; + } else if( !strcmp(el, "item") ) { + + if( ctx->curitem != NULL ) + feed_item_free(ctx->curitem); + + ctx->curitem = feed_item_new(ctx->feed); + ctx->location = FEED_LOC_RDF_ITEM; + + } else ctx->location = 0; + } + + ctx->depth++; + +} + +void feed_parser_rdf_end(void *data, const gchar *el) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + Feed *feed = ctx->feed; + gchar *text = NULL; + + if( ctx->str != NULL ) + text = ctx->str->str; + + ctx->depth--; + + switch( ctx->depth ) { + + case 0: + + if( !strcmp(el, "rdf") ) { + /* we finished parsing the feed */ + ctx->feed->items = g_slist_reverse(ctx->feed->items); + } + + break; + + case 1: + + /* block just ended, so ... */ + if( !strcmp(el, "item") ) { + + /* add the complete feed item to our feed struct */ + ctx->feed->items = + g_slist_prepend(ctx->feed->items, (gpointer)ctx->curitem); + + /* since it's in the linked list, lose this pointer */ + ctx->curitem = NULL; + } + + break; + + case 2: + + switch(ctx->location) { + + /* We're inside introductory */ + case FEED_LOC_RDF_CHANNEL: + if( !strcmp(el, "title") ) { + FILL(feed->title) + } else if( !strcmp(el, "description" ) ) { + FILL(feed->description) + } else if( !strcmp(el, "dc:language") ) { + FILL(feed->language) + } else if( !strcmp(el, "dc:creator") ) { + FILL(feed->author) + } else if( !strcmp(el, "dc:date") ) { + feed->date = parseISO8601Date(text); + } else if( !strcmp(el, "pubDate") ) { + feed->date = parseRFC822Date(text); + } + + break; + + /* We're inside an */ + case FEED_LOC_RDF_ITEM: + if( ctx->curitem == NULL ) { + break; + } + + /* decide which field did we just get */ + if( !strcmp(el, "title") ) { + FILL(ctx->curitem->title) + } else if( !strcmp(el, "dc:creator") ) { + FILL(ctx->curitem->author) + } else if( !strcmp(el, "description") ) { + FILL(ctx->curitem->summary) + } else if( !strcmp(el, "content:encoded") ) { + FILL(ctx->curitem->text) + } else if( !strcmp(el, "link") ) { + FILL(ctx->curitem->url) + } else if( !strcmp(el, "dc:date") ) { + ctx->curitem->date_modified = parseISO8601Date(text); + } else if( !strcmp(el, "pubDate") ) { + ctx->curitem->date_modified = parseRFC822Date(text); + } + + break; + } + + break; + + } + + if( ctx->str != NULL ) { + g_string_free(ctx->str, TRUE); + ctx->str = NULL; + } +} diff --git a/src/plugins/rssyl/libfeed/parser_rdf.h b/src/plugins/rssyl/libfeed/parser_rdf.h new file mode 100644 index 000000000..1c3809fec --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_rdf.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __PARSER_RDF_H +#define __PARSER_RDF_H + +void feed_parser_rdf_start(void *data, const char *el, const char **attr); +void feed_parser_rdf_end(void *data, const char *el); + +enum { + FEED_LOC_RDF_NONE, + FEED_LOC_RDF_CHANNEL, + FEED_LOC_RDF_ITEM +} FeedRdfLocations; + +#endif /* __PARSER_RDF_H */ diff --git a/src/plugins/rssyl/libfeed/parser_rss20.c b/src/plugins/rssyl/libfeed/parser_rss20.c new file mode 100644 index 000000000..3882750a8 --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_rss20.c @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#define __USE_GNU + +#include +#include +#include + +#include "feed.h" +#include "feeditem.h" +#include "feeditemenclosure.h" +#include "date.h" +#include "parser.h" + +void feed_parser_rss20_start(void *data, const gchar *el, const gchar **attr) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + FeedItemEnclosure *enclosure = NULL; + gchar *url, *type, *size_s; + gulong size = -1; + + /* ------------------- */ + if( ctx->depth == 2 ) { + if( !strcmp(el, "item") ) { /* Start of new item */ + + if( ctx->curitem != NULL ) + feed_item_free(ctx->curitem); + + ctx->curitem = feed_item_new(ctx->feed); + + } else ctx->location = 0; + /* ------------------- */ + } else if( ctx->depth == 3 ) { + if( !strcmp(el, "enclosure") ) { /* Media enclosure */ + + url = feed_parser_get_attribute_value(attr, "url"); + type = feed_parser_get_attribute_value(attr, "type"); + size_s = feed_parser_get_attribute_value(attr, "length"); + if( size_s != NULL ) + size = (gulong)atol(size_s); + + if( url != NULL && type != NULL && size > 0 ) { + if( (enclosure = feed_item_enclosure_new(url, type, size)) ) + feed_item_set_enclosure(ctx->curitem, enclosure); + } + + } else if( !strcmp(el, "guid") ) { /* Unique ID */ + type = feed_parser_get_attribute_value(attr, "isPermaLink"); + if( type != NULL && !strcmp(type, "false") ) + feed_item_set_id_permalink(ctx->curitem, TRUE); + } + } else ctx->location = 0; + + ctx->depth++; + +} + +void feed_parser_rss20_end(void *data, const gchar *el) +{ + FeedParserCtx *ctx = (FeedParserCtx *)data; + Feed *feed = ctx->feed; + gchar *text = NULL; + + if( ctx->str != NULL ) + text = ctx->str->str; + else + text = ""; + + ctx->depth--; + + switch( ctx->depth ) { + + /* ------------------- */ + case 0: + + if( !strcmp(el, "rss") ) { + /* we finished parsing the feed */ + ctx->feed->items = g_slist_reverse(ctx->feed->items); + } + + break; + + /* ------------------- */ + case 1: + + break; /* nothing to do at this depth */ + + /* ------------------- */ + case 2: + + /* decide if we just received , so we can + * add a complete item to feed */ + if( !strcmp(el, "item") ) { + + /* append the complete feed item, if it is valid + * "All elements of an item are optional, however at least one + * of title or description must be present." */ + if( ctx->curitem->title != NULL || ctx->curitem->summary != NULL ) { + ctx->feed->items = + g_slist_prepend(ctx->feed->items, (gpointer)ctx->curitem); + } + + /* since it's in the linked list, lose this pointer */ + ctx->curitem = NULL; + + } else if( !strcmp(el, "title") ) { /* so it wasn't end of item */ + FILL(feed->title) + } else if( !strcmp(el, "description" ) ) { + FILL(feed->description) + } else if( !strcmp(el, "dc:language") ) { + FILL(feed->language) + } else if( !strcmp(el, "author") ) { + FILL(feed->author) + } else if( !strcmp(el, "admin:generatorAgent") ) { + FILL(feed->generator) + } else if( !strcmp(el, "dc:date") ) { + feed->date = parseISO8601Date(text); + } else if( !strcmp(el, "pubDate") ) { + feed->date = parseRFC822Date(text); + } + + break; + + /* ------------------- */ + case 3: + + if( ctx->curitem == NULL ) { + break; + } + + /* decide which field did we just get */ + if( !strcmp(el, "title") ) { + FILL(ctx->curitem->title) + } else if( !strcmp(el, "author") ) { + FILL(ctx->curitem->author) + } else if( !strcmp(el, "description") ) { + FILL(ctx->curitem->summary) + } else if( !strcmp(el, "content:encoded") ) { + FILL(ctx->curitem->text) + } else if( !strcmp(el, "link") ) { + FILL(ctx->curitem->url) + } else if( !strcmp(el, "guid") ) { + FILL(ctx->curitem->id) + } else if( !strcmp(el, "wfw:commentRSS") || !strcmp(el, "wfw:commentRss") ) { + FILL(ctx->curitem->comments_url) + } else if( !strcmp(el, "dc:date") ) { + ctx->curitem->date_modified = parseISO8601Date(text); + } else if( !strcmp(el, "pubDate") ) { + ctx->curitem->date_modified = parseRFC822Date(text); + } else if( !strcmp(el, "dc:creator")) { + FILL(ctx->curitem->author) + } + + break; + + } + + if( ctx->str != NULL ) { + g_string_free(ctx->str, TRUE); + ctx->str = NULL; + } +} diff --git a/src/plugins/rssyl/libfeed/parser_rss20.h b/src/plugins/rssyl/libfeed/parser_rss20.h new file mode 100644 index 000000000..27cc40c1f --- /dev/null +++ b/src/plugins/rssyl/libfeed/parser_rss20.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __PARSER_RSS20_H +#define __PARSER_RSS20_H + +void feed_parser_rss20_start(void *data, const char *el, const char **attr); +void feed_parser_rss20_end(void *data, const char *el); + +#endif /* __PARSER_RSS20_H */ diff --git a/src/plugins/rssyl/old_feeds.c b/src/plugins/rssyl/old_feeds.c new file mode 100644 index 000000000..45e6cf635 --- /dev/null +++ b/src/plugins/rssyl/old_feeds.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Expat parser for old feeds.xml */ + +#include +#include +#include + +#include +#include + +#include "libfeed/parser.h" +#include "old_feeds.h" +#include "rssyl.h" + +struct _oldrssyl_ctx { + GSList *oldfeeds; +}; + +static void _elparse_start_oldrssyl(void *data, const gchar *el, + const gchar **attr) +{ + struct _oldrssyl_ctx *ctx = data; + OldRFeed *of; + gchar *tmp; + +#define GETVAL_STR(name) (g_strdup(feed_parser_get_attribute_value(attr, name))) +#define GETVAL_INT(name) \ + ((tmp = feed_parser_get_attribute_value(attr, name)) == NULL ? 0 : \ + (gint)atoi(tmp)) + + if (!strcmp(el, "feed")) { + of = g_new0(OldRFeed, 1); + + of->name = GETVAL_STR("name"); + of->official_name = GETVAL_STR("official_name"); + of->url = GETVAL_STR("url"); + of->default_refresh_interval = GETVAL_INT("default_refresh_interval"); + of->refresh_interval = GETVAL_INT("refresh_interval"); + of->expired_num = GETVAL_INT("expired_num"); + of->fetch_comments = GETVAL_INT("fetch_comments"); + of->fetch_comments_for = GETVAL_INT("fetch_comments_for"); + of->silent_update = GETVAL_INT("silent_update"); + + debug_print("RSSyl: old feeds.xml: Adding '%s' (%s).\n", of->name, + of->url); + + ctx->oldfeeds = g_slist_prepend(ctx->oldfeeds, of); + } + + return; +} + +static void _elparse_end_oldrssyl(void *data, const gchar *el) +{ + return; +} + +GSList *rssyl_old_feed_metadata_parse(gchar *filepath) +{ + XML_Parser parser; + GSList *oldfeeds = NULL; + gchar *contents = NULL; + gsize length; + GError *error; + struct _oldrssyl_ctx *ctx; + + debug_print("RSSyl: Starting to parse old feeds.xml\n"); + + /* Read contents of the file into memory */ + if (!g_file_get_contents(filepath, &contents, &length, &error)) { + alertpanel_error(_("Couldn't read contents of old feeds.xml file:\n%s"), + error->message); + debug_print("RSSyl: Couldn't read contents of feeds.xml\n"); + g_error_free(error); + return NULL; + } + + /* Set up expat parser */ + parser = XML_ParserCreate(NULL); + + ctx = g_new0(struct _oldrssyl_ctx, 1); + ctx->oldfeeds = NULL; + XML_SetUserData(parser, ctx); + XML_SetElementHandler(parser, + _elparse_start_oldrssyl, + _elparse_end_oldrssyl); + + /* Parse the XML, our output ending up in oldfeeds */ + XML_Parse(parser, contents, length, 1); + + /* And clean up */ + XML_ParserFree(parser); + g_free(contents); + oldfeeds = ctx->oldfeeds; + g_free(ctx); + + debug_print("RSSyl: old feeds.xml: added %d items in total\n", + g_slist_length(oldfeeds)); + + return oldfeeds; +} + +static void _free_old_feed_entry(gpointer d, gpointer user_data) +{ + OldRFeed *of = (OldRFeed *)d; + + if (of == NULL) + return; + + g_free(of->name); + g_free(of->official_name); + g_free(of->url); + g_free(of); +} + +void rssyl_old_feed_metadata_free(GSList *oldfeeds) +{ + if (oldfeeds != NULL) { + debug_print("RSSyl: releasing parsed contents of old feeds.xml\n"); + g_slist_foreach(oldfeeds, _free_old_feed_entry, NULL); + g_slist_free(oldfeeds); + oldfeeds = NULL; + } +} + +static gint _old_feed_find_by_url(gconstpointer a, gconstpointer b) +{ + OldRFeed *of = (OldRFeed *)a; + gchar *name = (gchar *)b; + + if (of == NULL || of->name == NULL || of->url == NULL || name == NULL) + return 1; + + return strcmp(of->name, name); +} + +OldRFeed *rssyl_old_feed_get_by_name(GSList *oldfeeds, gchar *name) +{ + GSList *needle; + + g_return_val_if_fail(oldfeeds != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + if ((needle = g_slist_find_custom(oldfeeds, name, _old_feed_find_by_url)) + != NULL) + return (OldRFeed *)needle->data; + + return NULL; +} diff --git a/src/plugins/rssyl/old_feeds.h b/src/plugins/rssyl/old_feeds.h new file mode 100644 index 000000000..b52cd869b --- /dev/null +++ b/src/plugins/rssyl/old_feeds.h @@ -0,0 +1,22 @@ +#ifndef __RSSYL_OLD_FEEDS +#define __RSSYL_OLD_FEEDS + +struct _OldRFeed { + gchar *name; + gchar *official_name; + gchar *url; + gint default_refresh_interval; + gint refresh_interval; + gint expired_num; + gint fetch_comments; + gint fetch_comments_for; + gint silent_update; +}; + +typedef struct _OldRFeed OldRFeed; + +GSList *rssyl_old_feed_metadata_parse(gchar *filepath); +void rssyl_old_feed_metadata_free(GSList *oldfeeds); +OldRFeed *rssyl_old_feed_get_by_name(GSList *oldfeeds, gchar *name); + +#endif /* __RSSYL_OLD_FEEDS */ diff --git a/src/plugins/rssyl/opml.c b/src/plugins/rssyl/opml_export.c similarity index 50% rename from src/plugins/rssyl/opml.c rename to src/plugins/rssyl/opml_export.c index c71cf8bff..7ec9369d6 100644 --- a/src/plugins/rssyl/opml.c +++ b/src/plugins/rssyl/opml_export.c @@ -23,42 +23,35 @@ # include "config.h" #endif -#include +/* Global includes */ #include +#include +#include -#include -#include - -#include "log.h" -#include "folder.h" -#include "folderview.h" +/* Claws Mail includes */ +#include +#include +#include -#include "date.h" -#include "feed.h" +/* Local includes */ +#include "libfeed/date.h" #include "rssyl.h" -#include "strreplace.h" +#include "opml_import.h" +#include "strutils.h" #define RSSYL_OPML_FILE "rssyl-feedlist.opml" -static gint _folder_depth(FolderItem *item) -{ - gint i; - - for( i = 0; item != NULL; item = folder_item_parent(item), i++ ) {} - return i; -} - -struct _RSSylOpmlExportCtx { +struct _RSSylOpmlCtx { FILE *f; gint depth; }; -typedef struct _RSSylOpmlExportCtx RSSylOpmlExportCtx; +typedef struct _RSSylOpmlCtx RSSylOpmlCtx; static void rssyl_opml_export_func(FolderItem *item, gpointer data) { - RSSylOpmlExportCtx *ctx = (RSSylOpmlExportCtx *)data; - RSSylFolderItem *ritem = (RSSylFolderItem *)item; + RSSylOpmlCtx *ctx = (RSSylOpmlCtx *)data; + RFolderItem *ritem = (RFolderItem *)item; gboolean isfolder = FALSE, err = FALSE; gboolean haschildren = FALSE; gchar *indent = NULL, *xmlurl = NULL; @@ -72,10 +65,10 @@ static void rssyl_opml_export_func(FolderItem *item, gpointer data) return; /* Check for depth and adjust indentation */ - depth = _folder_depth(item); + depth = rssyl_folder_depth(item); if( depth < ctx->depth ) { for( ctx->depth--; depth <= ctx->depth; ctx->depth-- ) { - indent = g_strnfill(ctx->depth, '\t'); + indent = g_strnfill(ctx->depth + 1, '\t'); err |= (fprintf(ctx->f, "%s\n", indent) < 0); g_free(indent); } @@ -93,13 +86,13 @@ static void rssyl_opml_export_func(FolderItem *item, gpointer data) if( g_node_n_children(item->node) ) haschildren = TRUE; - indent = g_strnfill(ctx->depth, '\t'); + indent = g_strnfill(ctx->depth + 1, '\t'); tmpname = rssyl_strreplace(item->name, "&", "&"); - if (ritem->official_name != NULL) - tmpoffn = rssyl_strreplace(item->name, "&", "&"); - else - tmpoffn = g_strdup(tmpname); + if( ritem->official_title != NULL ) + tmpoffn = rssyl_strreplace(ritem->official_title, "&", "&"); + else + tmpoffn = g_strdup(tmpname); err |= (fprintf(ctx->f, "%s\n", @@ -114,7 +107,7 @@ static void rssyl_opml_export_func(FolderItem *item, gpointer data) if( err ) { log_warning(LOG_PROTOCOL, - "Error while writing '%s' to feed export list.\n", + _("RSSyl: Error while writing '%s' to feed export list.\n"), item->name); debug_print("Error while writing '%s' to feed_export list.\n", item->name); @@ -124,9 +117,9 @@ static void rssyl_opml_export_func(FolderItem *item, gpointer data) void rssyl_opml_export(void) { FILE *f; - gchar *opmlfile, *tmpdate, *indent; + gchar *opmlfile, *tmp; time_t tt = time(NULL); - RSSylOpmlExportCtx *ctx = NULL; + RSSylOpmlCtx *ctx = NULL; gboolean err = FALSE; opmlfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, @@ -137,14 +130,14 @@ void rssyl_opml_export(void) if( (f = g_fopen(opmlfile, "w")) == NULL ) { log_warning(LOG_PROTOCOL, - "Couldn't open file '%s' for feed list exporting: %s\n", + _("RSSyl: Couldn't open file '%s' for feed list exporting: %s\n"), opmlfile, g_strerror(errno)); - debug_print("Couldn't open feed list export file, returning.\n"); + debug_print("RSSyl: Couldn't open feed list export file, returning.\n"); g_free(opmlfile); return; } - tmpdate = createRFC822Date(&tt); + tmp = createRFC822Date(&tt); /* Write OPML header */ err |= (fprintf(f, @@ -155,20 +148,22 @@ void rssyl_opml_export(void) "\t\t%s\n" "\t\n" "\t\n", - tmpdate) < 0); - g_free(tmpdate); + tmp) < 0); + g_free(tmp); - ctx = g_new0(RSSylOpmlExportCtx, 1); + ctx = g_new0(RSSylOpmlCtx, 1); ctx->f = f; ctx->depth = 1; folder_func_to_all_folders( (FolderItemFunc)rssyl_opml_export_func, ctx); - for( ctx->depth--; ctx->depth >= 2; ctx->depth-- ) { - indent = g_strnfill(ctx->depth, '\t'); - err |= (fprintf(ctx->f, "%s\n", indent) < 0); - g_free(indent); + /* Print close all open tags if needed. */ + while( ctx->depth > 2 ) { + ctx->depth--; + tmp = g_strnfill(ctx->depth, '\t'); + err |= (fprintf(f, "%s\n", tmp) < 0); + g_free(tmp); } err |= (fprintf(f, @@ -176,84 +171,13 @@ void rssyl_opml_export(void) "\n") < 0); if( err ) { - log_warning(LOG_PROTOCOL, "Error during writing feed export file.\n"); - debug_print("Error during writing feed export file."); + log_warning(LOG_PROTOCOL, _("RSSyl: Error during writing feed export file.\n")); + debug_print("RSSyl: Error during writing feed export file.\n"); } - debug_print("Feed export finished.\n"); + debug_print("RSSyl: Feed export finished.\n"); fclose(f); g_free(opmlfile); g_free(ctx); } - -static void rssyl_opml_import_node(xmlNodePtr node, - FolderItem *parent, gint depth) -{ - xmlNodePtr curn; - gchar *url = NULL, *title = NULL, *nodename = NULL; - FolderItem *item = NULL; - - if( node == NULL ) - return; - - for( curn = node; curn; curn = curn->next ) { - nodename = g_ascii_strdown((gchar *)curn->name, -1); - if( curn->type == XML_ELEMENT_NODE && - !strcmp(nodename, "outline") ) { - - url = (gchar *)xmlGetProp(curn, (xmlChar *)"xmlUrl"); - title = (gchar *)xmlGetProp(curn, (xmlChar *)"title"); - if (!title) - title = (gchar *)xmlGetProp(curn, (xmlChar *)"text"); - - debug_print("Adding '%s' (%s)\n", title, (url ? url : "folder") ); - if( url != NULL ) - item = rssyl_subscribe_new_feed(parent, url, FALSE); - else if (title != NULL) - item = folder_create_folder(parent, title); - else - item = NULL; - if (item) - rssyl_opml_import_node(curn->children, item, depth + 1); - } - g_free(nodename); - } -} - -void rssyl_opml_import(const gchar *opmlfile, FolderItem *parent) -{ - xmlDocPtr doc; - xmlNodePtr node; - xmlXPathContextPtr context; - xmlXPathObjectPtr result; - gchar *rootnode = NULL; - - doc = xmlParseFile(opmlfile); - if( doc == NULL ) - return; - - node = xmlDocGetRootElement(doc); - rootnode = g_ascii_strdown((gchar *)node->name, -1); - if( !strcmp(rootnode, "opml") ) { - gchar *xpath = "/opml/body"; - context = xmlXPathNewContext(doc); - if( !(result = xmlXPathEval((xmlChar *)xpath, context)) ) { - g_free(rootnode); - xmlFreeDoc(doc); - return; - } - - node = result->nodesetval->nodeTab[0]; - - debug_print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); - rssyl_opml_import_node(node->children, parent, 2); - debug_print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"); - - xmlXPathFreeNodeSetList(result); - xmlXPathFreeContext(context); - xmlFreeDoc(doc); - } - - g_free(rootnode); -} diff --git a/src/plugins/rssyl/opml.h b/src/plugins/rssyl/opml_export.h similarity index 64% rename from src/plugins/rssyl/opml.h rename to src/plugins/rssyl/opml_export.h index 9a8aef299..6e8914992 100644 --- a/src/plugins/rssyl/opml.h +++ b/src/plugins/rssyl/opml_export.h @@ -4,6 +4,5 @@ #include "rssyl.h" void rssyl_opml_export(void); -void rssyl_opml_import(const gchar *opmlfile, FolderItem *parent); #endif /* __RSSYL_OPML */ diff --git a/src/plugins/rssyl/opml_import.c b/src/plugins/rssyl/opml_import.c new file mode 100644 index 000000000..2a34f5d77 --- /dev/null +++ b/src/plugins/rssyl/opml_import.c @@ -0,0 +1,110 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * - OPML import handling + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include +#include +#include + +/* Local includes */ +#include "rssyl_feed.h" +#include "opml_import.h" + +gint rssyl_folder_depth(FolderItem *item) +{ + gint i; + + for( i = -1; item != NULL; item = folder_item_parent(item), i++ ) {} + return i; +} + +/* This gets called from the libfeed's OPML parser as a user function for + * each from the .opml file. + * It creates a folder or subscribes a feed, while keeping track of the + * location inside folder hierarchy (using depth and linked list of parent + * folders up to the root). */ +void rssyl_opml_import_func(gchar *title, gchar *url, gint depth, gpointer data) +{ + OPMLImportCtx *ctx = (OPMLImportCtx *)data; + gchar *tmp = NULL; + FolderItem *new_item; + gboolean nulltitle = FALSE; + gint i = 1; + + debug_print("depth %d, ctx->depth %d\n", depth, ctx->depth); + while (depth < ctx->depth) { + /* We've gone up at least one level, need to find correct parent */ + ctx->current = g_slist_delete_link(ctx->current, ctx->current); + ctx->depth--; + } + + debug_print("OPML_IMPORT: %s %s (%s)\n", + (url != NULL ? "feed": "folder"), title, url); + + if( title == NULL ) { + debug_print("NULL title received, substituting a placeholder title\n"); + title = g_strdup(_("Untitled")); + nulltitle = TRUE; + } + + /* If URL is not given, then it's a folder */ + if( url == NULL ) { + /* Find an unused name for new folder */ + tmp = g_strdup(title); + while (folder_find_child_item_by_name((FolderItem *)ctx->current->data, tmp)) { + debug_print("RSSyl: Folder '%s' already exists, trying another name\n", + title); + g_free(tmp); + tmp = g_strdup_printf("%s__%d", title, ++i); + } + + /* Create the folder */ + new_item = folder_create_folder((FolderItem *)ctx->current->data, tmp); + if (!new_item) { + alertpanel_error(_("Can't create the folder '%s'."), tmp); + g_free(tmp); + } + + if (nulltitle) { + g_free(title); + title = NULL; + } + + ctx->current = g_slist_prepend(ctx->current, new_item); + ctx->depth++; + } else { + /* We have URL, try to add new feed... */ + new_item = rssyl_feed_subscribe_new((FolderItem *)ctx->current->data, + url, TRUE); + /* ...and rename it if needed */ + if (new_item != NULL && strcmp(title, new_item->name)) + folder_item_rename(new_item, title); + } +} diff --git a/src/plugins/rssyl/opml_import.h b/src/plugins/rssyl/opml_import.h new file mode 100644 index 000000000..91b71aa33 --- /dev/null +++ b/src/plugins/rssyl/opml_import.h @@ -0,0 +1,15 @@ +#ifndef __OPML_IMPORT +#define __OPML_IMPORT + +struct _OPMLImportCtx { + GSList *current; + gint depth; + gint failures; +}; + +typedef struct _OPMLImportCtx OPMLImportCtx; + +gint rssyl_folder_depth(FolderItem *item); +void rssyl_opml_import_func(gchar *title, gchar *url, gint depth, gpointer data); + +#endif /* __OPML_IMPORT */ diff --git a/src/plugins/rssyl/parse822.c b/src/plugins/rssyl/parse822.c new file mode 100644 index 000000000..846fb6dd5 --- /dev/null +++ b/src/plugins/rssyl/parse822.c @@ -0,0 +1,352 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include +#include + +/* Claws Mail includes */ +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feed.h" +#include "libfeed/feeditem.h" +#include "libfeed/date.h" +#include "parse822.h" +#include "rssyl_feed.h" +#include "rssyl_parse_feed.h" +#include "strutils.h" + +/* rssyl_parse_folder_item_file() + * + * Parse a RFC822-formatted feed item given by "path", and returns a + * pointer to a newly-allocated FeedItem struct, which contains all required data. + * + */ +FeedItem *rssyl_parse_folder_item_file(gchar *path) +{ + gchar *contents, **lines, **line, **splid; + GError *error = NULL; + FeedItem *item; + RFeedCtx *ctx; + gint i = 0; + gboolean parsing_headers = TRUE, past_html_tag = FALSE, past_endhtml_tag = FALSE; + gboolean started_author = FALSE, started_subject = FALSE; + gboolean started_link = FALSE, started_clink = FALSE, got_original_title = FALSE; + + debug_print("RSSyl: parsing '%s'\n", path); + + g_file_get_contents(path, &contents, NULL, &error); + + if( error ) + g_warning("GError: '%s'\n", error->message); + + if( contents != NULL ) { + lines = strsplit_no_copy(contents, '\n'); + } else { + g_warning("Badly formatted file found, ignoring: '%s'\n", path); + return NULL; + } + + ctx = g_new0(RFeedCtx, 1); + ctx->path = g_strdup(path); /* store filesystem path to source file */ + ctx->last_seen = 0; + + item = feed_item_new(NULL); + item->data = ctx; + + while( lines[i] ) { + if( parsing_headers && lines[i] && !strlen(lines[i]) ) { + parsing_headers = FALSE; + debug_print("RSSyl: finished parsing headers\n"); + } + + if( parsing_headers ) { + line = g_strsplit(lines[i], ": ", 2); + if( line[0] && line[1] && strlen(line[0]) && lines[i][0] != ' ') { + started_author = FALSE; + started_subject = FALSE; + started_link = FALSE; + started_clink = FALSE; + + /* Author */ + if( !strcmp(line[0], "From") ) { + feed_item_set_author(item, line[1]); + debug_print("RSSyl: got author '%s'\n", feed_item_get_author(item)); + started_author = TRUE; + } + + /* Date */ + if( !strcmp(line[0], "Date") ) { + feed_item_set_date_modified(item, + procheader_date_parse(NULL, line[1], 0)); + debug_print("RSSyl: got date \n" ); + } + + /* Title */ + if( !strcmp(line[0], "Subject") && !got_original_title ) { + feed_item_set_title(item,line[1]); + debug_print("RSSyl: got title '%s'\n", feed_item_get_title(item)); + started_subject = TRUE; + } + + /* Original (including HTML) title - Atom feeds */ + if( !strcmp(line[0], "X-RSSyl-OrigTitle") ) { + feed_item_set_title(item, line[1]); + debug_print("RSSyl: got original title '%s'\n", + feed_item_get_title(item)); + got_original_title = TRUE; + } + + /* URL */ + if( !strcmp(line[0], "X-RSSyl-URL") ) { + feed_item_set_url(item, line[1]); + debug_print("RSSyl: got link '%s'\n", feed_item_get_url(item)); + started_link = TRUE; + } + + /* Last-Seen timestamp */ + if( !strcmp(line[0], "X-RSSyl-Last-Seen") ) { + ctx->last_seen = atol(line[1]); + debug_print("RSSyl: got last_seen timestamp %ld\n", ctx->last_seen); + } + + /* ID */ + if( !strcmp(line[0], "Message-ID") ) { + splid = g_strsplit_set(line[1], "<>", 3); + if( strlen(splid[1]) != 0 ) + feed_item_set_id(item, splid[1]); + g_strfreev(splid); + } + + /* Feed comments */ + if( !strcmp(line[0], "X-RSSyl-Comments") ) { + feed_item_set_comments_url(item, line[1]); + debug_print("RSSyl: got clink '%s'\n", feed_item_get_comments_url(item)); + started_clink = TRUE; + } + + /* References */ + if( !strcmp(line[0], "References") ) { + splid = g_strsplit_set(line[1], "<>", 3); + if( strlen(splid[1]) != 0 ) + feed_item_set_parent_id(item, line[1]); + g_strfreev(splid); + } + + } else if (lines[i][0] == ' ') { + gchar *tmp = NULL; + /* continuation line */ + if (started_author) { + tmp = g_strdup_printf("%s %s", feed_item_get_author(item), lines[i]+1); + feed_item_set_author(item, tmp); + debug_print("RSSyl: updated author to '%s'\n", tmp); + g_free(tmp); + } else if (started_subject) { + tmp = g_strdup_printf("%s %s", feed_item_get_title(item), lines[i]+1); + feed_item_set_title(item, tmp); + debug_print("RSSyl: updated title to '%s'\n", tmp); + g_free(tmp); + } else if (started_link) { + tmp = g_strdup_printf("%s%s", feed_item_get_url(item), lines[i]+1); + feed_item_set_url(item, tmp); + debug_print("RSSyl: updated link to '%s'\n", tmp); + g_free(tmp); + } else if (started_clink) { + tmp = g_strdup_printf("%s%s", feed_item_get_comments_url(item), lines[i]+1); + feed_item_set_comments_url(item, tmp); + debug_print("RSSyl: updated comments_link to '%s'\n", tmp); + } + } + g_strfreev(line); + } else { + if( !strcmp(lines[i], RSSYL_TEXT_START) ) { + debug_print("RSSyl: Leading html tag found at line %d\n", i); + past_html_tag = TRUE; + i++; + continue; + } + while( past_html_tag && !past_endhtml_tag && lines[i] ) { + if( !strcmp(lines[i], RSSYL_TEXT_END) ) { + debug_print("RSSyl: Trailing html tag found at line %d\n", i); + past_endhtml_tag = TRUE; + i++; + continue; + } + if( feed_item_get_text(item) != NULL ) { + gint e_len, n_len; + e_len = strlen(item->text); + n_len = strlen(lines[i]); + item->text = g_realloc(item->text, e_len + n_len + 2); + *(item->text+e_len) = '\n'; + strcpy(item->text+e_len+1, lines[i]); + *(item->text+e_len+n_len+1) = '\0'; + } else { + item->text = g_strdup(lines[i]); + } + i++; + } + + if( lines[i] == NULL ) + return item; + } + + i++; + } + g_free(lines); + g_free(contents); + return item; +} + +static void rssyl_flush_folder_func(gpointer data, gpointer user_data) +{ + FeedItem *item = (FeedItem *)data; + RFeedCtx *ctx = (RFeedCtx *)item->data; + + if( ctx != NULL && ctx->path != NULL) + g_free(ctx->path); + feed_item_free(item); +} + +static void rssyl_folder_read_existing_real(RFolderItem *ritem) +{ + gchar *path = NULL, *fname = NULL; + DIR *dp; + struct dirent *d; + struct stat st; + gint num; + FeedItem *item = NULL; + RFeedCtx *ctx; + + g_return_if_fail(ritem != NULL); + + path = folder_item_get_path(&ritem->item); + g_return_if_fail(path != NULL); + + debug_print("RSSyl: reading existing items from '%s'\n", path); + + /* Flush contents if any, so we can add new */ + if( g_slist_length(ritem->items) > 0 ) { + g_slist_foreach(ritem->items, (GFunc)rssyl_flush_folder_func, NULL); + g_slist_free(ritem->items); + } + ritem->items = NULL; + ritem->last_update = 0; + + if( (dp = opendir(path)) == NULL ) { + FILE_OP_ERROR(path, "opendir"); + g_free(path); + return; + } + + while( (d = readdir(dp)) != NULL ) { + if( claws_is_exiting() ) { + closedir(dp); + g_free(path); + return; + } + + if( d->d_name[0] != '.' && (num = to_number(d->d_name)) > 0 ) { + fname = g_strdup_printf("%s%c%s", path, G_DIR_SEPARATOR, d->d_name); + if( g_stat(fname, &st) < 0 ) { + debug_print("RSSyl: couldn't stat() file '%s', ignoring it\n", fname); + g_free(fname); + continue; + } + + if( !S_ISREG(st.st_mode)) { + debug_print("RSSyl: not a regular file: '%s', ignoring it\n", fname); + g_free(fname); + continue; + } + + debug_print("RSSyl: starting to parse '%s'\n", d->d_name); + if( (item = rssyl_parse_folder_item_file(fname)) != NULL ) { + /* Find latest timestamp */ + ctx = (RFeedCtx *)item->data; + if( ritem->last_update < ctx->last_seen ) + ritem->last_update = ctx->last_seen; + debug_print("RSSyl: Appending '%s'\n", feed_item_get_title(item)); + ritem->items = g_slist_prepend(ritem->items, item); + } + g_free(fname); + } + } + + closedir(dp); + g_free(path); + + ritem->items = g_slist_reverse(ritem->items); +} + +#ifdef USE_PTHREAD +static void *rssyl_read_existing_thr(void *arg) +{ + RParseCtx *ctx = (RParseCtx *)arg; + + rssyl_folder_read_existing_real(ctx->ritem); + ctx->ready = TRUE; + return NULL; +} +#endif + +void rssyl_folder_read_existing(RFolderItem *ritem) +{ +#ifdef USE_PTHREAD + RParseCtx *ctx; + pthread_t pt; +#endif + + g_return_if_fail(ritem != NULL); + + +#ifdef USE_PTHREAD + ctx = g_new0(RParseCtx, 1); + ctx->ritem = ritem; + ctx->ready = FALSE; + + if( pthread_create(&pt, PTHREAD_CREATE_JOINABLE, rssyl_read_existing_thr, + (void *)ctx) != 0 ) { + /* Couldn't create thread, let's continue non-threaded. */ + rssyl_folder_read_existing_real(ritem); + } else { + /* Thread started, wait until it is done. */ + debug_print("RSSyl: waiting for thread to finish\n"); + while( !ctx->ready ) { + claws_do_idle(); + } + + debug_print("RSSyl: thread finished\n"); + pthread_join(pt, NULL); + } + + g_free(ctx); +#else + rssyl_folder_read_existing_real(ritem); +#endif +} diff --git a/src/plugins/rssyl/parse822.h b/src/plugins/rssyl/parse822.h new file mode 100644 index 000000000..94318fc73 --- /dev/null +++ b/src/plugins/rssyl/parse822.h @@ -0,0 +1,19 @@ +#ifndef __RSSYL_PARSE822_H +#define __RSSYL_PARSE822_H + +#include + +#include "libfeed/feeditem.h" +#include "rssyl.h" + +struct _RFeedCtx { + gchar *path; + time_t last_seen; +}; + +typedef struct _RFeedCtx RFeedCtx; + +FeedItem *rssyl_parse_folder_item_file(gchar *path); +void rssyl_folder_read_existing(RFolderItem *ritem); + +#endif /* __RSSYL_PARSE822_H */ diff --git a/src/plugins/rssyl/parsers.c b/src/plugins/rssyl/parsers.c deleted file mode 100644 index bc491ae04..000000000 --- a/src/plugins/rssyl/parsers.c +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client - * Copyright (C) 1999-2004 Hiroyuki Yamamoto - * This file (C) 2005 Andrej Kacian - * - * - various feed parsing functions - * - this file could use some sorting and/or splitting - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include -#include -#include - -#include "date.h" -#include "feed.h" -#include "strreplace.h" -#include "utils.h" -#include "procheader.h" - -gint rssyl_parse_rdf(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent) -{ - xmlNodePtr rnode, node, n; - RSSylFeedItem *fitem = NULL; - gint count = 0; - gchar *content = NULL; - g_return_val_if_fail(doc != NULL, 0); - g_return_val_if_fail(ritem != NULL, 0); -#ifdef RSSYL_DEBUG - gchar *fetched = NULL; -#endif /* RSSYL_DEBUG */ - - if( ritem->contents == NULL ) - rssyl_read_existing(ritem); - - rnode = xmlDocGetRootElement(doc); - - for( node = rnode->children; node; node = node->next ) { - if( !xmlStrcmp(node->name, "item") ) { - /* We've found an "item" tag, let's poke through its contents */ - fitem = g_new0(RSSylFeedItem, 1); - fitem->date = 0; -#ifdef RSSYL_DEBUG - fetched = xmlGetProp(rnode, "fetched"); - fitem->debug_fetched = atoll(fetched); - xmlFree(fetched); -#endif /* RSSYL_DEBUG */ - - for( n = node->children; n; n = n->next ) { - /* Title */ - if( !xmlStrcmp(n->name, "title") ) { - content = xmlNodeGetContent(n); - fitem->title = rssyl_format_string(content, TRUE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - RDF title is '%s'\n", fitem->title); - } - - /* Text */ - if( !xmlStrcmp(n->name, "description") ) { - content = xmlNodeGetContent(n); - fitem->text = rssyl_format_string(content, FALSE, FALSE); - xmlFree(content); - debug_print("RSSyl: XML - got RDF text\n"); - } - - /* URL */ - if( !xmlStrcmp(n->name, "link") ) { - content = xmlNodeGetContent(n); - fitem->link = rssyl_format_string(content, FALSE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - RDF link is '%s'\n", fitem->link); - } - - /* Date - rfc822 format */ - if( !xmlStrcmp(n->name, "pubDate") ) { - content = xmlNodeGetContent(n); - fitem->date = procheader_date_parse(NULL, content, 0); - xmlFree(content); - if( fitem->date > 0 ) { - debug_print("RSSyl: XML - RDF pubDate found\n" ); - } else - fitem->date = 0; - } - /* Date - ISO8701 format */ - if( !xmlStrcmp(n->name, "date") && - (n->ns && n->ns->prefix && (!xmlStrcmp(n->ns->prefix, "ns") - || !xmlStrcmp(n->ns->prefix, "dc"))) ) { - content = xmlNodeGetContent(n); - fitem->date = parseISO8601Date(content); - xmlFree(content); - debug_print("RSSyl: XML - RDF date found\n" ); - } - - /* Author */ - if( !xmlStrcmp(n->name, "creator") ) { - content = xmlNodeGetContent(n); - fitem->author = rssyl_format_string(content, TRUE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - RDF author is '%s'\n", fitem->author); - } - } - } - - if( fitem && fitem->link && fitem->title ) { - if (rssyl_add_feed_item(ritem, fitem) == FALSE) { - rssyl_free_feeditem(fitem); - fitem = NULL; - } - fitem = NULL; - count++; - } - } - - return count; -} - - -/* rssyl_parse_rss() - * - * This is where we parse the fetched rss document and create a - * RSSylFolderItem from it. Returns number of parsed items - */ -gint rssyl_parse_rss(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent) -{ - xmlXPathContextPtr context; - xmlXPathObjectPtr result; - xmlNodePtr node, n, rnode; - gint i, count = 0; - RSSylFeedItem *fitem = NULL; - gchar *xpath; - gboolean got_encoded, got_author; - gchar *rootnode = NULL; - RSSylFeedItemMedia *media; - gchar *media_url, *media_type; - gulong media_size = 0; -#ifdef RSSYL_DEBUG - gchar *fetched = NULL; -#endif /* RSSYL_DEBUG */ - - g_return_val_if_fail(doc != NULL, 0); - g_return_val_if_fail(ritem != NULL, 0); - - if( ritem->contents == NULL ) - rssyl_read_existing(ritem); - - rnode = xmlDocGetRootElement(doc); - - rootnode = g_ascii_strdown(rnode->name, -1); - xpath = g_strconcat("/", rootnode, - "/channel/item", NULL); - g_free(rootnode); - context = xmlXPathNewContext(doc); - if( !(result = xmlXPathEvalExpression(xpath, context)) ){ - debug_print("RSSyl: XML - no result found for '%s'\n", xpath); - xmlXPathFreeContext(context); - g_free(xpath); - return 0; - } - - g_free(xpath); - - for( i = 0; i < result->nodesetval->nodeNr; i++ ) { - node = result->nodesetval->nodeTab[i]; - - if ((n = node->children) == NULL) - continue; - - fitem = g_new0(RSSylFeedItem, 1); - fitem->media = NULL; - fitem->date = 0; -#ifdef RSSYL_DEBUG - fetched = xmlGetProp(rnode, "fetched"); - fitem->debug_fetched = atoll(fetched); - xmlFree(fetched); -#endif /* RSSYL_DEBUG */ - fitem->text = NULL; - - if (parent) - fitem->parent_link = g_strdup(parent); - - got_encoded = FALSE; - got_author = FALSE; - do { - gchar *content = NULL; - - /* Title */ - if( !xmlStrcmp(n->name, "title") ) { - content = xmlNodeGetContent(n); - fitem->title = rssyl_format_string(content, TRUE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - item title: '%s'\n", fitem->title); - } - - /* Text */ - if( !xmlStrcmp(n->name, "description") ) { - if( (fitem->text == NULL) && (got_encoded == FALSE) ) { - content = xmlNodeGetContent(n); - debug_print("RSSyl: XML - item text (description) caught\n"); - fitem->text = rssyl_format_string(content, FALSE, FALSE); - xmlFree(content); - } - } - if( !xmlStrcmp(n->name, "encoded") && - (n->ns && n->ns->prefix && !xmlStrcmp(n->ns->prefix, "content")) ) { - debug_print("RSSyl: XML - item text (content) caught\n"); - - if (fitem->text != NULL) - g_free(fitem->text); /* free "description" */ - - content = xmlNodeGetContent(n); - fitem->text = rssyl_format_string(content, FALSE, FALSE); - xmlFree(content); - got_encoded = TRUE; - } - - /* URL link to the original post */ - if( !xmlStrcmp(n->name, "link") && - (!n->ns || !n->ns->prefix || !strlen(n->ns->prefix)) ) { - content = xmlNodeGetContent(n); - fitem->link = rssyl_format_string(content, FALSE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - item link: '%s'\n", fitem->link); - } - - /* GUID - sometimes used as link */ - if( !xmlStrcmp(n->name, "guid") ) { - gchar *tmp = xmlGetProp(n, "isPermaLink"); - content = xmlNodeGetContent(n); - fitem->id_is_permalink = FALSE; - if( !tmp || xmlStrcmp(tmp, "false") ) /* permalink? */ - fitem->id_is_permalink = TRUE; - fitem->id = rssyl_format_string(content, FALSE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - item guid: '%s'\n", fitem->id); - xmlFree(tmp); - } - - /* Date - rfc822 format */ - if( !xmlStrcmp(n->name, "pubDate") ) { - content = xmlNodeGetContent(n); - fitem->date = procheader_date_parse(NULL, content, 0); - xmlFree(content); - if( fitem->date > 0 ) { - debug_print("RSSyl: XML - item date found: %d\n", (gint)fitem->date); - } else - fitem->date = 0; - } - /* Date - ISO8701 format */ - if( !xmlStrcmp(n->name, "date") && !xmlStrcmp(n->ns->prefix, "dc") ) { - content = xmlNodeGetContent(n); - fitem->date = parseISO8601Date(content); - xmlFree(content); - debug_print("RSSyl: XML - item date found\n" ); - } - - /* Author */ - if( !xmlStrcmp(n->name, "author") ) { - content = xmlNodeGetContent(n); - fitem->author = rssyl_format_string(content, TRUE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - item author: '%s'\n", fitem->author); - got_author = TRUE; - } - - if( !xmlStrcmp(n->name, "creator") - && !xmlStrcmp(n->ns->prefix, "dc") && !got_author) { - content = xmlNodeGetContent(n); - fitem->author = rssyl_format_string(content, TRUE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - item author (creator): '%s'\n", fitem->author); - } - - /* Media enclosure */ - if( !xmlStrcmp(n->name, "enclosure") ) { - gchar *tmp = xmlGetProp(n, "length"); - media_url = xmlGetProp(n, "url"); - media_type = xmlGetProp(n, "type"); - media_size = (tmp ? atoi(tmp) : 0); - xmlFree(tmp); - - if( media_url != NULL && - media_type != NULL && - media_size != 0 ) { - debug_print("RSSyl: XML - enclosure: '%s' [%s] (%ld)\n", - media_url, media_type, media_size); - media = g_new(RSSylFeedItemMedia, 1); - media->url = media_url; - media->type = media_type; - media->size = media_size; - fitem->media = media; - } else { - debug_print("RSSyl: XML - enclosure found, but some data is missing\n"); - g_free(media_url); - g_free(media_type); - } - } - - /* Comments */ - if( !xmlStrcmp(n->name, "commentRSS") || !xmlStrcmp(n->name, "commentRss") ) { - content = xmlNodeGetContent(n); - fitem->comments_link = rssyl_format_string(content, FALSE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - comments_link: '%s'\n", fitem->comments_link); - } - } while( (n = n->next) != NULL); - - if( (fitem->link || fitem->id) && fitem->title ) { - if (rssyl_add_feed_item(ritem, fitem) == FALSE) { - rssyl_free_feeditem(fitem); - fitem = NULL; - } - count++; - } - } - - xmlXPathFreeObject(result); - xmlXPathFreeContext(context); - - return count; -} - -/* rssyl_parse_atom() - * - * This is where we parse the fetched atom document and create a - * RSSylFolderItem from it. Returns number of parsed items - */ -gint rssyl_parse_atom(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent) -{ - xmlNodePtr node, n, h; - xmlBufferPtr buf = NULL; - gint count = 0; - RSSylFeedItem *fitem = NULL; - RSSylFeedItemMedia *media = NULL; - gchar *link_type, *link_href, *link_rel, *tmp, *content = NULL; - gulong link_size; - - g_return_val_if_fail(doc != NULL, 0); - g_return_val_if_fail(ritem != NULL, 0); - - if( ritem->contents == NULL ) - rssyl_read_existing(ritem); - - node = xmlDocGetRootElement(doc); - - if (node == NULL) - return 0; - - node = node->children; - - for (; node; node = node->next) { - gboolean got_content = FALSE; - if (xmlStrcmp(node->name, "entry")) { - continue; - } - - n = node->children; - fitem = g_new0(RSSylFeedItem, 1); - fitem->date = 0; - fitem->date_published = 0; - fitem->text = NULL; - - if (parent) - fitem->parent_link = g_strdup(parent); - - do { - /* Title */ - if( !xmlStrcmp(n->name, "title") ) { - content = xmlNodeGetContent(n); - fitem->title = rssyl_format_string(content, TRUE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - Atom item title: '%s'\n", fitem->title); - } - - /* ID */ - if( !xmlStrcmp(n->name, "id") ) { - content = xmlNodeGetContent(n); - fitem->id = g_strdup_printf("%s%s", (parent?"comment-":""), content); - xmlFree(content); - debug_print("RSSyl: XML - Atom id: '%s'\n", fitem->id); - } - - /* Text */ - if( !xmlStrcmp(n->name, "summary") && !got_content ) { - content = xmlNodeGetContent(n); - debug_print("RSSyl: XML - Atom item text (summary) caught\n"); - fitem->text = rssyl_format_string(content, FALSE, FALSE); - xmlFree(content); - } - - if( !xmlStrcmp(n->name, "content") ) { - gchar *tmp = xmlGetProp(n, "type"); - debug_print("RSSyl: XML - Atom item text (content) caught\n"); - if (fitem->text) - g_free(fitem->text); - if( !xmlStrcmp(tmp, "xhtml")) { - for( h = n->children; h; h = h->next ) { - if( !xmlStrcmp(h->name, "div") ) { - buf = xmlBufferCreate(); - htmlNodeDump(buf, doc, h); - content = g_strdup((gchar *)xmlBufferContent(buf)); - xmlBufferFree(buf); - } - } - } else - content = xmlNodeGetContent(n); - xmlFree(tmp); - fitem->text = rssyl_format_string(content, FALSE, FALSE); - xmlFree(content); - got_content = TRUE; - } - - /* link */ - if( !xmlStrcmp(n->name, "link") ) { - link_type = xmlGetProp(n, "type"); - link_rel = xmlGetProp(n, "rel"); - link_href = xmlGetProp(n, "href"); - tmp = xmlGetProp(n, "length"); - link_size = (tmp ? atoi(tmp) : 0); - g_free(tmp); - - if( !link_rel || (link_rel && !xmlStrcmp(link_rel, "alternate")) ) { - fitem->link = link_href; - debug_print("RSSyl: XML - Atom item link: '%s'\n", fitem->link); - xmlFree(link_type); - xmlFree(link_rel); - } else if( link_rel && !xmlStrcmp(link_rel, "enclosure") ) { - debug_print("RSSyl: XML - Atom item enclosure: '%s' (%ld) [%s]\n", - link_href, link_size, link_type); - media = g_new(RSSylFeedItemMedia, 1); - media->url = link_href; - media->type = link_type; - media->size = link_size; - fitem->media = media; - xmlFree(link_rel); - } else { - xmlFree(link_type); - xmlFree(link_rel); - xmlFree(link_href); - } - } - - /* Date published - ISO8701 format */ - if( !xmlStrcmp(n->name, "published") ) { - content = xmlNodeGetContent(n); - fitem->date_published = parseISO8601Date(content); - xmlFree(content); - debug_print("RSSyl: XML - Atom item 'issued' date found\n" ); - } - - /* Date modified - ISO8701 format */ - if( !xmlStrcmp(n->name, "updated") ) { - content = xmlNodeGetContent(n); - fitem->date = parseISO8601Date(content); - xmlFree(content); - debug_print("RSSyl: XML - Atom item 'updated' date found\n" ); - } - - /* Author */ - if( !xmlStrcmp(n->name, "author") ) { - xmlNodePtr subnode; - gchar *name = NULL, *mail = NULL; - gchar *tmp; - for (subnode = n->children; subnode; subnode = subnode->next) { - content = xmlNodeGetContent(subnode); - if (!xmlStrcmp(subnode->name, "name") && !name) - name = g_strdup(content); - if (!xmlStrcmp(subnode->name, "email") && !mail) - mail = g_strdup(content); - xmlFree(content); - } - tmp = g_strdup_printf("%s%s%s%s%s", - name ? name:"", - name && mail ? " <":(mail?"<":""), - mail ? mail:"", - mail ? ">":"", - !name && !mail ? "N/A":""); - fitem->author = rssyl_format_string(tmp, TRUE, TRUE); - g_free(tmp); - g_free(name); - g_free(mail); - debug_print("RSSyl: XML - Atom item author: '%s'\n", fitem->author); - } - - /* Comments */ - if( !xmlStrcmp(n->name, "commentRSS") || !xmlStrcmp(n->name, "commentRss")) { - content = xmlNodeGetContent(n); - fitem->comments_link = rssyl_format_string(content, FALSE, TRUE); - xmlFree(content); - debug_print("RSSyl: XML - comments_link: '%s'\n", fitem->comments_link); - } - } while( (n = n->next) != NULL); - - if( fitem->id && fitem->title && fitem->date ) { - - /* If no link is available, and we can safely guess ID - * might be a (perma)link, mark it so. */ - if (!fitem->link && fitem->id /* no url, but we have id */ - && (!strncmp(fitem->id, "http:", 5) /* id looks like an url */ - || !strncmp(fitem->id, "https:", 6))) { - if (!ritem->url || strcmp(ritem->url, fitem->id)) { - /* id is different from feed url (good chance it is a permalink) */ - debug_print("RSSyl: Marking ID as permalink\n"); - fitem->id_is_permalink = TRUE; - } - } - - if (rssyl_add_feed_item(ritem, fitem) == FALSE) { - rssyl_free_feeditem(fitem); - fitem = NULL; - } - count++; - } else - debug_print("RSSyl: Incomplete Atom entry, need at least 'id', 'title' and 'updated' tags\n"); - } - - return count; -} diff --git a/src/plugins/rssyl/parsers.h b/src/plugins/rssyl/parsers.h deleted file mode 100644 index 48321e657..000000000 --- a/src/plugins/rssyl/parsers.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef __PARSERS_H -#define __PARSERS_H - -#include -#include - -#include "feed.h" - -gint rssyl_parse_rss(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent); -gint rssyl_parse_rdf(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent); -gint rssyl_parse_atom(xmlDocPtr doc, RSSylFolderItem *ritem, gchar *parent); - -#endif /* __PARSERS_H */ diff --git a/src/plugins/rssyl/plugin.c b/src/plugins/rssyl/plugin.c index 9cf3cdc65..7ade91479 100644 --- a/src/plugins/rssyl/plugin.c +++ b/src/plugins/rssyl/plugin.c @@ -1,5 +1,5 @@ /* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 1999-2004 Hiroyuki Yamamoto * This file (C) 2005 Andrej Kacian * @@ -22,23 +22,25 @@ #ifdef HAVE_CONFIG_H # include "config.h" -#include "claws-features.h" +# include "claws-features.h" #endif -#include +/* Global includes */ #include - -#include "common/version.h" -#include "claws.h" #include +/* Claws Mail includes */ +#include +#include +#include + +/* Local includes */ #include "rssyl.h" -#include "plugin.h" gint plugin_init(gchar **error) { - if( !check_plugin_version(MAKE_NUMERIC_VERSION(3,7,8,31), - VERSION_NUMERIC, PLUGIN_NAME, error) ) + if( !check_plugin_version(MAKE_NUMERIC_VERSION(3, 7, 8, 31), + VERSION_NUMERIC, "RSSyl", error) ) return -1; curl_global_init(CURL_GLOBAL_DEFAULT); @@ -55,7 +57,7 @@ gboolean plugin_done(void) const gchar *plugin_name(void) { - return PLUGIN_NAME; + return "RSSyl"; } const gchar *plugin_desc(void) diff --git a/src/plugins/rssyl/rssyl.c b/src/plugins/rssyl/rssyl.c index 670dd86c4..2c5a634dc 100644 --- a/src/plugins/rssyl/rssyl.c +++ b/src/plugins/rssyl/rssyl.c @@ -1,5 +1,5 @@ /* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 1999-2004 Hiroyuki Yamamoto * This file (C) 2005 Andrej Kacian * @@ -22,52 +22,73 @@ #ifdef HAVE_CONFIG_H # include "config.h" -#include "claws-features.h" #endif +/* Global includes */ #include #include - -#ifdef G_OS_WIN32 -# include -#endif - -#include #include -#include "folder.h" -#include "localfolder.h" - -#include "procheader.h" -#include "common/utils.h" -#include "toolbar.h" -#include "prefs_toolbar.h" - -#include "main.h" - -#include "feed.h" -#include "feedprops.h" -#include "opml.h" +/* Claws Mail includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feeditem.h" #include "rssyl.h" +#include "rssyl_deleted.h" #include "rssyl_gtk.h" +#include "rssyl_feed.h" #include "rssyl_prefs.h" -#include "strreplace.h" +#include "rssyl_update_feed.h" +#include "rssyl_update_format.h" +#include "opml_import.h" +#include "opml_export.h" +#include "strutils.h" + +FolderClass rssyl_class; static gint rssyl_create_tree(Folder *folder); +static gint rssyl_scan_tree(Folder *folder); static gboolean existing_tree_found = FALSE; static void rssyl_init_read_func(FolderItem *item, gpointer data) { + RFolderItem *ritem = (RFolderItem *)item; + RPrefs *rsprefs = NULL; + if( !IS_RSSYL_FOLDER_ITEM(item) ) return; existing_tree_found = TRUE; - if( folder_item_parent(item) == NULL ) + /* Don't do anything if we're on root of our folder tree or on + * a regular folder (no feed) */ + if( folder_item_parent(item) == NULL || ritem->url == NULL ) return; - rssyl_get_feed_props((RSSylFolderItem *)item); + ritem->refresh_id = 0; + + /* Start automatic refresh timer, if necessary */ + if( ritem->default_refresh_interval ) { + rsprefs = rssyl_prefs_get(); + if( !rsprefs->refresh_enabled ) + return; + + ritem->refresh_interval = rsprefs->refresh; + } + + /* Start the timer, if determined interval is >0 */ + if( ritem->refresh_interval > 0 ) + rssyl_feed_start_refresh_timeout(ritem); } static void rssyl_make_rc_dir(void) @@ -80,7 +101,7 @@ static void rssyl_make_rc_dir(void) g_warning("couldn't create directory %s\n", rssyl_dir); } - debug_print("created directorty %s\n", rssyl_dir); + debug_print("RSSyl: created directory %s\n", rssyl_dir); } g_free(rssyl_dir); @@ -89,7 +110,6 @@ static void rssyl_make_rc_dir(void) static void rssyl_create_default_mailbox(void) { Folder *root = NULL; - FolderItem *item; rssyl_make_rc_dir(); @@ -98,20 +118,21 @@ static void rssyl_create_default_mailbox(void) g_return_if_fail(root != NULL); folder_add(root); - item = FOLDER_ITEM(root->node->data); + rssyl_scan_tree(root); - rssyl_subscribe_new_feed(item, RSSYL_DEFAULT_FEED, TRUE); + /* FIXME: subscribe default feed */ +// rssyl_subscribe_new_feed(item, RSSYL_DEFAULT_FEED, TRUE); } -static gboolean rssyl_refresh_all_feeds_deferred(gpointer data) +static gboolean rssyl_update_all_feeds_deferred(gpointer data) { - rssyl_refresh_all_feeds(); + rssyl_update_all_feeds(); return FALSE; } -static void rssyl_toolbar_cb_refresh_all(gpointer parent, const gchar *item_name, gpointer data) +static void rssyl_toolbar_cb_refresh_all_feeds(gpointer parent, const gchar *item_name, gpointer data) { - rssyl_refresh_all_feeds(); + rssyl_update_all_feeds(); } void rssyl_init(void) @@ -119,34 +140,37 @@ void rssyl_init(void) folder_register_class(rssyl_folder_get_class()); rssyl_gtk_init(); - rssyl_make_rc_dir(); rssyl_prefs_init(); folder_func_to_all_folders((FolderItemFunc)rssyl_init_read_func, NULL); - if( existing_tree_found == FALSE ) + if( !existing_tree_found ) rssyl_create_default_mailbox(); + else + rssyl_update_format(); - prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "RSSyl", - _("Refresh all feeds"), rssyl_toolbar_cb_refresh_all, NULL); - - rssyl_opml_export(); + prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "RSSyl", _("Refresh all feeds"), rssyl_toolbar_cb_refresh_all_feeds, NULL); if( rssyl_prefs_get()->refresh_on_startup && claws_is_starting() ) - g_timeout_add(2000, rssyl_refresh_all_feeds_deferred, NULL); + g_timeout_add(2000, rssyl_update_all_feeds_deferred, NULL); } void rssyl_done(void) { - prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "RSSyl", - _("Refresh all feeds")); + rssyl_opml_export(); + + prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "RSSyl", _("Refresh all feeds")); + rssyl_prefs_done(); rssyl_gtk_done(); - if (!claws_is_exiting()) + + if( !claws_is_exiting() ) folder_unregister_class(rssyl_folder_get_class()); + + debug_print("RSSyl is done\n"); } static gchar *rssyl_get_new_msg_filename(FolderItem *dest) @@ -188,17 +212,15 @@ static void rssyl_get_last_num(Folder *folder, FolderItem *item) debug_print("rssyl_get_last_num(): Scanning %s ...\n", item->path); path = folder_item_get_path(item); g_return_if_fail(path != NULL); - if( change_dir(path) < 0 ) { - g_free(path); - return; - } - g_free(path); - if( (dp = opendir(".")) == NULL ) { + if( (dp = opendir(path)) == NULL ) { FILE_OP_ERROR(item->path, "opendir"); + g_free(path); return; } + g_free(path); + while( (d = readdir(dp)) != NULL ) { if( (num = to_number(d->d_name)) > 0 && dirent_is_regular_file(d) ) { if( max < num ) @@ -211,36 +233,127 @@ static void rssyl_get_last_num(Folder *folder, FolderItem *item) item->last_num = max; } -struct _RSSylFolder { - LocalFolder folder; -}; - -typedef struct _RSSylFolder RSSylFolder; - -FolderClass rssyl_class; - static Folder *rssyl_new_folder(const gchar *name, const gchar *path) { - RSSylFolder *folder; + Folder *folder; - debug_print("RSSyl: new_folder\n"); + debug_print("RSSyl: new_folder: %s (%s)\n", name, path); rssyl_make_rc_dir(); - folder = g_new0(RSSylFolder, 1); + folder = g_new0(Folder, 1); FOLDER(folder)->klass = &rssyl_class; folder_init(FOLDER(folder), name); return FOLDER(folder); } -static void rssyl_destroy_folder(Folder *_folder) +static void rssyl_destroy_folder(Folder *folder) { - RSSylFolder *folder = (RSSylFolder *)_folder; - folder_local_folder_destroy(LOCAL_FOLDER(folder)); } +static void rssyl_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag) +{ + GList *cur; + RFolderItem *ritem = (RFolderItem *)item; + + folder_item_set_xml(folder, item, tag); + + for( cur = tag->attr; cur != NULL; cur = g_list_next(cur)) { + XMLAttr *attr = (XMLAttr *) cur->data; + + if( !attr || !attr->name || !attr->value) + continue; + + /* (str) URL */ + if( !strcmp(attr->name, "uri")) { + g_free(ritem->url); + ritem->url = g_strdup(attr->value); + } + /* (str) Official title */ + if( !strcmp(attr->name, "official_title")) { + g_free(ritem->official_title); + ritem->official_title = g_strdup(attr->value); + } + /* (bool) Keep old items */ + if( !strcmp(attr->name, "keep_old")) + ritem->keep_old = (atoi(attr->value) == 0 ? FALSE : TRUE ); + /* (bool) Use default refresh_interval */ + if( !strcmp(attr->name, "default_refresh_interval")) + ritem->default_refresh_interval = (atoi(attr->value) == 0 ? FALSE : TRUE ); + /* (int) Refresh interval */ + if( !strcmp(attr->name, "refresh_interval")) + ritem->refresh_interval = atoi(attr->value); + /* (bool) Fetch comments */ + if( !strcmp(attr->name, "fetch_comments")) + ritem->fetch_comments = (atoi(attr->value) == 0 ? FALSE : TRUE ); + /* (int) Max age of posts to fetch comments for */ + if( !strcmp(attr->name, "fetch_comments_max_age")) + ritem->fetch_comments_max_age = atoi(attr->value); + /* (bool) Write heading */ + if( !strcmp(attr->name, "write_heading")) + ritem->write_heading = (atoi(attr->value) == 0 ? FALSE : TRUE ); + /* (int) Silent update */ + if( !strcmp(attr->name, "silent_update")) + ritem->silent_update = atoi(attr->value); + /* (bool) Ignore title rename */ + if( !strcmp(attr->name, "ignore_title_rename")) + ritem->ignore_title_rename = (atoi(attr->value) == 0 ? FALSE : TRUE ); + /* (bool) Verify SSL peer */ + if( !strcmp(attr->name, "ssl_verify_peer")) + ritem->ssl_verify_peer = (atoi(attr->value) == 0 ? FALSE : TRUE ); + } +} + +static XMLTag *rssyl_item_get_xml(Folder *folder, FolderItem *item) +{ + XMLTag *tag; + RFolderItem *ri = (RFolderItem *)item; + gchar *tmp = NULL; + + tag = folder_item_get_xml(folder, item); + + /* (str) URL */ + if( ri->url != NULL ) + xml_tag_add_attr(tag, xml_attr_new("uri", ri->url)); + /* (str) Official title */ + if( ri->official_title != NULL ) + xml_tag_add_attr(tag, xml_attr_new("official_title", ri->official_title)); + /* (bool) Keep old items */ + xml_tag_add_attr(tag, xml_attr_new("keep_old", + (ri->keep_old ? "1" : "0")) ); + /* (bool) Use default refresh interval */ + xml_tag_add_attr(tag, xml_attr_new("default_refresh_interval", + (ri->default_refresh_interval ? "1" : "0")) ); + /* (int) Refresh interval */ + tmp = g_strdup_printf("%d", ri->refresh_interval); + xml_tag_add_attr(tag, xml_attr_new("refresh_interval", tmp)); + g_free(tmp); + /* (bool) Fetch comments */ + xml_tag_add_attr(tag, xml_attr_new("fetch_comments", + (ri->fetch_comments ? "1" : "0")) ); + /* (int) Max age of posts to fetch comments for */ + tmp = g_strdup_printf("%d", ri->fetch_comments_max_age); + xml_tag_add_attr(tag, xml_attr_new("fetch_comments_max_age", tmp)); + g_free(tmp); + /* (bool) Write heading */ + xml_tag_add_attr(tag, xml_attr_new("write_heading", + (ri->write_heading ? "1" : "0")) ); + /* (int) Silent update */ + tmp = g_strdup_printf("%d", ri->silent_update); + xml_tag_add_attr(tag, xml_attr_new("silent_update", tmp)); + g_free(tmp); + /* (bool) Ignore title rename */ + xml_tag_add_attr(tag, xml_attr_new("ignore_title_rename", + (ri->ignore_title_rename ? "1" : "0")) ); + /* (bool) Verify SSL peer */ + xml_tag_add_attr(tag, xml_attr_new("ssl_verify_peer", + (ri->ssl_verify_peer ? "1" : "0")) ); + + return tag; +} + static gint rssyl_scan_tree(Folder *folder) { g_return_val_if_fail(folder != NULL, -1); @@ -261,6 +374,8 @@ static gint rssyl_create_tree(Folder *folder) FolderItem *rootitem; GNode *rootnode; + g_return_val_if_fail(folder != NULL, -1); + rssyl_make_rc_dir(); if( !folder->node ) { @@ -269,9 +384,6 @@ static gint rssyl_create_tree(Folder *folder) rootnode = g_node_new(rootitem); folder->node = rootnode; rootitem->node = rootnode; - } else { - rootitem = FOLDER_ITEM(folder->node->data); - rootnode = folder->node; } debug_print("RSSyl: created new rssyl tree\n"); @@ -280,125 +392,201 @@ static gint rssyl_create_tree(Folder *folder) static FolderItem *rssyl_item_new(Folder *folder) { - RSSylFolderItem *ritem; - - debug_print("RSSyl: item_new\n"); - - ritem = g_new0(RSSylFolderItem, 1); + RFolderItem *ritem = g_new0(RFolderItem, 1); ritem->url = NULL; + ritem->official_title = NULL; + ritem->source_id = NULL; + ritem->items = NULL; + ritem->keep_old = FALSE; ritem->default_refresh_interval = TRUE; - ritem->default_expired_num = TRUE; + ritem->refresh_interval = atoi(PREF_DEFAULT_REFRESH); ritem->fetch_comments = FALSE; - ritem->fetch_comments_for = -1; + ritem->fetch_comments_max_age = -1; + ritem->write_heading = TRUE; + ritem->fetching_comments = FALSE; ritem->silent_update = 0; - ritem->refresh_interval = rssyl_prefs_get()->refresh; - ritem->refresh_id = 0; - ritem->expired_num = rssyl_prefs_get()->expired; - ritem->last_count = 0; - ritem->ssl_verify_peer = rssyl_prefs_get()->ssl_verify_peer; - - ritem->contents = NULL; - ritem->feedprop = NULL; + ritem->last_update = 0; + ritem->ignore_title_rename = FALSE; return (FolderItem *)ritem; } static void rssyl_item_destroy(Folder *folder, FolderItem *item) { - RSSylFolderItem *ritem = (RSSylFolderItem *)item; + RFolderItem *ritem = (RFolderItem *)item; g_return_if_fail(ritem != NULL); - /* Silently remove feed refresh timeouts */ - if( ritem->refresh_id != 0 ) - g_source_remove(ritem->refresh_id); - g_free(ritem->url); - g_free(ritem->official_name); - g_slist_free(ritem->contents); + g_free(ritem->official_title); + g_slist_free(ritem->items); - g_free(item); + /* Remove a scheduled refresh, if any */ + if( ritem->refresh_id != 0) + g_source_remove(ritem->refresh_id); + + g_free(ritem); } static FolderItem *rssyl_create_folder(Folder *folder, FolderItem *parent, const gchar *name) { - gchar *path = NULL, *tmp; + gchar *path = NULL, *basepath = NULL, *itempath = NULL; FolderItem *newitem = NULL; g_return_val_if_fail(folder != NULL, NULL); g_return_val_if_fail(parent != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); - tmp = rssyl_feed_title_to_dir((gchar *)name); - path = g_strconcat((parent->path != NULL) ? parent->path : "", ".", - tmp, NULL); - g_free(tmp); - newitem = folder_item_new(folder, name, path); - folder_item_append(parent, newitem); + + path = folder_item_get_path(parent); + if( !is_dir_exist(path) ) { + if( (make_dir_hier(path) != 0) ) { + debug_print("RSSyl: Couldn't create directory (rec) '%s'\n", path); + return NULL; + } + } + + basepath = g_strdelimit(g_strdup(name), G_DIR_SEPARATOR_S, '_'); + path = g_strconcat(path, G_DIR_SEPARATOR_S, basepath, NULL); + + if( make_dir(path) < 0 ) { + debug_print("RSSyl: Couldn't create directory '%s'\n", path); + g_free(path); + g_free(basepath); + return NULL; + } g_free(path); + itempath = g_strconcat((parent->path ? parent->path : ""), + G_DIR_SEPARATOR_S, basepath, NULL); + newitem = folder_item_new(folder, name, itempath); + g_free(itempath); + g_free(basepath); + + folder_item_append(parent, newitem); + return newitem; } +FolderItem *rssyl_get_root_folderitem(FolderItem *item) +{ + FolderItem *i; + + for( i = item; folder_item_parent(i) != NULL; i = folder_item_parent(i) ) { } + return i; +} + static gchar *rssyl_item_get_path(Folder *folder, FolderItem *item) { - gchar *result, *tmp; - tmp = rssyl_feed_title_to_dir(item->name); - result = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, tmp, NULL); - g_free(tmp); - return result; + gchar *path, *name; + + g_return_val_if_fail(folder != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + debug_print("RSSyl: item_get_path\n"); + + name = folder_item_get_name(rssyl_get_root_folderitem(item)); + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, + G_DIR_SEPARATOR_S, name, G_DIR_SEPARATOR_S, item->path, NULL); + g_free(name); + + return path; +} + +static gboolean rssyl_rename_folder_func(GNode *node, gpointer data) +{ + FolderItem *item = node->data; + gchar **paths = data; + const gchar *oldpath = paths[0]; + const gchar *newpath = paths[1]; + gchar *base; + gchar *new_itempath; + gint oldpathlen; + + oldpathlen = strlen(oldpath); + if (strncmp(oldpath, item->path, oldpathlen) != 0) { + g_warning("path doesn't match: %s, %s\n", oldpath, item->path); + return TRUE; + } + + base = item->path + oldpathlen; + while (*base == G_DIR_SEPARATOR) base++; + if (*base == '\0') + new_itempath = g_strdup(newpath); + else + new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base, + NULL); + g_free(item->path); + item->path = new_itempath; + + return FALSE; } static gint rssyl_rename_folder(Folder *folder, FolderItem *item, const gchar *name) { - gchar *oldname = NULL, *oldpath = NULL, *newpath = NULL; - RSSylFolderItem *ritem = NULL; + gchar *oldpath; + gchar *dirname; + gchar *newpath, *utf8newpath; + gchar *basenewpath; + gchar *paths[2]; + g_return_val_if_fail(folder != NULL, -1); g_return_val_if_fail(item != NULL, -1); g_return_val_if_fail(item->path != NULL, -1); g_return_val_if_fail(name != NULL, -1); - debug_print("RSSyl: renaming folder '%s' to '%s'\n", item->path, name); + debug_print("RSSyl: rssyl_rename_folder '%s' -> '%s'\n", + item->name, name); - oldpath = rssyl_item_get_path(folder, item); - - /* now get the new path using the new name */ - oldname = item->name; - item->name = g_strdup(name); - newpath = rssyl_item_get_path(folder, item); - - /* put back the old name in case the rename fails */ - g_free(item->name); - item->name = oldname; - - if (g_rename(oldpath, newpath) < 0) { + if (!strcmp(item->name, name)) + return 0; + + oldpath = folder_item_get_path(item); + if( !is_dir_exist(oldpath) ) + make_dir_hier(oldpath); + + dirname = g_path_get_dirname(oldpath); + basenewpath = g_strdelimit(g_strdup(name), G_DIR_SEPARATOR_S, '_'); + newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, basenewpath, NULL); + g_free(basenewpath); + + if( g_rename(oldpath, newpath) < 0 ) { FILE_OP_ERROR(oldpath, "rename"); g_free(oldpath); g_free(newpath); return -1; } - - g_free(item->path); - item->path = g_strdup_printf(".%s", name); - - ritem = (RSSylFolderItem *)item; - - if (ritem->url) - rssyl_props_update_name(ritem, (gchar *)name); - + + g_free(oldpath); + g_free(newpath); + + if( strchr(item->path, G_DIR_SEPARATOR) != NULL ) { + dirname = g_path_get_dirname(item->path); + utf8newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL); + g_free(dirname); + } else + utf8newpath = g_strdup(name); + g_free(item->name); item->name = g_strdup(name); - - folder_write_list(); + + paths[0] = g_strdup(item->path); + paths[1] = utf8newpath; + g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + rssyl_rename_folder_func, paths); + + g_free(paths[0]); + g_free(paths[1]); return 0; } static gint rssyl_remove_folder(Folder *folder, FolderItem *item) { + gchar *path = NULL; + g_return_val_if_fail(folder != NULL, -1); g_return_val_if_fail(item != NULL, -1); g_return_val_if_fail(item->path != NULL, -1); @@ -406,6 +594,14 @@ static gint rssyl_remove_folder(Folder *folder, FolderItem *item) debug_print("RSSyl: removing folder item %s\n", item->path); + path = folder_item_get_path(item); + if( remove_dir_recursive(path) < 0 ) { + g_warning("can't remove directory '%s'\n", path); + g_free(path); + return -1; + } + + g_free(path); folder_item_remove(item); return 0; @@ -418,40 +614,35 @@ static gint rssyl_get_num_list(Folder *folder, FolderItem *item, DIR *dp; struct dirent *d; gint num, nummsgs = 0; - RSSylFolderItem *ritem = (RSSylFolderItem *)item; g_return_val_if_fail(item != NULL, -1); - debug_print("RSSyl: scanning '%s'...\n", item->path); - - rssyl_get_feed_props(ritem); - - if (ritem->url == NULL) - return -1; + debug_print("RSSyl: get_num_list: scanning '%s'\n", item->path); *old_uids_valid = TRUE; - + path = folder_item_get_path(item); g_return_val_if_fail(path != NULL, -1); - if( change_dir(path) < 0 ) { - g_free(path); - return -1; - } - g_free(path); - if( (dp = opendir(".")) == NULL ) { + if( (dp = opendir(path)) == NULL ) { FILE_OP_ERROR(item->path, "opendir"); + g_free(path); return -1; } + g_free(path); + while( (d = readdir(dp)) != NULL ) { if( (num = to_number(d->d_name)) > 0 ) { *list = g_slist_prepend(*list, GINT_TO_POINTER(num)); nummsgs++; } } + closedir(dp); + debug_print("Rssyl: get_num_list: returning %d\n", nummsgs); + return nummsgs; } @@ -462,15 +653,22 @@ static gboolean rssyl_scan_required(Folder *folder, FolderItem *item) static gchar *rssyl_fetch_msg(Folder *folder, FolderItem *item, gint num) { - gchar *snum = g_strdup_printf("%d", num); - gchar *tmp = rssyl_feed_title_to_dir(item->name); - gchar *file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, - G_DIR_SEPARATOR_S, tmp, - G_DIR_SEPARATOR_S, snum, NULL); - g_free(tmp); - debug_print("RSSyl: fetch_msg: '%s'\n", file); + gchar *path; + gchar *file; + + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(num > 0, NULL); + + path = folder_item_get_path(item); + file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL); + g_free(path); + + debug_print("RSSyl: fetch_msg '%s'\n", file); - g_free(snum); + if( !is_file_exist(file)) { + g_free(file); + return NULL; + } return file; } @@ -481,25 +679,24 @@ static MsgInfo *rssyl_get_msginfo(Folder *folder, FolderItem *item, gint num) gchar *file; MsgFlags flags; - debug_print("RSSyl: get_msginfo: %d\n", num); - g_return_val_if_fail(folder != NULL, NULL); g_return_val_if_fail(item != NULL, NULL); g_return_val_if_fail(num > 0, NULL); + debug_print("RSSyl: get_msginfo: %d\n", num); + file = rssyl_fetch_msg(folder, item, num); g_return_val_if_fail(file != NULL, NULL); - flags.perm_flags = MSG_NEW | MSG_UNREAD; + flags.perm_flags = 0; flags.tmp_flags = 0; - msginfo = rssyl_parse_feed_item_to_msginfo(file, flags, TRUE, TRUE, item); + msginfo = rssyl_feed_parse_item_to_msginfo(file, flags, TRUE, TRUE, item); + g_free(file); if( msginfo ) msginfo->msgnum = num; - g_free(file); - return msginfo; } @@ -523,30 +720,31 @@ static gint rssyl_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list, destfile = rssyl_get_new_msg_filename(dest); g_return_val_if_fail(destfile != NULL, -1); + debug_print("RSSyl: add_msgs: new filename is '%s'\n", destfile); -#ifdef G_OS_UNIX - if( link(fileinfo->file, destfile) < 0 ) -#endif + if( link(fileinfo->file, destfile) < 0 ) { if( copy_file(fileinfo->file, destfile, TRUE) < 0 ) { g_warning("can't copy message %s to %s\n", fileinfo->file, destfile); g_free(destfile); return -1; } + } if( relation != NULL ) - g_hash_table_insert(relation, fileinfo, - GINT_TO_POINTER(dest->last_num + 1) ); + g_hash_table_insert(relation, fileinfo->msginfo != NULL ? + (gpointer) fileinfo->msginfo : (gpointer) fileinfo, + GINT_TO_POINTER(dest->last_num + 1)); g_free(destfile); dest->last_num++; } + return dest->last_num; } static gint rssyl_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags) { - gint ret; GSList file_list; MsgFileInfo fileinfo; @@ -558,28 +756,9 @@ static gint rssyl_add_msg(Folder *folder, FolderItem *dest, const gchar *file, file_list.data = &fileinfo; file_list.next = NULL; - ret = rssyl_add_msgs(folder, dest, &file_list, NULL); - return ret; + return rssyl_add_msgs(folder, dest, &file_list, NULL); } -static gint rssyl_dummy_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *info) -{ - if (info->folder == NULL || info->folder->folder != dest->folder) { - return -1; - } - if (info->folder && info->folder->name && dest->name - && !strcmp(info->folder->name, dest->name)) { - /* this is a folder move */ - gchar *file = procmsg_get_message_file(info); - gchar *tmp = g_strdup_printf("%s.tmp", file); - copy_file(file, tmp, TRUE); - g_free(file); - g_free(tmp); - return info->msgnum; - } else { - return -1; - } -} static gint rssyl_remove_msg(Folder *folder, FolderItem *item, gint num) { gboolean need_scan = FALSE; @@ -595,13 +774,16 @@ static gint rssyl_remove_msg(Folder *folder, FolderItem *item, gint num) /* are we doing a folder move ? */ tmp = g_strdup_printf("%s.tmp", file); if (is_file_exist(tmp)) { - claws_unlink(tmp); + g_unlink(tmp); g_free(tmp); g_free(file); return 0; } g_free(tmp); - if( claws_unlink(file) < 0 ) { + + rssyl_deleted_add((RFolderItem *)item, file); + + if( g_unlink(file) < 0 ) { FILE_OP_ERROR(file, "unlink"); g_free(file); return -1; @@ -618,11 +800,29 @@ static gboolean rssyl_subscribe_uri(Folder *folder, const gchar *uri) { if (folder->klass != rssyl_folder_get_class()) return FALSE; - - if( rssyl_subscribe_new_feed( - FOLDER_ITEM(folder->node->data), uri, FALSE) != NULL ) - return TRUE; - return FALSE; + return (rssyl_feed_subscribe_new(FOLDER_ITEM(folder->node->data), uri, FALSE) ? + TRUE : FALSE); +} + +static void rssyl_copy_private_data(Folder *folder, FolderItem *oldi, + FolderItem *newi) +{ + RFolderItem *olditem = (RFolderItem *)oldi, + *newitem = (RFolderItem *)newi; + + g_return_if_fail(folder != NULL); + g_return_if_fail(olditem != NULL); + g_return_if_fail(newitem != NULL); + + if( olditem->url != NULL ) { + g_free(newitem->url); + newitem->url = g_strdup(olditem->url); + } + + if( olditem->official_title != NULL ) { + g_free(newitem->official_title); + newitem->official_title = g_strdup(olditem->official_title); + } } /************************************************************************/ @@ -651,11 +851,14 @@ FolderClass *rssyl_folder_get_class() rssyl_class.remove_folder = rssyl_remove_folder; rssyl_class.get_num_list = rssyl_get_num_list; rssyl_class.scan_required = rssyl_scan_required; + rssyl_class.item_set_xml = rssyl_item_set_xml; + rssyl_class.item_get_xml = rssyl_item_get_xml; /* Message functions */ rssyl_class.get_msginfo = rssyl_get_msginfo; rssyl_class.fetch_msg = rssyl_fetch_msg; - rssyl_class.copy_msg = rssyl_dummy_copy_msg; + rssyl_class.copy_msg = mh_get_class()->copy_msg; + rssyl_class.copy_msgs = mh_get_class()->copy_msgs; rssyl_class.add_msg = rssyl_add_msg; rssyl_class.add_msgs = rssyl_add_msgs; rssyl_class.remove_msg = rssyl_remove_msg; @@ -663,7 +866,8 @@ FolderClass *rssyl_folder_get_class() // rssyl_class.change_flags = rssyl_change_flags; rssyl_class.change_flags = NULL; rssyl_class.subscribe = rssyl_subscribe_uri; - debug_print("RSSyl: registered folderclass\n"); + rssyl_class.copy_private_data = rssyl_copy_private_data; + rssyl_class.search_msgs = folder_item_search_msgs_local; } return &rssyl_class; diff --git a/src/plugins/rssyl/rssyl.h b/src/plugins/rssyl/rssyl.h index 917adbe79..7b1b169c2 100644 --- a/src/plugins/rssyl/rssyl.h +++ b/src/plugins/rssyl/rssyl.h @@ -5,10 +5,13 @@ #include -#define PLUGIN_NAME (_("RSSyl")) +#include "libfeed/feed.h" /* Name of directory in rcdir where RSSyl will store its data. */ -#define RSSYL_DIR "RSSyl" +#define RSSYL_DIR "RSSyl" + +/* Folder name for a new feed, before it is parsed for the first time. */ +#define RSSYL_NEW_FOLDER_NAME "NewFeed" /* Default RSSyl mailbox name */ #define RSSYL_DEFAULT_MAILBOX _("My Feeds") @@ -16,43 +19,80 @@ /* Default feed to be added when creating mailbox for the first time */ #define RSSYL_DEFAULT_FEED "http://planet.claws-mail.org/rss20.xml" -struct _RSSylFolderItem { - FolderItem item; - GSList *contents; - gint last_count; +/* File where info about user-deleted feed items is stored */ +#define RSSYL_DELETED_FILE ".deleted" +struct _RFolderItem { + FolderItem item; gchar *url; - gchar *official_name; + gchar *official_title; + gchar *source_id; + + gboolean keep_old; gboolean default_refresh_interval; gint refresh_interval; - gboolean default_expired_num; - gint expired_num; - - guint refresh_id; gboolean fetch_comments; - gint fetch_comments_for; + gint fetch_comments_max_age; + gint silent_update; + gboolean write_heading; + gboolean ignore_title_rename; gboolean ssl_verify_peer; - struct _RSSylFeedProp *feedprop; + guint refresh_id; + gboolean fetching_comments; + time_t last_update; + + struct _RFeedProp *feedprop; + + GSList *items; + GSList *deleted_items; }; -typedef struct _RSSylFolderItem RSSylFolderItem; +typedef struct _RFolderItem RFolderItem; -struct _RSSylRefreshCtx { - RSSylFolderItem *ritem; +struct _RRefreshCtx { + RFolderItem *ritem; guint id; }; -typedef struct _RSSylRefreshCtx RSSylRefreshCtx; +typedef struct _RRefreshCtx RRefreshCtx; + +struct _RFetchCtx { + Feed *feed; + guint response_code; + gchar *error; + gboolean success; + gboolean ready; +}; + +typedef struct _RFetchCtx RFetchCtx; + +struct _RParseCtx { + RFolderItem *ritem; + gboolean ready; +}; + +typedef struct _RParseCtx RParseCtx; + +struct _RDeletedItem { + gchar *id; + gchar *title; + time_t date_published; + time_t date_modified; +}; + +typedef struct _RDeletedItem RDeletedItem; void rssyl_init(void); void rssyl_done(void); FolderClass *rssyl_folder_get_class(void); +FolderItem *rssyl_get_root_folderitem(FolderItem *item); + #define IS_RSSYL_FOLDER_ITEM(item) \ (item->folder->klass == rssyl_folder_get_class()) diff --git a/src/plugins/rssyl/rssyl_add_item.c b/src/plugins/rssyl/rssyl_add_item.c new file mode 100644 index 000000000..b058660d0 --- /dev/null +++ b/src/plugins/rssyl/rssyl_add_item.c @@ -0,0 +1,540 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * - DESCRIPTION HERE + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include +#include + +/* Claws Mail includes */ +#include +#include +#include + +/* Local includes */ +#include "libfeed/date.h" +#include "libfeed/feeditem.h" +#include "parse822.h" +#include "rssyl.h" +#include "rssyl_deleted.h" +#include "rssyl_feed.h" +#include "rssyl_parse_feed.h" +#include "strutils.h" + +/* rssyl_cb_feed_compare() + * + * GCompareFunc function called by glib2's g_slist_find_custom(). + */ + +static gint rssyl_cb_feed_compare(const FeedItem *a, const FeedItem *b) +{ + gboolean date_eq = FALSE, url_eq = FALSE, title_eq = FALSE; + gboolean no_url = FALSE, no_date = FALSE, no_title = FALSE; + gchar *atit = NULL, *btit = NULL; + + g_return_val_if_fail(a != NULL && b != NULL, 1); + + /* ID should be unique. If it matches, we've found what we came for. */ + if( (a->id != NULL) && (b->id != NULL) ) { + if( !strcmp(a->id, b->id) ) { + return 0; + } + + /* If both IDs are present, but they do not match, these are not the + * droids we're looking for. */ + return 1; + } + + /* Ok, we have no ID to aid us. Let's have a look at item timestamps + * and item title & url. */ + if( (a->url != NULL) && (b->url != NULL) ) { + if( !strcmp(a->url, b->url) ) + url_eq = TRUE; + } else + no_url = TRUE; + + if( (a->title != NULL) && (b->title != NULL) ) { + atit = conv_unmime_header(a->title, CS_UTF_8, FALSE); + btit = conv_unmime_header(b->title, CS_UTF_8, FALSE); + if( !strcmp(atit, btit) ) + title_eq = TRUE; + g_free(atit); + g_free(btit); + } else + no_title = TRUE; + + /* If there's no 'published' timestamp for the item, we can only judge + * by item url - 'modified' timestamp can have changed if the item was + * updated recently. */ + if( b->date_published <= 0 ) { + if( b->date_modified > 0 ) { + /* If the item has 'modified' timestamp, we can only rely on url + * and title at this point. */ + if( (url_eq || no_url) && title_eq + && (a->date_modified >= b->date_modified) ) + return 0; + else + return 1; + } else { + /* No timestamp of any kind, we'll just assume if both title and url + * match, we found the right item. Items in such feeds rarely change, + * and if they do, there's no way we can really */ + if( (url_eq || no_url) && title_eq ) + return 0; + else + return 1; + } + } + + /* Check if 'published' or at least 'modified' timestamps match */ + if( ((a->date_published > 0) && (b->date_published > 0) && + (a->date_published == b->date_published)) + || ((a->date_modified > 0) && (b->date_modified > 0) && + (a->date_modified == b->date_modified))) { + date_eq = TRUE; + } else + no_date = TRUE; + + /* If 'published' time and item url match, it is reasonable to assume + * we found our item. */ + if( (no_url || url_eq) && date_eq ) + return 0; + + /* There is no timestamp and the url matches (or there is none), + * we need to compare titles, ... */ + if( (no_url || url_eq) && no_date ) { + if( title_eq ) + return 0; + else + return 1; + + /* ... and as a last resort, if there is no title, item texts. */ + if( no_title && a->text && b->text ) { + if( !strcmp(a->text, b->text) ) + return 0; + else + return 1; + } + } + + /* We don't know this item. */ + return 1; +} + +enum { + ITEM_UNCHANGED, + ITEM_CHANGED_TEXTONLY, + ITEM_CHANGED +}; + +static gint rssyl_feed_item_changed(FeedItem *new_item, FeedItem *old_item ) +{ + debug_print("RSSyl: comparing '%s' and '%s'\n", + new_item->title, old_item->title); + + /* if both have title ... */ + if( old_item->title && new_item->title ) { + gchar *old = conv_unmime_header(old_item->title, CS_UTF_8, FALSE); + gchar *new = conv_unmime_header(new_item->title, CS_UTF_8, FALSE); + if( strcmp(old, new) != 0 ) { /* ... compare "unmimed" titles */ + debug_print("RSSyl:\t\titem titles differ:\nOLD: '%s'\nNEW: '%s'\n", + old, new); + g_free(old); + g_free(new); + return ITEM_CHANGED; + } + g_free(old); + g_free(new); + } else { + /* if atleast one has a title, they differ */ + if( old_item->title || new_item->title ) { + debug_print("RSSyl:\t\t+/- title\n"); + return ITEM_CHANGED; + } + } + + if( old_item->author && new_item->author ) { + gchar *old = conv_unmime_header(old_item->author, CS_UTF_8, TRUE); + gchar *new = conv_unmime_header(new_item->author, CS_UTF_8, TRUE); + if( strcmp(old, new) ) { /* ... compare "unmimed" authors */ + g_free(old); + g_free(new); + debug_print("RSSyl:\t\titem authors differ\n"); + return ITEM_CHANGED; + } + g_free(old); + g_free(new); + } else { + /* if atleast one has author, they differ */ + if( old_item->author || new_item->author ) { + debug_print("RSSyl:\t\t+/- author\n"); + return ITEM_CHANGED; + } + } + + /* if both have text ... */ + if( old_item->text && new_item->text ) { + if( strcmp(old_item->text, new_item->text) ) { /* ... compare them */ + debug_print("RSSyl:\t\titem texts differ\n"); + debug_print("\nOLD: '%s'\n", old_item->text); + debug_print("\nNEW: '%s'\n", new_item->text); + + return ITEM_CHANGED_TEXTONLY; + } + } else { + /* if at least one has some text, they differ */ + if( old_item->text || new_item->text ) { + debug_print("RSSyl:\t\t+/- text\n"); + return ITEM_CHANGED_TEXTONLY; + } + } + + /* they don't seem to differ */ + return ITEM_UNCHANGED; +} + +enum { + EXISTS_NEW, + EXISTS_UNCHANGED, + EXISTS_CHANGED, + EXISTS_CHANGED_TEXTONLY +}; + +/* rssyl_feed_item_exists() + * + * Returns 1 if a feed item already exists locally, 2 if there's a changed + * item with link that already belongs to existing item, 3 if only item's + * text has changed, 0 if item is new. + */ + +static guint rssyl_feed_item_exists(RFolderItem *ritem, FeedItem *fitem, + FeedItem **oldfitem) +{ + GSList *item = NULL; + FeedItem *efitem = NULL; + gint changed; + + g_return_val_if_fail(ritem != NULL, FALSE); + g_return_val_if_fail(fitem != NULL, FALSE); + + if( ritem->items == NULL || g_slist_length(ritem->items) == 0 ) + return EXISTS_NEW; + + if( (item = g_slist_find_custom(ritem->items, + (gconstpointer)fitem, (GCompareFunc)rssyl_cb_feed_compare)) ) { + efitem = (FeedItem *)item->data; + if( (changed = rssyl_feed_item_changed(fitem, efitem)) > ITEM_UNCHANGED ) { + *oldfitem = efitem; + if (changed == ITEM_CHANGED_TEXTONLY) + return EXISTS_CHANGED_TEXTONLY; + else + return EXISTS_CHANGED; + } + + return EXISTS_UNCHANGED; + } + + return EXISTS_NEW; +} + +/* =============================================================== */ + +void rssyl_add_item(RFolderItem *ritem, FeedItem *feed_item) +{ + FeedItem *old_item = NULL; + MsgFlags *flags; + MsgPermFlags oldperm_flags = 0; + MsgInfo *msginfo; + FILE *f; + gint fd, d, dif; + time_t tmpd; + gchar *meta_charset = NULL; + gchar *baseurl = NULL; + gchar *template = NULL; + gchar *tmp = NULL, *tmpurl = NULL, *tmpid = NULL; + gchar *dirname = NULL; + gchar *text = NULL; + gchar *heading = NULL; + gchar hdr[1024]; + FeedItemEnclosure *enc = NULL; + RFeedCtx *ctx; + + g_return_if_fail(ritem != NULL); + + /* If item title is empty, try to fill it from source title (Atom only). */ + debug_print(",%s,\n", (feed_item->title ? feed_item->title : "")); + debug_print(",%s,\n", (feed_item->sourcetitle ? feed_item->sourcetitle : "")); + tmp = feed_item_get_sourcetitle(feed_item); + if( feed_item_get_title(feed_item) == NULL || + strlen(feed_item->title) == 0 ) { + if( tmp != NULL && strlen(tmp) > 0 ) + feed_item_set_title(feed_item, tmp); + else + feed_item_set_title(feed_item, C_("Empty RSS feed title placeholder", "(empty)")); + } + + debug_print(",%s,\n", (feed_item->title ? feed_item->title : "")); + debug_print(",%s,\n", (feed_item->sourcetitle ? feed_item->sourcetitle : "")); + if (feed_item_get_id(feed_item) == NULL) { + debug_print("RSSyl: item ID empty, using its URL as ID.\n"); + feed_item_set_id(feed_item, feed_item_get_url(feed_item)); + } + + /* If neither item date is set, use date from source (Atom only). */ + if( feed_item_get_date_modified(feed_item) == -1 && + feed_item_get_date_published(feed_item) == -1 ) + feed_item_set_date_published(feed_item, + feed_item_get_sourcedate(feed_item)); + + /* Fix up subject, url and ID (rssyl_format_string()) so that + * comparing doesn't break. */ + debug_print("RSSyl: fixing up subject '%s'\n", feed_item_get_title(feed_item)); + feed_item_set_title(feed_item, rssyl_format_string(feed_item_get_title(feed_item), TRUE, TRUE)); + debug_print("RSSyl: fixing up URL\n"); + feed_item_set_url(feed_item, rssyl_format_string(feed_item_get_url(feed_item), + TRUE, TRUE)); + if( feed_item_get_id(feed_item) != NULL ) { + debug_print("RSSyl: fixing up ID\n"); + feed_item_set_id(feed_item, rssyl_format_string(feed_item_get_id(feed_item), + TRUE, TRUE)); + } + + /* If there's a summary, but no text, use summary as text. */ + if( feed_item_get_text(feed_item) == NULL && + (tmp = feed_item_get_summary(feed_item)) != NULL ) { + feed_item_set_text(feed_item, tmp); + g_free(feed_item->summary); /* We do not need summary in rssyl now. */ + feed_item->summary = NULL; + } + + /* Do not add if the item already exists, update if it does exist, but + * has changed. */ + dif = rssyl_feed_item_exists(ritem, feed_item, &old_item); + debug_print("RSSyl: rssyl_feed_item_exists returned %d\n", dif); + + if( dif == EXISTS_UNCHANGED ) { + debug_print("RSSyl: This item already exists, skipping...\n"); + return; + } + + /* Item is already in the list, but has changed */ + if( dif >= EXISTS_CHANGED && old_item != NULL ) { + debug_print("RSSyl: Item changed, removing old one and adding new.\n"); + + /* Store permflags of the old item. */ + ctx = (RFeedCtx *)old_item->data; + msginfo = folder_item_get_msginfo((FolderItem *)ritem, + atoi(g_path_get_basename(ctx->path))); + oldperm_flags = msginfo->flags.perm_flags; + + ritem->items = g_slist_remove(ritem->items, old_item); + g_remove(ctx->path); + + g_free(ctx->path); + feed_item_free(old_item); + old_item = NULL; + } + + /* Check against list of deleted items. */ + if (rssyl_deleted_check(ritem->deleted_items, feed_item)) { + debug_print("RSSyl: Item '%s' found among deleted items, NOT adding it.\n", + feed_item_get_title(feed_item)); + return; + } + + /* Add a new item, formatting its title along the way */ + debug_print("RSSyl: Adding item '%s'\n", feed_item_get_title(feed_item)); + ritem->items = g_slist_prepend(ritem->items, feed_item_copy(feed_item)); + + dirname = folder_item_get_path(&ritem->item); + template = g_strconcat(dirname, G_DIR_SEPARATOR_S, + RSSYL_TMP_TEMPLATE, NULL); + fd = mkstemp(template); + + f = fdopen(fd, "w"); + if(f == NULL) { + g_warning("Couldn't open file '%s', not adding msg!\n", template); + g_free(template); + return; + } + + /* From */ + if( (tmp = feed_item_get_author(feed_item)) != NULL ) { + if( g_utf8_validate(tmp, -1, NULL)) { + conv_encode_header_full(hdr, 1023, tmp, strlen("From: "), + TRUE, CS_UTF_8); + fprintf(f, "From: %s\n", hdr); + } else + fprintf(f, "From: %s\n", tmp); + } + + /* Date */ + if( (tmpd = feed_item_get_date_modified(feed_item)) != -1 ) { + tmp = createRFC822Date(&tmpd); + debug_print("RSSyl: using date_modified: '%s'\n", tmp); + } else if( (tmpd = feed_item_get_date_published(feed_item)) != -1 ) { + tmp = createRFC822Date(&tmpd); + debug_print("RSSyl: using date_published: '%s'\n", tmp); + } else { + tmpd = time(NULL); + tmp = createRFC822Date(&tmpd); + } + + if( tmp != NULL ) { + fprintf(f, "Date: %s\n", tmp); + g_free(tmp); + } + + if( (tmp = feed_item_get_title(feed_item)) != NULL ) { + + /* (Atom only) Strip HTML markup from title for the Subject line. */ + if( feed_item_get_title_format(feed_item) == FEED_ITEM_TITLE_HTML + || feed_item_get_title_format(feed_item) == FEED_ITEM_TITLE_XHTML) { + debug_print("RSSyl: item title is HTML/XHTML, stripping tags for Subject line\n"); + tmp = g_strdup(tmp); + strip_html(tmp); + } + + if( g_utf8_validate(tmp, -1, NULL) ) { + conv_encode_header_full(hdr, 1023, tmp, strlen("Subject: "), + FALSE, CS_UTF_8); + debug_print("RSSyl: Subject: %s\n", hdr); + fprintf(f, "Subject: %s\n", hdr); + } else + fprintf(f, "Subject: %s\n", tmp); + + if( feed_item_get_title_format(feed_item) == FEED_ITEM_TITLE_HTML + || feed_item_get_title_format(feed_item) == FEED_ITEM_TITLE_XHTML) { + g_free(tmp); + fprintf(f, "X-RSSyl-OrigTitle: %s\n", feed_item_get_title(feed_item)); + } + } else { + debug_print("RSSyl: No feed title, it seems\n"); + fprintf(f, "Subject: (empty)\n"); + } + + /* X-RSSyl-URL */ + if( (tmpurl = feed_item_get_url(feed_item)) == NULL ) { + if( feed_item_get_id(feed_item) != NULL && + feed_item_id_is_permalink(feed_item) ) { + tmpurl = feed_item_get_id(feed_item); + } + } + + if( tmpurl != NULL ) + fprintf(f, "X-RSSyl-URL: %s\n", tmpurl); + + if( ritem->last_update > 0) { + fprintf(f, "X-RSSyl-Last-Seen: %ld\n", ritem->last_update); + } + + /* Message-ID */ + if( (tmpid = feed_item_get_id(feed_item)) == NULL ) + tmpid = feed_item_get_url(feed_item); + if( tmpid != NULL ) + fprintf(f, "Message-ID: <%s>\n", tmpid); + + /* X-RSSyl-Comments */ + if( (text = feed_item_get_comments_url(feed_item)) != NULL ) + fprintf(f, "X-RSSyl-Comments: %s\n", text); + + /* References */ + if( (text = feed_item_get_parent_id(feed_item)) != NULL ) + fprintf(f, "References: <%s>\n", text); + + /* Content-Type */ + text = feed_item_get_text(feed_item); + if( text && g_utf8_validate(text, -1, NULL) ) { + fprintf(f, "Content-Type: text/html; charset=UTF-8\n\n"); + meta_charset = g_strdup(""); + } else { + fprintf(f, "Content-Type: text/html\n\n"); + } + + /* construct base href */ + if( feed_item_get_url(feed_item) != NULL ) + baseurl = g_strdup_printf("\n", + feed_item_get_url(feed_item) ); + + if( ritem->write_heading ) + heading = g_strdup_printf("

%s

\n

\n", + feed_item_get_title(feed_item)); + + /* Message body */ + fprintf(f, "" + "%s\n" + "%s" + "\n\n" + "%s\n" + "URL: %s\n\n

\n" + RSSYL_TEXT_START"\n" + "%s%s" + RSSYL_TEXT_END"\n\n", + (meta_charset ? meta_charset : ""), + (baseurl ? baseurl : ""), + (heading ? heading : ""), + (tmpurl ? tmpurl : ""), + (tmpurl ? tmpurl : "n/a"), + (text ? text : ""), (text ? "\n" : "") ); + + g_free(meta_charset); + g_free(baseurl); + g_free(heading); + + if( (enc = feed_item_get_enclosure(feed_item)) != NULL ) + fprintf(f, "

Attached media file [%s] (%ld bytes)

\n", + feed_item_enclosure_get_url(enc), + feed_item_enclosure_get_type(enc), + feed_item_enclosure_get_size(enc) ); + + fprintf(f, "\n"); + fclose(f); + + g_return_if_fail(template != NULL); + + flags = g_new(MsgFlags, 1); + flags->perm_flags = MSG_NEW | MSG_UNREAD; + flags->tmp_flags = 0; + + d = folder_item_add_msg(&ritem->item, template, flags, TRUE); + g_free(template); + + ctx = g_new0(RFeedCtx, 1); + ctx->path = (gpointer)g_strdup_printf("%s%c%d", dirname, + G_DIR_SEPARATOR, d); + ctx->last_seen = ritem->last_update; + ((FeedItem *)ritem->items->data)->data = (gpointer)ctx; + + /* Unset unread+new if the changed item wasn't set unread and user + * doesn't want to see it unread because of the change. */ + if (!(oldperm_flags & MSG_UNREAD) && (ritem->silent_update == 2 + || (ritem->silent_update == 1 && dif == EXISTS_CHANGED_TEXTONLY))) + procmsg_msginfo_unset_flags( + folder_item_get_msginfo((FolderItem *)ritem, d), MSG_NEW | MSG_UNREAD, 0); + + debug_print("RSSyl: folder_item_add_msg(): %d\n", d); +} diff --git a/src/plugins/rssyl/rssyl_add_item.h b/src/plugins/rssyl/rssyl_add_item.h new file mode 100644 index 000000000..5e5e4e3b0 --- /dev/null +++ b/src/plugins/rssyl/rssyl_add_item.h @@ -0,0 +1,6 @@ +#ifndef __RSSYL_ADD_ITEM_H +#define __RSSYL_ADD_ITEM_H + +void rssyl_add_item(RFolderItem *ritem, FeedItem *feed_item); + +#endif /* __RSSYL_ADD_ITEM_H */ diff --git a/src/plugins/rssyl/rssyl_cb_gtk.c b/src/plugins/rssyl/rssyl_cb_gtk.c deleted file mode 100644 index 0e49bb97b..000000000 --- a/src/plugins/rssyl/rssyl_cb_gtk.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client - * Copyright (C) 1999-2004 Hiroyuki Yamamoto - * This file (C) 2005 Andrej Kacian - * - * - callback handler functions for GTK signals and timeouts - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include - -#include "folderview.h" -#include "alertpanel.h" -#include "gtk/inputdialog.h" - -#include "date.h" -#include "feed.h" -#include "rssyl.h" -#include "rssyl_gtk.h" -#include "rssyl_cb_gtk.h" -#include "prefs_common.h" - -gboolean rssyl_props_cancel_cb(GtkWidget *widget, gpointer data) -{ - RSSylFolderItem *ritem = (RSSylFolderItem *)data; - - debug_print("RSSyl: Cancel pressed\n"); - gtk_widget_destroy(ritem->feedprop->window); - return FALSE; -} - -gboolean rssyl_props_ok_cb(GtkWidget *widget, gpointer data) -{ - RSSylFolderItem *ritem = (RSSylFolderItem *)data; - - debug_print("RSSyl: OK pressed\n"); - rssyl_gtk_prop_store(ritem); - - gtk_widget_destroy(ritem->feedprop->window); - - return FALSE; -} - -gboolean rssyl_refresh_timeout_cb(gpointer data) -{ - RSSylRefreshCtx *ctx = (RSSylRefreshCtx *)data; - time_t tt = time(NULL); - gchar *tmpdate; - - g_return_val_if_fail(ctx != NULL, FALSE); - - if( prefs_common_get_prefs()->work_offline) - return TRUE; - - if( ctx->ritem == NULL || ctx->ritem->url == NULL ) { - debug_print("RSSyl: refresh_timeout_cb - ritem or url NULL\n"); - g_free(ctx); - return FALSE; - } - - if( ctx->id != ctx->ritem->refresh_id ) { - tmpdate = createRFC822Date(&tt); - debug_print(" %s: timeout id changed, stopping: %d != %d\n", - tmpdate, ctx->id, ctx->ritem->refresh_id); - g_free(tmpdate); - g_free(ctx); - return FALSE; - } - - tmpdate = createRFC822Date(&tt); - debug_print(" %s: refresh %s (%d)\n", tmpdate, ctx->ritem->url, - ctx->ritem->refresh_id); - g_free(tmpdate); - rssyl_update_feed(ctx->ritem); - - return TRUE; -} - -gboolean rssyl_default_refresh_interval_toggled_cb(GtkToggleButton *default_ri, - gpointer data) -{ - gboolean active = gtk_toggle_button_get_active(default_ri); - GtkWidget *refresh_interval = (GtkWidget *)data; - - debug_print("default is %s\n", ( active ? "ON" : "OFF" ) ); - - gtk_widget_set_sensitive(refresh_interval, !active); - - return FALSE; -} - -gboolean rssyl_default_expired_num_toggled_cb(GtkToggleButton *default_ex, - gpointer data) -{ - gboolean active = gtk_toggle_button_get_active(default_ex); - GtkWidget *expired_num = (GtkWidget *)data; - - debug_print("default is %s\n", ( active ? "ON" : "OFF" ) ); - - gtk_widget_set_sensitive(expired_num, !active); - - return FALSE; -} - -gboolean rssyl_fetch_comments_toggled_cb(GtkToggleButton *fetch_comments, - gpointer data) -{ - gboolean active = gtk_toggle_button_get_active(fetch_comments); - GtkWidget *num_comments = (GtkWidget *)data; - - debug_print("fetch comments is %s\n", (active ? "ON" : "OFF") ); - - gtk_widget_set_sensitive(num_comments, active); - - return FALSE; -} - -gboolean rssyl_props_key_press_cb(GtkWidget *widget, GdkEventKey *event, - gpointer data) -{ - if (event) { - switch (event->keyval) { - case GDK_Escape: - rssyl_props_cancel_cb(widget, data); - return TRUE; - case GDK_Return: - case GDK_KP_Enter: - rssyl_props_ok_cb(widget, data); - return TRUE; - default: - break; - } - } - - return FALSE; -} diff --git a/src/plugins/rssyl/rssyl_cb_gtk.h b/src/plugins/rssyl/rssyl_cb_gtk.h deleted file mode 100644 index a49d3a31d..000000000 --- a/src/plugins/rssyl/rssyl_cb_gtk.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __RSSYL_GTK_CB -#define __RSSYL_GTK_CB - -#include -#include - -gboolean rssyl_props_cancel_cb(GtkWidget *widget, gpointer data); -gboolean rssyl_props_ok_cb(GtkWidget *widget, gpointer data); -gboolean rssyl_refresh_timeout_cb(gpointer data); -gboolean rssyl_default_refresh_interval_toggled_cb(GtkToggleButton *default_ri, - gpointer data); -gboolean rssyl_default_expired_num_toggled_cb(GtkToggleButton *default_ex, - gpointer data); -gboolean rssyl_fetch_comments_toggled_cb(GtkToggleButton *fetch_comments, - gpointer data); -gboolean rssyl_props_key_press_cb(GtkWidget *widget, GdkEventKey *event, - gpointer data); - -#endif /* __RSSYL_GTK_CB */ diff --git a/src/plugins/rssyl/rssyl_cb_menu.c b/src/plugins/rssyl/rssyl_cb_menu.c index f3047dea0..d0c2ef99f 100644 --- a/src/plugins/rssyl/rssyl_cb_menu.c +++ b/src/plugins/rssyl/rssyl_cb_menu.c @@ -1,5 +1,5 @@ /* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 1999-2004 Hiroyuki Yamamoto * This file (C) 2005 Andrej Kacian * @@ -22,34 +22,38 @@ #ifdef HAVE_CONFIG_H # include "config.h" -#include "claws-features.h" #endif +/* Global includes */ #include #include - #include -#include "folderview.h" -#include "alertpanel.h" -#include "gtk/inputdialog.h" -#include "prefs_common.h" -#include "filesel.h" -#include "inc.h" -#include "folder_item_prefs.h" - -#include "feed.h" -#include "feedprops.h" -#include "opml.h" -#include "rssyl.h" +/* Claws Mail includes */ +#include +#include +#include +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/parser_opml.h" #include "rssyl_gtk.h" - -void rssyl_new_feed_cb(GtkAction *action, gpointer data) +#include "rssyl_feed.h" +#include "rssyl_feed_props.h" +#include "rssyl_update_feed.h" +#include "rssyl_subscribe.h" +#include "opml_import.h" + +void rssyl_new_feed_cb(GtkAction *action, + gpointer data) { - FolderView *folderview = (FolderView *)data; + FolderView *folderview = (FolderView*)data; GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); FolderItem *item; - gchar *new_feed; + gchar *url; debug_print("RSSyl: new_feed_cb\n"); @@ -59,26 +63,26 @@ void rssyl_new_feed_cb(GtkAction *action, gpointer data) g_return_if_fail(item != NULL); g_return_if_fail(item->folder != NULL); - new_feed = input_dialog(_("Subscribe feed"), - _("Input the URL of the news feed you wish to subscribe:"), - ""); - g_return_if_fail(new_feed != NULL); + url = input_dialog(_("Subscribe feed"), + _("Input the URL of the news feed you wish to subscribe:"), + ""); + if( url == NULL ) /* User cancelled */ + return; - rssyl_subscribe_new_feed(item, new_feed, TRUE); + rssyl_subscribe(item, url, TRUE); - g_free(new_feed); + g_free(url); } -void rssyl_new_folder_cb(GtkAction *action, gpointer data) +void rssyl_new_folder_cb(GtkAction *action, + gpointer data) { - FolderView *folderview = (FolderView *)data; + FolderView *folderview = (FolderView*)data; GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); FolderItem *item; FolderItem *new_item; - gchar *new_folder; - gchar *name; - gchar *p; - RSSylFolderItem *ritem = NULL; + gchar *new_folder, *p, *tmp; + gint i = 1; if (!folderview->selected) return; @@ -90,132 +94,44 @@ void rssyl_new_folder_cb(GtkAction *action, gpointer data) _("Input the name of new folder:"), _("NewFolder")); if (!new_folder) return; - AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); p = strchr(new_folder, G_DIR_SEPARATOR); if (p) { - alertpanel_error(_("'%c' can't be included in folder name."), + alertpanel_error(_("'%c' can't be used in folder name."), G_DIR_SEPARATOR); + g_free(new_folder); return; } - name = trim_string(new_folder, 32); - AUTORELEASE_STR(name, {g_free(name); return;}); - - /* find whether the directory already exists */ - if (folder_find_child_item_by_name(item, new_folder)) { - alertpanel_error(_("The folder '%s' already exists."), name); - return; + /* Find an unused name for new folder */ + /* TODO: Perhaps stop after X attempts? */ + tmp = g_strdup(new_folder); + while (folder_find_child_item_by_name(item, tmp)) { + debug_print("RSSyl: Folder '%s' already exists, trying another name\n", + new_folder); + g_free(tmp); + tmp = g_strdup_printf("%s__%d", new_folder, ++i); } + g_free(new_folder); + new_folder = tmp; + new_item = folder_create_folder(item, new_folder); if (!new_item) { - alertpanel_error(_("Can't create the folder '%s'."), name); - return; - } - - ritem = (RSSylFolderItem *)new_item; - ritem->url = NULL; - - folder_write_list(); -} - -void rssyl_remove_rss_cb(GtkAction *action, gpointer data) -{ - FolderView *folderview = (FolderView *)data; - FolderItem *item; - gchar *name, *message; - AlertValue aval; - - debug_print("RSSyl: remove_rss_cb\n"); - - item = folderview_get_selected_item(folderview); - g_return_if_fail(item != NULL); - g_return_if_fail(item->folder != NULL); - g_return_if_fail( !folder_item_parent(item) ); - - name = trim_string(item->folder->name, 32); - message = g_strdup_printf(_("Really remove the folder tree '%s'?\n"), name); - aval = alertpanel_full(_("Remove folder tree"), message, - GTK_STOCK_CANCEL, _("_Remove"), NULL, FALSE, NULL, - ALERT_WARNING, G_ALERTDEFAULT); - g_free(message); - g_free(name); - - if (aval != G_ALERTALTERNATE) - return; - - folderview_unselect(folderview); - summary_clear_all(folderview->summaryview); - - folder_destroy(item->folder); -} - -void rssyl_remove_feed_cb(GtkAction *action, gpointer data) -{ - FolderView *folderview = (FolderView *)data; - GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); - FolderItem *item; - gint response; - GtkWidget *dialog, *rmcache_widget = NULL; - gboolean rmcache = FALSE; - debug_print("RSSyl: remove_feed_cb\n"); - - item = folderview_get_selected_item(folderview); - g_return_if_fail(item != NULL); - g_return_if_fail(item->path != NULL); - g_return_if_fail(item->folder != NULL); - - dialog = rssyl_feed_removal_dialog(item->name, &rmcache_widget); - - g_return_if_fail(dialog != NULL); - - gtk_widget_show_all(dialog); - - response = gtk_dialog_run(GTK_DIALOG(dialog)); - - if( response != GTK_RESPONSE_YES ) { - debug_print("'No' clicked, returning\n"); - gtk_widget_destroy(dialog); + alertpanel_error(_("Can't create the folder '%s'."), new_folder); + g_free(new_folder); return; } - g_return_if_fail(rmcache_widget != NULL); - - rmcache = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(rmcache_widget) ); - - gtk_widget_destroy(dialog); - - if( folderview->opened == folderview->selected || - gtk_cmctree_is_ancestor(ctree, - folderview->selected, - folderview->opened) ) { - summary_clear_all(folderview->summaryview); - folderview->opened = NULL; - } - - rssyl_remove_feed_props((RSSylFolderItem *)item); - - if( rmcache == TRUE ) - rssyl_remove_feed_cache(item); - - if( item->folder->klass->remove_folder(item->folder, item) < 0 ) { - gchar *tmp; - tmp = g_markup_printf_escaped(_("Can't remove feed '%s'."), item->name); - alertpanel_error("%s", tmp); - g_free(tmp); - if( folderview->opened == folderview->selected ) - summary_show(folderview->summaryview, - folderview->summaryview->folder_item); - return; - } + g_free(new_folder); folder_write_list(); } -void rssyl_remove_folder_cb(GtkAction *action, gpointer data) +void rssyl_remove_folder_cb(GtkAction *action, + gpointer data) { - FolderView *folderview = (FolderView *)data; + FolderView *folderview = (FolderView*)data; GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); FolderItem *item; gchar *message, *name; @@ -263,45 +179,82 @@ void rssyl_remove_folder_cb(GtkAction *action, gpointer data) } -void rssyl_refresh_cb(GtkAction *action, gpointer data) +void rssyl_rename_cb(GtkAction *action, + gpointer *data) { - FolderView *folderview = (FolderView *)data; FolderItem *item; - RSSylFolderItem *ritem; - + gchar *new_folder; + gchar *name; + gchar *message; + FolderView *folderview = (FolderView*)data; item = folderview_get_selected_item(folderview); g_return_if_fail(item != NULL); + g_return_if_fail(item->path != NULL); g_return_if_fail(item->folder != NULL); - ritem = (RSSylFolderItem *)item; + name = trim_string(item->name, 32); + message = g_strdup_printf(_("Input new name for '%s':"), name); + new_folder = input_dialog(_("Rename folder"), message, name); + g_free(message); + g_free(name); + if (!new_folder) return; + AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); + + if (strchr(new_folder, G_DIR_SEPARATOR) != NULL) { + alertpanel_error(_("'%c' can't be included in folder name."), + G_DIR_SEPARATOR); + return; + } - if (prefs_common_get_prefs()->work_offline && - !inc_offline_should_override(TRUE, - ngettext( - "Claws Mail needs network access in order " - "to update the feed.", - "Claws Mail needs network access in order " - "to update the feeds.", 1))) { - return; + if (folder_find_child_item_by_name(folder_item_parent(item), new_folder)) { + name = trim_string(new_folder, 32); + alertpanel_error(_("The folder '%s' already exists."), name); + g_free(name); + return; } - main_window_cursor_wait(mainwindow_get_mainwindow()); - rssyl_update_feed(ritem); - main_window_cursor_normal(mainwindow_get_mainwindow()); + if (folder_item_rename(item, new_folder) < 0) { + alertpanel_error(_("The folder could not be renamed.\n" + "The new folder name is not allowed.")); + return; + } + + folder_item_prefs_save_config(item); + folder_write_list(); } -void rssyl_refresh_all_cb(GtkAction *action, gpointer data) +void rssyl_refresh_feed_cb(GtkAction *action, + gpointer data) { - main_window_cursor_wait(mainwindow_get_mainwindow()); - rssyl_refresh_all_feeds(); - main_window_cursor_normal(mainwindow_get_mainwindow()); + FolderView *folderview = (FolderView*)data; + FolderItem *item = NULL; + RFolderItem *ritem = NULL; + + item = folderview_get_selected_item(folderview); + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + ritem = (RFolderItem *)item; + + /* Offline check */ + if( prefs_common.work_offline && + !inc_offline_should_override(TRUE, + ngettext("Claws-Mail needs network access in order " + "to update the feed.", + "Claws-Mail needs network access in order " + "to update feeds.", 1))) { + return; + } + + /* Update feed, displaying errors if any. */ + rssyl_update_feed(ritem, TRUE); } void rssyl_prop_cb(GtkAction *action, gpointer data) { - FolderView *folderview = (FolderView *)data; + FolderView *folderview = (FolderView*)data; FolderItem *item; - RSSylFolderItem *ritem; + RFolderItem *ritem; item = folderview_get_selected_item(folderview); g_return_if_fail(item != NULL); @@ -309,74 +262,103 @@ void rssyl_prop_cb(GtkAction *action, gpointer data) debug_print("RSSyl: rssyl_prop_cb() for '%s'\n", item->name); - ritem = (RSSylFolderItem *)item; - rssyl_get_feed_props(ritem); + ritem = (RFolderItem *)item; + rssyl_gtk_prop(ritem); } -void rssyl_rename_cb(GtkAction *action, gpointer data) +void rssyl_update_all_cb( GtkAction *action, gpointer data) { - FolderView *folderview = (FolderView *)data; FolderItem *item; - gchar *new_folder; - gchar *name; - gchar *message; + FolderView *folderview = (FolderView*)data; item = folderview_get_selected_item(folderview); g_return_if_fail(item != NULL); - g_return_if_fail(item->path != NULL); g_return_if_fail(item->folder != NULL); - name = trim_string(item->name, 32); - message = g_strdup_printf(_("Input new name for '%s':"), name); - new_folder = input_dialog(_("Rename folder"), message, item->name); - g_free(message); - g_free(name); - if (!new_folder) return; - AUTORELEASE_STR(new_folder, {g_free(new_folder); return;}); + debug_print("RSSyl: rssyl_update_all_cb(): clicked on '%s'\n", item->name); - if (strchr(new_folder, G_DIR_SEPARATOR) != NULL) { - alertpanel_error(_("'%c' can't be included in folder name."), - G_DIR_SEPARATOR); + if( item->folder->klass != rssyl_folder_get_class() ) { + debug_print("RSSyl: this is not a RSSyl folder, returning\n"); return; } - if (folder_find_child_item_by_name(folder_item_parent(item), new_folder)) { - name = trim_string(new_folder, 32); - alertpanel_error(_("The folder '%s' already exists."), name); - g_free(name); + rssyl_update_recursively(item); +} + +void rssyl_remove_mailbox_cb(GtkAction *action, gpointer data) +{ + FolderView *folderview = (FolderView *)data; + FolderItem *item = NULL; + gchar *n, *message; + AlertValue avalue; + + item = folderview_get_selected_item(folderview); + + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if( folder_item_parent(item) ) return; - } - if (folder_item_rename(item, new_folder) < 0) { - alertpanel_error(_("The folder could not be renamed.\n" - "The new folder name is not allowed.")); + n = trim_string(item->folder->name, 32); + message = g_strdup_printf(_("Really remove the feed tree `%s' ?\n"), n); + avalue = alertpanel_full(_("Remove feed tree"), message, + GTK_STOCK_CANCEL, _("_Remove"), NULL, FALSE, + NULL, ALERT_WARNING, G_ALERTDEFAULT); + g_free(message); + g_free(n); + + if( avalue != G_ALERTALTERNATE ) + return; + + folderview_unselect(folderview); + summary_clear_all(folderview->summaryview); + + n = folder_item_get_path(item); + if( remove_dir_recursive(n) < 0 ) { + g_warning("can't remove directory '%s'\n", n); + g_free(n); return; } - folder_item_prefs_save_config_recursive(item); - folder_write_list(); + g_free(n); + folder_destroy(item->folder); } void rssyl_import_feed_list_cb(GtkAction *action, gpointer data) { FolderView *folderview = (FolderView *)data; - debug_print("RSSyl: rssyl_import_feed_cb\n"); + GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree); + FolderItem *item = NULL; + gchar *path = NULL; + OPMLImportCtx *ctx = NULL; - FolderItem *item; - gchar *opmlfile = NULL; + debug_print("RSSyl: import_feed_list_cb\n"); - item = folderview_get_selected_item(folderview); + /* Ask user for a file to import */ + path = filesel_select_file_open_with_filter( + _("Select an OPML file"), NULL, "*.opml"); + if (!is_file_exist(path)) { + g_free(path); + return; + } + + /* Find the destination folder for the import */ + g_return_if_fail(folderview->selected != NULL); + item = gtk_cmctree_node_get_row_data(ctree, folderview->selected); g_return_if_fail(item != NULL); g_return_if_fail(item->folder != NULL); - opmlfile = filesel_select_file_open_with_filter( - _("Select a .opml file"), NULL, "*.opml"); - - if (!is_file_exist(opmlfile)) { - g_free(opmlfile); - return; - } + ctx = malloc( sizeof(OPMLImportCtx) ); + ctx->failures = 0; + ctx->depth = rssyl_folder_depth(item); + ctx->current = NULL; + ctx->current = g_slist_append(ctx->current, item); + + /* Start the whole shebang - call libfeed's OPML parser with correct + * user function */ + opml_process(path, rssyl_opml_import_func, (gpointer)ctx); - rssyl_opml_import(opmlfile, item); + g_free(ctx); } diff --git a/src/plugins/rssyl/rssyl_cb_menu.h b/src/plugins/rssyl/rssyl_cb_menu.h index 098f43c2d..258f9a5a8 100644 --- a/src/plugins/rssyl/rssyl_cb_menu.h +++ b/src/plugins/rssyl/rssyl_cb_menu.h @@ -7,14 +7,13 @@ #include "folderview.h" void rssyl_new_feed_cb(GtkAction *action, gpointer data); -void rssyl_remove_feed_cb(GtkAction *action, gpointer data); -void rssyl_remove_rss_cb(GtkAction *action, gpointer data); -void rssyl_refresh_cb(GtkAction *action, gpointer data); -void rssyl_prop_cb(GtkAction *action, gpointer data); -void rssyl_import_feed_list_cb(GtkAction *action, gpointer data); -void rssyl_refresh_all_cb(GtkAction *action, gpointer data); void rssyl_rename_cb(GtkAction *action, gpointer data); void rssyl_new_folder_cb(GtkAction *action, gpointer data); void rssyl_remove_folder_cb(GtkAction *action, gpointer data); +void rssyl_refresh_feed_cb(GtkAction *action, gpointer data); +void rssyl_prop_cb(GtkAction *action, gpointer data); +void rssyl_update_all_cb(GtkAction *action, gpointer data); +void rssyl_remove_mailbox_cb(GtkAction *action, gpointer data); +void rssyl_import_feed_list_cb(GtkAction *action, gpointer data); #endif /* __RSSYL_MENU_FEED */ diff --git a/src/plugins/rssyl/rssyl_deleted.c b/src/plugins/rssyl/rssyl_deleted.c new file mode 100644 index 000000000..2d39adb8f --- /dev/null +++ b/src/plugins/rssyl/rssyl_deleted.c @@ -0,0 +1,354 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * - handling of info about user-deleted feed items + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include +#include + +/* Local includes */ +#include "rssyl.h" +#include "parse822.h" +#include "strutils.h" + +static RDeletedItem *_new_deleted_item() +{ + RDeletedItem *ditem = g_new0(RDeletedItem, 1); + + ditem->id = NULL; + ditem->title = NULL; + ditem->date_published = -1; + ditem->date_modified = -1; + + return ditem; +} + +static void _free_deleted_item(gpointer d, gpointer user_data) +{ + RDeletedItem *ditem = (RDeletedItem *)d; + + if (ditem == NULL) + return; + + g_free(ditem->id); + g_free(ditem->title); + g_free(ditem); +} + +void rssyl_deleted_free(GSList *deleted_items) +{ + if (deleted_items != NULL) { + debug_print("RSSyl: releasing list of deleted items\n"); + g_slist_foreach(deleted_items, _free_deleted_item, NULL); + g_slist_free(deleted_items); + deleted_items = NULL; + } +} + +static gchar * _deleted_file_path(RFolderItem *ritem) +{ + gchar *itempath, *deleted_file; + + itempath = folder_item_get_path(&ritem->item); + deleted_file = g_strconcat(itempath, G_DIR_SEPARATOR_S, RSSYL_DELETED_FILE, NULL); + g_free(itempath); + + return deleted_file; +} + +/***************************************************************/ +GSList *rssyl_deleted_update(RFolderItem *ritem) +{ + gchar *deleted_file, *contents, **lines, **line; + GError *error = NULL; + guint i = 0; + RDeletedItem *ditem = NULL; + GSList *deleted_items = NULL; + + g_return_val_if_fail(ritem != NULL, NULL); + + deleted_file = _deleted_file_path(ritem); + + debug_print("RSSyl: getting list of deleted items from '%s'\n", deleted_file); + + if (!g_file_test(deleted_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + debug_print("RSSyl: '%s' doesn't exist, ignoring\n", deleted_file); + return NULL; + } + + g_file_get_contents(deleted_file, &contents, NULL, &error); + + if (error) + g_warning("GError: '%s'\n", error->message); + + if (contents != NULL) { + lines = strsplit_no_copy(contents, '\n'); + } else { + g_warning("Couldn't read '%s', ignoring\n", deleted_file); + g_free(deleted_file); + return NULL; + } + + g_free(deleted_file); + + while (lines[i]) { + line = g_strsplit(lines[i], ": ", 2); + if (line[0] && line[1] && strlen(line[0]) && strlen(line[1])) { + if (!strcmp(line[0], "ID")) { + ditem = _new_deleted_item(); + ditem->id = g_strdup(line[1]); + } else if (ditem != NULL && !strcmp(line[0], "TITLE")) { + ditem->title = g_strdup(line[1]); + } else if (ditem != NULL && !strcmp(line[0], "DPUB")) { + ditem->date_published = atoi(line[1]); + } else if (ditem != NULL && !strcmp(line[0], "DMOD")) { + ditem->date_modified = atoi(line[1]); + deleted_items = g_slist_prepend(deleted_items, ditem); + ditem = NULL; + } + } + + g_strfreev(line); + i++; + } + + g_free(lines); + g_free(contents); + + debug_print("RSSyl: got %d deleted items\n", g_slist_length(deleted_items)); + return deleted_items; +} + +static void _store_one_deleted_item(gpointer data, gpointer user_data) +{ + RDeletedItem *ditem = (RDeletedItem *)data; + FILE *f = (FILE *)user_data; + gboolean err = FALSE; + + if (ditem == NULL || ditem->id == NULL) + return; + + err |= (fprintf(f, + "ID: %s\n" + "TITLE: %s\n" + "DPUB: %ld\n" + "DMOD: %ld\n", + ditem->id, ditem->title, + ditem->date_published, ditem->date_modified) < 0); + + if (err) + debug_print("RSSyl: Error during writing deletion file.\n"); +} + +static void rssyl_deleted_store_internal(GSList *deleted_items, const gchar *deleted_file) +{ + FILE *f; + + if (g_file_test(deleted_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) + g_remove(deleted_file); + + if (g_slist_length(deleted_items) == 0) + return; + + if ((f = g_fopen(deleted_file, "w")) == NULL) { + debug_print("RSSyl: Couldn't open '%s', ignoring.\n", deleted_file); + return; + } + + g_slist_foreach(deleted_items, (GFunc)_store_one_deleted_item, + (gpointer)f); + + fclose(f); + debug_print("RSSyl: written and closed deletion file\n"); +} + +void rssyl_deleted_store(RFolderItem *ritem) +{ + gchar *path; + + g_return_if_fail(ritem != NULL); + + path = _deleted_file_path(ritem); + rssyl_deleted_store_internal(ritem->deleted_items, path); +} + + +/* Creates a FeedItem from a message file and uses the data to add a item + * to the list of deleted stuff. */ +void rssyl_deleted_add(RFolderItem *ritem, gchar *path) +{ + FeedItem *fitem = NULL; + RDeletedItem *ditem = NULL; + GSList *deleted_items = rssyl_deleted_update(ritem); + gchar *deleted_file = NULL; + + if (!(fitem = rssyl_parse_folder_item_file(path))) + return; + + ditem = _new_deleted_item(); + ditem->id = g_strdup(feed_item_get_id(fitem)); + ditem->title = conv_unmime_header(feed_item_get_title(fitem), + CS_UTF_8, FALSE); + ditem->date_published = feed_item_get_date_published(fitem); + ditem->date_modified = feed_item_get_date_modified(fitem); + + deleted_items = g_slist_prepend(deleted_items, ditem); + + deleted_file = _deleted_file_path(ritem); + rssyl_deleted_store_internal(deleted_items, deleted_file); + g_free(deleted_file); + + rssyl_deleted_free(deleted_items); +} + +static gint _rssyl_deleted_check_func(gconstpointer a, gconstpointer b) +{ + RDeletedItem *ditem = (RDeletedItem *)a; + FeedItem *fitem = (FeedItem *)b; + + g_return_val_if_fail(ditem != NULL, -10); + g_return_val_if_fail(fitem != NULL, -20); + + /* Following must match: + * ID, ... */ + if (!ditem->id || !feed_item_get_id(fitem) || + strcmp(ditem->id, feed_item_get_id(fitem))) + return -1; + + /* title, ... */ + if (!ditem->title || !feed_item_get_title(fitem) || + strcmp(ditem->title, feed_item_get_title(fitem))) + return -2; + + /* time of publishing, ... */ + if (ditem->date_published != -1 && + ditem->date_published != feed_item_get_date_published(fitem)) + return -3; + + /* and the time of last modification must be greater + * (this means that the item was updated since deletion, and possibly + * contains new data, so we add it again) */ + if (ditem->date_modified != -1 && + ditem->date_modified < feed_item_get_date_modified(fitem)) + return -4; + + return 0; +} + +/* Returns TRUE if fitem is found among the deleted stuff. */ +gboolean rssyl_deleted_check(GSList *deleted_items, FeedItem *fitem) +{ + if (g_slist_find_custom(deleted_items, (gconstpointer)fitem, + _rssyl_deleted_check_func) != NULL) + return TRUE; + + return FALSE; +} + +/******** Expiring ********/ +struct _RDelExpireCtx { + RDeletedItem *ditem; + gboolean delete; +}; + +typedef struct _RDelExpireCtx RDelExpireCtx; + +static void _rssyl_deleted_expire_func_f(gpointer data, gpointer user_data) +{ + FeedItem *fitem = (FeedItem *)data; + RDelExpireCtx *ctx = (RDelExpireCtx *)user_data; + + /* Following must match: + * ID, ... */ + if (!ctx->ditem->id || !feed_item_get_id(fitem) || + strcmp(ctx->ditem->id, feed_item_get_id(fitem))) + return; + + /* title, ... */ + if (!ctx->ditem->title || !feed_item_get_title(fitem) || + strcmp(ctx->ditem->title, feed_item_get_title(fitem))) + return; + + /* time of publishing, ... */ + if (ctx->ditem->date_published != feed_item_get_date_published(fitem)) + return; + + /* and the time of last modification must be greater + * (this means that the item was updated since deletion, and possibly + * contains new data, so we add it again) */ + if (ctx->ditem->date_modified != feed_item_get_date_modified(fitem)) + return; + + ctx->delete = FALSE; +} + +/* Checks each item in deleted items list against feed and removes it if + * it is not found there anymore. */ +void rssyl_deleted_expire(RFolderItem *ritem, Feed *feed) +{ + GSList *d = NULL, *d2; + RDelExpireCtx *ctx = NULL; + RDeletedItem *ditem; + + g_return_if_fail(ritem != NULL); + g_return_if_fail(feed != NULL); + + ritem->deleted_items = rssyl_deleted_update(ritem); + + /* Iterate over all items in the list */ + d = ritem->deleted_items; + while (d) { + ditem = (RDeletedItem *)d->data; + ctx = g_new0(RDelExpireCtx, 1); + ctx->ditem = ditem; + ctx->delete = TRUE; + + /* Adjust ctx->delete accordingly */ + feed_foreach_item(feed, _rssyl_deleted_expire_func_f, (gpointer)ctx); + + /* Remove the item if necessary */ + if (ctx->delete) { + debug_print("RSSyl: (DELETED) removing '%s' from list\n", ditem->title); + d2 = d->next; + ritem->deleted_items = g_slist_remove_link(ritem->deleted_items, d); + d = d2; + continue; + } else { + d = d->next; + } + + g_free(ctx); + } + + /* Write the new list to disk */ + rssyl_deleted_store(ritem); + + /* And clean up after myself */ + rssyl_deleted_free(ritem->deleted_items); +} diff --git a/src/plugins/rssyl/rssyl_deleted.h b/src/plugins/rssyl/rssyl_deleted.h new file mode 100644 index 000000000..d3c198d71 --- /dev/null +++ b/src/plugins/rssyl/rssyl_deleted.h @@ -0,0 +1,14 @@ +#ifndef __RSSYL_DELETED_H +#define __RSSYL_DELETED_H + +#include + +#include "rssyl.h" + +GSList *rssyl_deleted_update(RFolderItem *ritem); +void *rssyl_deleted_free(GSList *deleted_items); +void rssyl_deleted_add(RFolderItem *ritem, gchar *path); +gboolean rssyl_deleted_check(GSList *deleted_items, FeedItem *fitem); +void rssyl_deleted_expire(RFolderItem *ritem, Feed *feed); + +#endif /* __RSSYL_DELETED_H */ diff --git a/src/plugins/rssyl/rssyl_feed.c b/src/plugins/rssyl/rssyl_feed.c new file mode 100644 index 000000000..46edc014d --- /dev/null +++ b/src/plugins/rssyl/rssyl_feed.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feeditem.h" +#include "libfeed/date.h" +#include "rssyl.h" +#include "rssyl_feed.h" +#include "rssyl_prefs.h" +#include "rssyl_update_feed.h" +#include "strutils.h" + +FolderItem *rssyl_feed_subscribe_new(FolderItem *parent, const gchar *url, + gboolean verbose) +{ + gchar *myurl = NULL, *tmpname = NULL; + FolderItem *new_item = NULL; + RFolderItem *ritem = NULL; + gboolean success = FALSE; + + g_return_val_if_fail(parent != NULL, FALSE); + g_return_val_if_fail(url != NULL, FALSE); + + log_print(LOG_PROTOCOL, RSSYL_LOG_SUBSCRIBING, url); + + if( !strncmp(url, "feed://", 7) ) + myurl = g_strdup(url+7); + else if( !strncmp(url, "feed:", 5) ) + myurl = g_strdup(url+5); + else + myurl = g_strdup(url); + + myurl = g_strchomp(myurl); + + gtk_cmclist_freeze(GTK_CMCLIST(mainwindow_get_mainwindow()->folderview->ctree)); + folder_item_update_freeze(); + + /* Create a feed folder with generic name. */ + tmpname = g_strdup_printf("%s.%ld", RSSYL_NEW_FOLDER_NAME, (long int)time(NULL)); + new_item = folder_create_folder(parent, tmpname); + g_free(tmpname); + if( !new_item ) { + if( verbose ) + alertpanel_error(_("Couldn't create folder for new feed '%s'."), + myurl); + g_free(myurl); + return NULL; + } + + /* Set it up as a RSSyl folder */ + ritem = (RFolderItem *)new_item; + ritem->url = g_strdup(myurl); + + /* Try to update it, delete if failed. + * (it is renamed in rssyl_update_feed(). */ + if( (success = rssyl_update_feed(ritem, verbose)) == FALSE ) + new_item->folder->klass->remove_folder(new_item->folder, new_item); + else { + folder_item_scan(new_item); + folder_write_list(); + } + + folder_item_update_thaw(); + gtk_cmclist_thaw(GTK_CMCLIST(mainwindow_get_mainwindow()->folderview->ctree)); + + if( success ) + log_print(LOG_PROTOCOL, RSSYL_LOG_SUBSCRIBED, ritem->official_title, + ritem->url); + else { + debug_print("RSSyl: Failed to add feed '%s'\n", myurl); + g_free(myurl); + return NULL; + } + + return new_item; +} + +MsgInfo *rssyl_feed_parse_item_to_msginfo(gchar *file, MsgFlags flags, + gboolean a, gboolean b, FolderItem *item) +{ + MsgInfo *msginfo; + + g_return_val_if_fail(item != NULL, NULL); + + msginfo = procheader_parse_file(file, flags, a, b); + if (msginfo) + msginfo->folder = item; + + return msginfo; +} + +gboolean rssyl_refresh_timeout_cb(gpointer data) +{ + RRefreshCtx *ctx = (RRefreshCtx *)data; + time_t tt = time(NULL); + gchar *tmpdate = NULL; + + g_return_val_if_fail(ctx != NULL, FALSE); + + if( prefs_common.work_offline) + return TRUE; + + if( ctx->ritem == NULL || ctx->ritem->url == NULL ) { + debug_print("RSSyl: refresh_timeout_cb - ritem or url NULL\n"); + g_free(ctx); + return FALSE; + } + + if( ctx->id != ctx->ritem->refresh_id ) { + tmpdate = createRFC822Date(&tt); + debug_print("RSSyl: %s: timeout id changed, stopping: %d != %d\n", + tmpdate, ctx->id, ctx->ritem->refresh_id); + g_free(tmpdate); + g_free(ctx); + return FALSE; + } + + tmpdate = createRFC822Date(&tt); + debug_print(" %s: refresh %s (%d)\n", tmpdate, ctx->ritem->url, + ctx->ritem->refresh_id); + g_free(tmpdate); + rssyl_update_feed(ctx->ritem, FALSE); + + return TRUE; +} + +void rssyl_feed_start_refresh_timeout(RFolderItem *ritem) +{ + RRefreshCtx *ctx; + guint source_id; + RPrefs *rsprefs = NULL; + + g_return_if_fail(ritem != NULL); + + if( ritem->default_refresh_interval ) { + rsprefs = rssyl_prefs_get(); + if( !rsprefs->refresh_enabled ) + return; + ritem->refresh_interval = rsprefs->refresh; + } + + ctx = g_new0(RRefreshCtx, 1); + ctx->ritem = ritem; + + source_id = g_timeout_add(ritem->refresh_interval * 60 * 1000, + (GSourceFunc)rssyl_refresh_timeout_cb, ctx ); + ritem->refresh_id = source_id; + ctx->id = source_id; + + debug_print("RSSyl: start_refresh_timeout - %d min (id %d)\n", + ritem->refresh_interval, ctx->id); +} diff --git a/src/plugins/rssyl/rssyl_feed.h b/src/plugins/rssyl/rssyl_feed.h new file mode 100644 index 000000000..e34fc1086 --- /dev/null +++ b/src/plugins/rssyl/rssyl_feed.h @@ -0,0 +1,27 @@ +#ifndef __RSSYL_FEED_H +#define __RSSYL_FEED_H + +#include + +#include + +#include "rssyl.h" + +#define RSSYL_LOG_SUBSCRIBING _("RSSyl: Subscribing new feed: %s\n") +#define RSSYL_LOG_SUBSCRIBED _("RSSyl: New feed subscribed: '%s' (%s)\n") +#define RSSYL_LOG_UPDATING _("RSSyl: Updating feed: %s\n") +#define RSSYL_LOG_UPDATED _("RSSyl: Feed update finished: %s\n") +#define RSSYL_LOG_ERROR_FETCH _("RSSyl: Error fetching feed at '%s': %s\n") +#define RSSYL_LOG_ERROR_NOFEED _("RSSyl: No valid feed found at '%s'\n") +#define RSSYL_LOG_ERROR_PROC _("RSSyl: Couldn't process feed at '%s'\n") +#define RSSYL_LOG_ABORTED_EXITING _("RSSyl: Application is exiting, couldn't finish updating feed at '%s'\n") + +FolderItem *rssyl_feed_subscribe_new(FolderItem *parent, const gchar *url, + gboolean verbose); + +MsgInfo *rssyl_feed_parse_item_to_msginfo(gchar *file, MsgFlags flags, + gboolean a, gboolean b, FolderItem *item); + +void rssyl_feed_start_refresh_timeout(RFolderItem *ritem); + +#endif /* __RSSYL_FEED_H */ diff --git a/src/plugins/rssyl/rssyl_feed_props.c b/src/plugins/rssyl/rssyl_feed_props.c new file mode 100644 index 000000000..f6a2d897c --- /dev/null +++ b/src/plugins/rssyl/rssyl_feed_props.c @@ -0,0 +1,566 @@ +/* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * - Plugin preferences + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include +#include + +/* Claws Mail includes */ +#include + +/* Local includes */ +#include "rssyl.h" +#include "rssyl_feed.h" +#include "rssyl_feed_props.h" +#include "rssyl_prefs.h" +#include "rssyl_update_feed.h" + +static void rssyl_gtk_prop_store(RFolderItem *ritem) +{ + gchar *url; + gint x, old_ri, old_fetch_comments; + gboolean use_default_ri = FALSE, keep_old = FALSE; + FolderItem *item; + + g_return_if_fail(ritem != NULL); + g_return_if_fail(ritem->feedprop != NULL); + + url = (gchar *)gtk_entry_get_text(GTK_ENTRY(ritem->feedprop->url)); + + if( strlen(url) ) { + if( ritem->url ) { + g_free(ritem->url); + } + ritem->url = g_strdup(url); + } + + use_default_ri = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ritem->feedprop->default_refresh_interval)); + ritem->default_refresh_interval = use_default_ri; + debug_print("store: default refresh interval is %s\n", + ( use_default_ri ? "ON" : "OFF" ) ); + + /* Use default if checkbutton is set */ + if( use_default_ri ) + x = rssyl_prefs_get()->refresh; + else + x = gtk_spin_button_get_value_as_int( + GTK_SPIN_BUTTON(ritem->feedprop->refresh_interval) ); + + old_ri = ritem->refresh_interval; + ritem->refresh_interval = x; + + /* Set timer for next automatic refresh, if needed. */ + if( x > 0 ) { + if( old_ri != x || ritem->refresh_id == 0 ) { + debug_print("RSSyl: GTK - refresh interval changed to %d , updating " + "timeout\n", ritem->refresh_interval); + rssyl_feed_start_refresh_timeout(ritem); + } + } else + ritem->refresh_id = 0; + + old_fetch_comments = ritem->fetch_comments; + ritem->fetch_comments = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ritem->feedprop->fetch_comments)); + + if (!old_fetch_comments && ritem->fetch_comments) { + /* reset the RFolderItem's mtime to be sure we get all + * available comments */ + ritem->item.mtime = 0; + } + + ritem->fetch_comments_max_age = gtk_spin_button_get_value_as_int( + GTK_SPIN_BUTTON(ritem->feedprop->fetch_comments_max_age)); + + keep_old = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ritem->feedprop->keep_old)); + ritem->keep_old = keep_old; + + ritem->silent_update = + gtk_combo_box_get_active(GTK_COMBO_BOX(ritem->feedprop->silent_update)); + + ritem->write_heading = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ritem->feedprop->write_heading)); + + ritem->ignore_title_rename = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ritem->feedprop->ignore_title_rename)); + + ritem->ssl_verify_peer = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(ritem->feedprop->ssl_verify_peer)); + + /* Store updated properties */ + item = &ritem->item; + item->folder->klass->item_get_xml(item->folder, item); +} + +static gboolean +rssyl_feedprop_togglebutton_toggled_cb(GtkToggleButton *tb, + gpointer data) +{ + gboolean active = gtk_toggle_button_get_active(tb); + RFeedProp *feedprop = (RFeedProp *)data; + GtkWidget *sb = NULL; + + if( (GtkWidget *)tb == feedprop->default_refresh_interval ) { + active = !active; + sb = feedprop->refresh_interval; + } else if( (GtkWidget *)tb == feedprop->fetch_comments ) { + sb = feedprop->fetch_comments_max_age; + } + + g_return_val_if_fail(sb != NULL, FALSE); + + gtk_widget_set_sensitive(sb, active); + + return FALSE; +} + + +static gboolean +rssyl_props_cancel_cb(GtkWidget *widget, gpointer data) +{ + RFolderItem *ritem = (RFolderItem *)data; + + debug_print("RSSyl: Cancel pressed\n"); + + gtk_widget_destroy(ritem->feedprop->window); + + return FALSE; +} + +static gboolean +rssyl_props_ok_cb(GtkWidget *widget, gpointer data) +{ + RFolderItem *ritem = (RFolderItem *)data; + + debug_print("RSSyl: OK pressed\n"); + rssyl_gtk_prop_store(ritem); + + gtk_widget_destroy(ritem->feedprop->window); + + return FALSE; +} + +static gboolean +rssyl_props_trim_cb(GtkWidget *widget, gpointer data) +{ + RFolderItem *ritem = (RFolderItem *)data; + gboolean k = ritem->keep_old; + + if( k ) + ritem->keep_old = FALSE; + + rssyl_update_feed(ritem, FALSE); + + if( k ) + ritem->keep_old = TRUE; + + return FALSE; +} + +static gboolean +rssyl_props_key_press_cb(GtkWidget *widget, GdkEventKey *event, + gpointer data) +{ + if (event) { + switch (event->keyval) { + case GDK_Escape: + rssyl_props_cancel_cb(widget, data); + return TRUE; + case GDK_Return: + case GDK_KP_Enter: + rssyl_props_ok_cb(widget, data); + return TRUE; + default: + break; + } + } + + return FALSE; +} + + +void rssyl_gtk_prop(RFolderItem *ritem) +{ + MainWindow *mainwin = mainwindow_get_mainwindow(); + RFeedProp *feedprop; + GtkWidget *vbox, *urllabel, *urlframe, *urlalign, *table, *label, + *hsep, *sep, *bbox, *cancel_button, *cancel_align, + *cancel_hbox, *cancel_image, *cancel_label, *ok_button, *ok_align, + *ok_hbox, *ok_image, *ok_label, *trim_button, *silent_update_label; + GtkObject *adj; +#if !(GTK_CHECK_VERSION(2, 12, 0)) + GtkTooltips *tooltips; +#endif + gint refresh; + gint row = 0; + + g_return_if_fail(ritem != NULL); + + feedprop = g_new0(RFeedProp, 1); + + /**** Create required widgets ****/ + + /* Window */ + feedprop->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + +#if !(GTK_CHECK_VERSION(2, 12, 0)) + tooltips = gtk_tooltips_new(); + gtk_tooltips_enable(tooltips); +#endif + + /* URL entry */ + feedprop->url = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(feedprop->url), ritem->url); + + /* "Use default refresh interval" checkbutton */ + feedprop->default_refresh_interval = gtk_check_button_new_with_mnemonic( + _("Use default refresh interval")); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(feedprop->default_refresh_interval), + ritem->default_refresh_interval); + + if( ritem->refresh_interval >= 0 && !ritem->default_refresh_interval ) + refresh = ritem->refresh_interval; + else + refresh = rssyl_prefs_get()->refresh; + + /* "Keep old items" checkbutton */ + feedprop->keep_old = gtk_check_button_new_with_mnemonic( + _("Keep old items")); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(feedprop->keep_old), + ritem->keep_old); + + /* "Trim" button */ + trim_button = gtk_button_new_with_mnemonic(_("_Trim")); +#if !(GTK_CHECK_VERSION(2, 12, 0)) + gtk_tooltips_set_tip(tooltips, trim_button, + _("Update feed, deleting items which are no longer in the source feed"), NULL); +#else + gtk_widget_set_tooltip_text(trim_button, + _("Update feed, deleting items which are no longer in the source feed")); +#endif + + feedprop->fetch_comments = gtk_check_button_new_with_mnemonic( + _("Fetch comments if possible")); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(feedprop->fetch_comments), + ritem->fetch_comments); + + /* Fetch comments max age spinbutton */ + adj = gtk_adjustment_new(ritem->fetch_comments_max_age, + -1, 100000, 1, 10, 0); + feedprop->fetch_comments_max_age = gtk_spin_button_new(GTK_ADJUSTMENT(adj), + 1, 0); + + /* Refresh interval spinbutton */ + adj = gtk_adjustment_new(refresh, + 0, 100000, 10, 100, 0); + feedprop->refresh_interval = gtk_spin_button_new(GTK_ADJUSTMENT(adj), + 1, 0); + + /* Silent update - combobox */ + feedprop->silent_update = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(feedprop->silent_update), + _("Always mark as new")); + gtk_combo_box_append_text(GTK_COMBO_BOX(feedprop->silent_update), + _("If only its text changed")); + gtk_combo_box_append_text(GTK_COMBO_BOX(feedprop->silent_update), + _("Never mark as new")); + gtk_combo_box_set_active(GTK_COMBO_BOX(feedprop->silent_update), + ritem->silent_update); + + feedprop->write_heading = gtk_check_button_new_with_mnemonic( + _("Add item title to top of message")); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(feedprop->write_heading), + ritem->write_heading); + + /* Ignore title rename - checkbox */ + feedprop->ignore_title_rename = gtk_check_button_new_with_mnemonic( + _("Ignore title rename")); + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(feedprop->ignore_title_rename), + ritem->ignore_title_rename); +#if !(GTK_CHECK_VERSION(2, 12, 0)) + gtk_tooltips_set_tip(tooltips, feedprop->ignore_title_rename, + _("Enable this to keep current folder name, even if feed author changes title of the feed."), NULL); +#else + gtk_widget_set_tooltip_text(feedprop->ignore_title_rename, + _("Enable this to keep current folder name, even if feed author changes title of the feed.")); +#endif + + /* Verify SSL peer certificate */ + feedprop->ssl_verify_peer = gtk_check_button_new_with_label( + _("Verify SSL certificate validity")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(feedprop->ssl_verify_peer), + ritem->ssl_verify_peer); + + /* === Now pack all the widgets */ + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(feedprop->window), vbox); + + /* URL frame */ + urlframe = gtk_frame_new(NULL); + gtk_container_set_border_width(GTK_CONTAINER(urlframe), 5); + gtk_frame_set_label_align(GTK_FRAME(urlframe), 0.05, 0.5); + gtk_frame_set_shadow_type(GTK_FRAME(urlframe), GTK_SHADOW_ETCHED_OUT); + gtk_box_pack_start(GTK_BOX(vbox), urlframe, FALSE, FALSE, 0); + + /* Label for URL frame */ + urllabel = gtk_label_new(_("Source URL:")); + gtk_label_set_use_markup(GTK_LABEL(urllabel), TRUE); + gtk_misc_set_padding(GTK_MISC(urllabel), 5, 0); + gtk_frame_set_label_widget(GTK_FRAME(urlframe), urllabel); + + /* URL entry (+ alignment in frame) */ + urlalign = gtk_alignment_new(0, 0.5, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(urlalign), 5, 5, 5, 5); + gtk_container_add(GTK_CONTAINER(urlframe), urlalign); + + gtk_entry_set_activates_default(GTK_ENTRY(feedprop->url), TRUE); + gtk_container_add(GTK_CONTAINER(urlalign), feedprop->url); + + /* Table for remaining properties */ + table = gtk_table_new(11, 2, FALSE); + gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0); + + /* Fetch comments - checkbutton */ + gtk_table_attach(GTK_TABLE(table), feedprop->fetch_comments, + 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 0); + g_signal_connect(G_OBJECT(feedprop->fetch_comments), "toggled", + G_CALLBACK(rssyl_feedprop_togglebutton_toggled_cb), + (gpointer)feedprop); + + row++; + /* Fetch comments max age - label */ + label = gtk_label_new(_("Fetch comments on posts aged less than:\n" + "(In days; set to -1 to fetch all comments)")); + gtk_label_set_use_markup(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + /* Fetch comments max age - spinbutton */ + gtk_widget_set_sensitive(feedprop->fetch_comments_max_age, + ritem->fetch_comments); + gtk_table_attach(GTK_TABLE(table), feedprop->fetch_comments_max_age, + 1, 2, row, row+1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 10, 5); + + row++; + /* Separator below comments max age */ + hsep = gtk_hseparator_new(); + gtk_widget_set_size_request(hsep, -1, 10); + gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + row++; + /* Keep old items - checkbutton */ + gtk_table_attach(GTK_TABLE(table), feedprop->keep_old, + 0, 1, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 0); + + /* 'Trim' - button */ + gtk_table_attach(GTK_TABLE(table), trim_button, + 1, 2, row, row+1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 10, 0); + g_signal_connect(G_OBJECT(trim_button), "clicked", + G_CALLBACK(rssyl_props_trim_cb), ritem); + + row++; + hsep = gtk_hseparator_new(); + gtk_widget_set_size_request(hsep, -1, 10); + gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + row++; + /* Use default refresh interval - checkbutton */ + gtk_table_attach(GTK_TABLE(table), feedprop->default_refresh_interval, + 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 0); + g_signal_connect(G_OBJECT(feedprop->default_refresh_interval), "toggled", + G_CALLBACK(rssyl_feedprop_togglebutton_toggled_cb), + (gpointer)feedprop); + + row++; + /* Refresh interval - label */ + label = gtk_label_new(_("Refresh interval in minutes:\n" + "(Set to 0 to disable automatic refreshing for this feed)" + "")); + gtk_label_set_use_markup(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + /* Refresh interval - spinbutton */ + gtk_widget_set_sensitive(feedprop->refresh_interval, + !ritem->default_refresh_interval); + gtk_table_attach(GTK_TABLE(table), feedprop->refresh_interval, 1, 2, row, row+1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 10, 5); + + row++; + hsep = gtk_hseparator_new(); + gtk_widget_set_size_request(hsep, -1, 10); + gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + row++; + /* Silent update - label */ + silent_update_label = + gtk_label_new(_("If an item changes, do not mark it as new:")); + gtk_label_set_use_markup(GTK_LABEL(silent_update_label), TRUE); + gtk_misc_set_alignment(GTK_MISC(silent_update_label), 0, 0.5); + gtk_table_attach(GTK_TABLE(table), silent_update_label, 0, 1, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + gtk_table_attach(GTK_TABLE(table), feedprop->silent_update, 1, 2, row, row+1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 10, 5); + + row++; + hsep = gtk_hseparator_new(); + gtk_widget_set_size_request(hsep, -1, 10); + gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 5); + + row++; + + /* Write heading - checkbox */ + gtk_table_attach(GTK_TABLE(table), feedprop->write_heading, + 0, 2, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 0); + + + row++; + + /* Ignore title rename - checkbutton */ + gtk_table_attach(GTK_TABLE(table), feedprop->ignore_title_rename, + 0, 1, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 0); + + row++; + + /* Verify SSL peer certificate - checkbutton */ + gtk_table_attach(GTK_TABLE(table), feedprop->ssl_verify_peer, + 0, 1, row, row+1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 10, 0); + + row++; + + /* Separator above the button box */ + sep = gtk_hseparator_new(); + gtk_widget_set_size_request(sep, -1, 10); + gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0); + + /* Buttonbox */ + bbox = gtk_hbutton_box_new(); + gtk_container_set_border_width(GTK_CONTAINER(bbox), 5); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing(GTK_BOX(bbox), 5); + gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + + /* Cancel button */ + cancel_button = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(bbox), cancel_button); + + cancel_align = gtk_alignment_new(0.5, 0.5, 0, 0); + gtk_container_add(GTK_CONTAINER(cancel_button), cancel_align); + + cancel_hbox = gtk_hbox_new(FALSE, 2); + gtk_container_add(GTK_CONTAINER(cancel_align), cancel_hbox); + + cancel_image = gtk_image_new_from_stock(GTK_STOCK_CANCEL, + GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start(GTK_BOX(cancel_hbox), cancel_image, FALSE, FALSE, 0); + + cancel_label = gtk_label_new_with_mnemonic(_("_Cancel")); + gtk_box_pack_end(GTK_BOX(cancel_hbox), cancel_label, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(cancel_button), "clicked", + G_CALLBACK(rssyl_props_cancel_cb), ritem); + + /* OK button */ + ok_button = gtk_button_new(); + gtk_container_add(GTK_CONTAINER(bbox), ok_button); + GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT ); + + ok_align = gtk_alignment_new(0.5, 0.5, 0, 0); + gtk_container_add(GTK_CONTAINER(ok_button), ok_align); + + ok_hbox = gtk_hbox_new(FALSE, 2); + gtk_container_add(GTK_CONTAINER(ok_align), ok_hbox); + + ok_image = gtk_image_new_from_stock(GTK_STOCK_OK, + GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start(GTK_BOX(ok_hbox), ok_image, FALSE, FALSE, 0); + + ok_label = gtk_label_new_with_mnemonic(_("_OK")); + gtk_box_pack_end(GTK_BOX(ok_hbox), ok_label, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(ok_button), "clicked", + G_CALLBACK(rssyl_props_ok_cb), ritem); + + /* Set some misc. stuff */ + gtk_window_set_title(GTK_WINDOW(feedprop->window), + g_strdup(_("Set feed properties")) ); + gtk_window_set_modal(GTK_WINDOW(feedprop->window), TRUE); + gtk_window_set_transient_for(GTK_WINDOW(feedprop->window), + GTK_WINDOW(mainwin->window) ); + + /* Attach callbacks to handle Enter and Escape keys */ + g_signal_connect(G_OBJECT(feedprop->window), "key_press_event", + G_CALLBACK(rssyl_props_key_press_cb), ritem); + + /* ...and voila! */ + gtk_widget_show_all(feedprop->window); + gtk_widget_grab_default(ok_button); + + /* Unselect the text in URL entry */ + gtk_editable_select_region(GTK_EDITABLE(feedprop->url), 0, 0); + + ritem->feedprop = feedprop; +} diff --git a/src/plugins/rssyl/rssyl_feed_props.h b/src/plugins/rssyl/rssyl_feed_props.h new file mode 100644 index 000000000..5aa453946 --- /dev/null +++ b/src/plugins/rssyl/rssyl_feed_props.h @@ -0,0 +1,24 @@ +#ifndef __RSSYL_FEED_PROPS +#define __RSSYL_FEED_PROPS + +#include "rssyl.h" + +void rssyl_gtk_prop(RFolderItem *ritem); + +struct _RFeedProp { + GtkWidget *window; + GtkWidget *url; + GtkWidget *default_refresh_interval; + GtkWidget *refresh_interval; + GtkWidget *keep_old; + GtkWidget *fetch_comments; + GtkWidget *fetch_comments_max_age; + GtkWidget *silent_update; + GtkWidget *write_heading; + GtkWidget *ignore_title_rename; + GtkWidget *ssl_verify_peer; +}; + +typedef struct _RFeedProp RFeedProp; + +#endif /* __RSSYL_FEED_PROPS */ diff --git a/src/plugins/rssyl/rssyl_gtk.c b/src/plugins/rssyl/rssyl_gtk.c index 24c2abe43..dc6e72a0a 100644 --- a/src/plugins/rssyl/rssyl_gtk.c +++ b/src/plugins/rssyl/rssyl_gtk.c @@ -1,5 +1,5 @@ /* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 1999-2004 Hiroyuki Yamamoto * This file (C) 2005 Andrej Kacian * @@ -22,101 +22,85 @@ #ifdef HAVE_CONFIG_H # include "config.h" -#include "claws-features.h" #endif -#include -#include - +/* Global includes */ #include +#include -#include "gtk/menu.h" -#include "mainwindow.h" -#include "inputdialog.h" -#include "folderview.h" -#include "alertpanel.h" -#include "summaryview.h" - -#include "main.h" -#include "gtkutils.h" +/* Claws Mail includes */ +#include +#include +#include +#include +#include +#include +#include -#include "feed.h" -#include "feedprops.h" +/* Local includes */ #include "rssyl.h" -#include "rssyl_cb_gtk.h" -#include "rssyl_cb_menu.h" -#include "rssyl_gtk.h" #include "rssyl_prefs.h" +#include "rssyl_gtk.h" +#include "rssyl_cb_menu.h" static char *rssyl_popup_menu_labels[] = { N_("_Refresh feed"), - N_("Refresh _all feeds"), + N_("Feed pr_operties"), + N_("Rena_me..."), + N_("R_efresh recursively"), N_("Subscribe _new feed..."), - N_("_Unsubscribe feed..."), - N_("Feed pr_operties..."), + N_("Create new _folder..."), N_("Import feed list..."), - N_("Rena_me..."), - N_("_Create new folder..."), N_("_Delete folder..."), - N_("Remove folder _tree..."), + N_("Remove tree"), NULL }; static GtkActionEntry rssyl_popup_entries[] = { - {"FolderViewPopup/RefreshFeed", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_refresh_cb) }, - {"FolderViewPopup/RefreshAllFeeds", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_refresh_all_cb) }, - - {"FolderViewPopup/NewFeed", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_new_feed_cb) }, - {"FolderViewPopup/RemoveFeed", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_remove_feed_cb) }, - {"FolderViewPopup/FeedProperties", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_prop_cb) }, - {"FolderViewPopup/ImportFeedlist", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_import_feed_list_cb) }, - - {"FolderViewPopup/RenameFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_rename_cb) }, - - {"FolderViewPopup/NewFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_new_folder_cb) }, - {"FolderViewPopup/RemoveFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_remove_folder_cb) }, - - {"FolderViewPopup/RemoveMailbox", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_remove_rss_cb) }, - + {"FolderViewPopup/RefreshFeed", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_refresh_feed_cb) }, + {"FolderViewPopup/FeedProperties", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_prop_cb) }, + {"FolderViewPopup/RenameFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_rename_cb) }, + {"FolderViewPopup/RefreshAllFeeds", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_update_all_cb) }, + {"FolderViewPopup/NewFeed", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_new_feed_cb) }, + {"FolderViewPopup/NewFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_new_folder_cb) }, + {"FolderViewPopup/ImportFeedList", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_import_feed_list_cb) }, + {"FolderViewPopup/RemoveFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_remove_folder_cb) }, + {"FolderViewPopup/RemoveMailbox", NULL, NULL, NULL, NULL, G_CALLBACK(rssyl_remove_mailbox_cb) } }; static void rssyl_add_menuitems(GtkUIManager *ui_manager, FolderItem *item) { MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RefreshFeed", "FolderViewPopup/RefreshFeed", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RefreshAllFeeds", "FolderViewPopup/RefreshAllFeeds", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS1", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "NewFeed", "FolderViewPopup/NewFeed", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RemoveFeed", "FolderViewPopup/RemoveFeed", GTK_UI_MANAGER_MENUITEM) MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "FeedProperties", "FolderViewPopup/FeedProperties", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "ImportFeedlist", "FolderViewPopup/ImportFeedlist", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS2", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RenameFolder", "FolderViewPopup/RenameFolder", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS3", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS1", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RefreshAllFeeds", "FolderViewPopup/RefreshAllFeeds", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS2", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "NewFeed", "FolderViewPopup/NewFeed", GTK_UI_MANAGER_MENUITEM) MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "NewFolder", "FolderViewPopup/NewFolder", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "ImportFeedList", "FolderViewPopup/ImportFeedList", GTK_UI_MANAGER_MENUITEM) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS3", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RemoveFolder", "FolderViewPopup/RemoveFolder", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS4", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS4", "FolderView/Popup/---", GTK_UI_MANAGER_SEPARATOR) MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RemoveMailbox", "FolderViewPopup/RemoveMailbox", GTK_UI_MANAGER_MENUITEM) - MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS5", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR) + MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS5", "FolderView/Popup/---", GTK_UI_MANAGER_SEPARATOR) } static void rssyl_set_sensitivity(GtkUIManager *ui_manager, FolderItem *item) { #define SET_SENS(name, sens) \ cm_menu_set_sensitive_full(ui_manager, "Popup/"name, sens) - - RSSylFolderItem *ritem = (RSSylFolderItem *)item; - SET_SENS("FolderViewPopup/RefreshFeed", folder_item_parent(item) != NULL && ritem->url); - SET_SENS("FolderViewPopup/RefreshAllFeeds", folder_item_parent(item) == NULL ); - SET_SENS("FolderViewPopup/NewFeed", TRUE); - SET_SENS("FolderViewPopup/ImportFeedlist", TRUE ); - SET_SENS("FolderViewPopup/RemoveFeed", folder_item_parent(item) != NULL && ritem->url ); + RFolderItem *ritem = (RFolderItem *)item; + SET_SENS("FolderViewPopup/RefreshFeed", folder_item_parent(item) != NULL && ritem->url ); SET_SENS("FolderViewPopup/FeedProperties", folder_item_parent(item) != NULL && ritem->url ); SET_SENS("FolderViewPopup/RenameFolder", folder_item_parent(item) != NULL ); + SET_SENS("FolderViewPopup/RefreshAllFeeds", TRUE ); + SET_SENS("FolderViewPopup/NewFeed", TRUE); SET_SENS("FolderViewPopup/NewFolder", TRUE ); - SET_SENS("FolderViewPopup/RemoveFolder", folder_item_parent(item) != NULL && !ritem->url ); - SET_SENS("FolderViewPopup/RemoveMailbox", folder_item_parent(item) == NULL ); + SET_SENS("FolderViewPopup/RemoveFolder", folder_item_parent(item) != NULL); + SET_SENS("FolderViewPopup/RemoveMailbox", folder_item_parent(item) == NULL); #undef SET_SENS } @@ -133,20 +117,10 @@ static FolderViewPopup rssyl_popup = rssyl_set_sensitivity }; -static void rssyl_fill_popup_menu_labels(void) -{ - gint i; - - for( i = 0; rssyl_popup_menu_labels[i] != NULL; i++ ) { - (rssyl_popup_entries[i]).label = _(rssyl_popup_menu_labels[i]); - } -} - static void rssyl_add_mailbox(GtkAction *action, gpointer callback_data) { MainWindow *mainwin = (MainWindow *) callback_data; - gchar *path; - gchar *base = NULL; + gchar *path = NULL, *tmp = NULL; Folder *folder; path = input_dialog(_("Add RSS folder tree"), @@ -160,10 +134,10 @@ static void rssyl_add_mailbox(GtkAction *action, gpointer callback_data) return; } - base = g_path_get_basename(path); - folder = folder_new(folder_get_class_from_string("rssyl"), - base, path); - g_free(base); + tmp = g_path_get_basename(path); + folder = folder_new(folder_get_class_from_string("rssyl"), tmp, path); + g_free(tmp); + g_free(path); if( folder->klass->create_tree(folder) < 0 ) { alertpanel_error(_("Creation of folder tree failed.\n" @@ -179,24 +153,30 @@ static void rssyl_add_mailbox(GtkAction *action, gpointer callback_data) folderview_set(mainwin->folderview); } + static GtkActionEntry mainwindow_add_mailbox[] = {{ "File/AddMailbox/RSSyl", - NULL, N_("RSSyl..."), NULL, NULL, G_CALLBACK(rssyl_add_mailbox) + NULL, "RSSyl...", NULL, NULL, G_CALLBACK(rssyl_add_mailbox) }}; +static void rssyl_fill_popup_menu_labels(void) { + gint i; + + for (i = 0; rssyl_popup_menu_labels[i] != NULL; i++) { + (rssyl_popup_entries[i].label = _(rssyl_popup_menu_labels[i])); + } +} + static guint main_menu_id = 0; void rssyl_gtk_init(void) { MainWindow *mainwin = mainwindow_get_mainwindow(); - gtk_action_group_add_actions(mainwin->action_group, mainwindow_add_mailbox, 1, (gpointer)mainwin); MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/File/AddMailbox", "RSSyl", "File/AddMailbox/RSSyl", GTK_UI_MANAGER_MENUITEM, - main_menu_id) - - + main_menu_id); rssyl_fill_popup_menu_labels(); folderview_register_popup(&rssyl_popup); } @@ -222,482 +202,5 @@ void rssyl_gtk_done(void) MENUITEM_REMUI_MANAGER(mainwin->ui_manager,mainwin->action_group, "File/AddMailbox/RSSyl", main_menu_id); main_menu_id = 0; -} - -/***********************************************/ - -static RSSylFeedProp *rssyl_gtk_prop_real(RSSylFolderItem *ritem) -{ - MainWindow *mainwin = mainwindow_get_mainwindow(); - RSSylFeedProp *feedprop; - GtkWidget *vbox, *urllabel, *urlframe, *urlalign, *table, *refresh_label, - *expired_label, *hsep, *sep, *bbox, *cancel_button, *cancel_align, - *cancel_hbox, *cancel_image, *cancel_label, *ok_button, *ok_align, - *ok_hbox, *ok_image, *ok_label, *silent_update_label; - - GtkObject *refresh_adj, *expired_adj, *fetch_comments_for_adj; - gint refresh; - gint row = 0; - - g_return_val_if_fail(ritem != NULL, NULL); - - feedprop = g_new0(RSSylFeedProp, 1); - - /* Create required widgets */ - - /* Window */ - feedprop->window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "rssyl_gtk"); - - /* URL entry */ - feedprop->url = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(feedprop->url), ritem->url); - - /* "Use default refresh interval" checkbutton */ - feedprop->default_refresh_interval = gtk_check_button_new_with_mnemonic( - _("Use default refresh interval")); - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(feedprop->default_refresh_interval), - ritem->default_refresh_interval); - - if( ritem->refresh_interval >= 0 && !ritem->default_refresh_interval ) - refresh = ritem->refresh_interval; - else - refresh = rssyl_prefs_get()->refresh; - - /* "Keep default number of expired items" checkbutton */ - feedprop->default_expired_num = gtk_check_button_new_with_mnemonic( - _("Keep default number of expired entries")); - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(feedprop->default_expired_num), - ritem->default_expired_num); - - feedprop->fetch_comments = gtk_check_button_new_with_mnemonic( - _("Fetch comments if possible")); - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(feedprop->fetch_comments), - ritem->fetch_comments); - - /* Refresh interval spinbutton */ - fetch_comments_for_adj = gtk_adjustment_new(ritem->fetch_comments_for, - -1, 100000, 1, 10, 0); - feedprop->fetch_comments_for = gtk_spin_button_new(GTK_ADJUSTMENT(fetch_comments_for_adj), - 1, 0); - - /* Refresh interval spinbutton */ - refresh_adj = gtk_adjustment_new(refresh, - 0, 100000, 1, 10, 0); - feedprop->refresh_interval = gtk_spin_button_new(GTK_ADJUSTMENT(refresh_adj), - 1, 0); - - /* Expired num spinbutton */ - expired_adj = gtk_adjustment_new(ritem->expired_num, -1, 100000, 1, 10, 0); - feedprop->expired_num = gtk_spin_button_new(GTK_ADJUSTMENT(expired_adj), - 1, 0); - - vbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(feedprop->window), vbox); - - /* URL frame */ - urlframe = gtk_frame_new(NULL); - gtk_container_set_border_width(GTK_CONTAINER(urlframe), 5); - gtk_frame_set_label_align(GTK_FRAME(urlframe), 0.05, 0.5); - gtk_frame_set_shadow_type(GTK_FRAME(urlframe), GTK_SHADOW_ETCHED_OUT); - gtk_box_pack_start(GTK_BOX(vbox), urlframe, FALSE, FALSE, 0); - - /* Label for URL frame */ - urllabel = gtk_label_new(_("Source URL:")); - gtk_label_set_use_markup(GTK_LABEL(urllabel), TRUE); - gtk_misc_set_padding(GTK_MISC(urllabel), 5, 0); - gtk_frame_set_label_widget(GTK_FRAME(urlframe), urllabel); - - /* URL entry (+ alignment in frame) */ - urlalign = gtk_alignment_new(0, 0.5, 1, 1); - gtk_alignment_set_padding(GTK_ALIGNMENT(urlalign), 5, 5, 5, 5); - gtk_container_add(GTK_CONTAINER(urlframe), urlalign); - - gtk_entry_set_activates_default(GTK_ENTRY(feedprop->url), TRUE); - gtk_container_add(GTK_CONTAINER(urlalign), feedprop->url); - - /* Table for remaining properties */ - table = gtk_table_new(8, 2, FALSE); - gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0); - - /* Fetch comments - checkbutton */ - gtk_table_attach(GTK_TABLE(table), feedprop->fetch_comments, - 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 0); - g_signal_connect(G_OBJECT(feedprop->fetch_comments), "toggled", - G_CALLBACK(rssyl_fetch_comments_toggled_cb), - (gpointer)feedprop->fetch_comments_for); - row++; - - /* Fetch comments for - label */ - refresh_label = gtk_label_new(_("Fetch comments on posts aged less than:\n" - "(In days; set to -1 to fetch all comments)" - "")); - gtk_label_set_use_markup(GTK_LABEL(refresh_label), TRUE); - gtk_misc_set_alignment(GTK_MISC(refresh_label), 0, 0.5); - gtk_table_attach(GTK_TABLE(table), refresh_label, 0, 1, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - /* Fetch comments for - spinbutton */ - gtk_widget_set_sensitive(feedprop->fetch_comments_for, - ritem->fetch_comments); - gtk_table_attach(GTK_TABLE(table), feedprop->fetch_comments_for, 1, 2, row, row+1, - (GtkAttachOptions) (0), - (GtkAttachOptions) (0), 10, 5); - - row++; - hsep = gtk_hseparator_new(); - gtk_widget_set_size_request(hsep, -1, 10); - gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - row++; - /* Use default refresh interval - checkbutton */ - gtk_table_attach(GTK_TABLE(table), feedprop->default_refresh_interval, - 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 0); - g_signal_connect(G_OBJECT(feedprop->default_refresh_interval), "toggled", - G_CALLBACK(rssyl_default_refresh_interval_toggled_cb), - (gpointer)feedprop->refresh_interval); - row++; - /* Refresh interval - label */ - refresh_label = gtk_label_new(_("Refresh interval in minutes:\n" - "(Set to 0 to disable automatic refreshing for this feed)" - "")); - gtk_label_set_use_markup(GTK_LABEL(refresh_label), TRUE); - gtk_misc_set_alignment(GTK_MISC(refresh_label), 0, 0.5); - gtk_table_attach(GTK_TABLE(table), refresh_label, 0, 1, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - /* Refresh interval - spinbutton */ - gtk_widget_set_sensitive(feedprop->refresh_interval, - !ritem->default_refresh_interval); - gtk_table_attach(GTK_TABLE(table), feedprop->refresh_interval, 1, 2, row, row+1, - (GtkAttachOptions) (0), - (GtkAttachOptions) (0), 10, 5); - row++; - hsep = gtk_hseparator_new(); - gtk_widget_set_size_request(hsep, -1, 10); - gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - row++; - /* Use default number for expired - checkbutton */ - gtk_table_attach(GTK_TABLE(table), feedprop->default_expired_num, 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 0); - g_signal_connect(G_OBJECT(feedprop->default_expired_num), "toggled", - G_CALLBACK(rssyl_default_expired_num_toggled_cb), - (gpointer)feedprop->expired_num); - - row++; - /* Expired items - label */ - expired_label = gtk_label_new(_("Number of expired entries to keep:" - "\n(Set to -1 if you want to keep expired entries)" - "")); - gtk_label_set_use_markup(GTK_LABEL(expired_label), TRUE); - gtk_misc_set_alignment(GTK_MISC(expired_label), 0, 0.5); - gtk_table_attach(GTK_TABLE(table), expired_label, 0, 1, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - /* Expired items - spinbutton */ - gtk_widget_set_sensitive(feedprop->expired_num, - !ritem->default_expired_num); - gtk_table_attach(GTK_TABLE(table), feedprop->expired_num, 1, 2, row, row+1, - (GtkAttachOptions) (0), - (GtkAttachOptions) (0), 10, 5); - - row++; - hsep = gtk_hseparator_new(); - gtk_widget_set_size_request(hsep, -1, 10); - gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - row++; - /* Silent update - label */ - silent_update_label = - gtk_label_new(_("If an item changes, do not mark it as unread:")); - gtk_label_set_use_markup(GTK_LABEL(silent_update_label), TRUE); - gtk_misc_set_alignment(GTK_MISC(silent_update_label), 0, 0.5); - gtk_table_attach(GTK_TABLE(table), silent_update_label, 0, 1, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - /* Silent update - combobox */ - feedprop->silent_update = gtk_combo_box_new_text(); - gtk_combo_box_append_text(GTK_COMBO_BOX(feedprop->silent_update), - _("Always mark as unread")); - gtk_combo_box_append_text(GTK_COMBO_BOX(feedprop->silent_update), - _("If only its text has changed")); - gtk_combo_box_append_text(GTK_COMBO_BOX(feedprop->silent_update), - _("Never mark as unread")); - gtk_table_attach(GTK_TABLE(table), feedprop->silent_update, 1, 2, row, row+1, - (GtkAttachOptions) (0), - (GtkAttachOptions) (0), 10, 5); - gtk_combo_box_set_active(GTK_COMBO_BOX(feedprop->silent_update), - ritem->silent_update); - - row++; - hsep = gtk_hseparator_new(); - gtk_widget_set_size_request(hsep, -1, 10); - gtk_table_attach(GTK_TABLE(table), hsep, 0, 2, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - row++; - feedprop->ssl_verify_peer_checkbtn = gtk_check_button_new_with_label( - _("Verify SSL certificate validity")); - gtk_widget_show(feedprop->ssl_verify_peer_checkbtn); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(feedprop->ssl_verify_peer_checkbtn), - ritem->ssl_verify_peer); - gtk_table_attach(GTK_TABLE(table), feedprop->ssl_verify_peer_checkbtn, 0, 1, row, row+1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (0), 10, 5); - - - /* Separator above the button box */ - sep = gtk_hseparator_new(); - gtk_widget_set_size_request(sep, -1, 10); - gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0); - - /* Buttonbox */ - bbox = gtk_hbutton_box_new(); - gtk_container_set_border_width(GTK_CONTAINER(bbox), 5); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_set_spacing(GTK_BOX(bbox), 5); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); - - /* Cancel button */ - cancel_button = gtk_button_new(); - gtk_container_add(GTK_CONTAINER(bbox), cancel_button); - - cancel_align = gtk_alignment_new(0.5, 0.5, 0, 0); - gtk_container_add(GTK_CONTAINER(cancel_button), cancel_align); - - cancel_hbox = gtk_hbox_new(FALSE, 2); - gtk_container_add(GTK_CONTAINER(cancel_align), cancel_hbox); - - cancel_image = gtk_image_new_from_stock(GTK_STOCK_CANCEL, - GTK_ICON_SIZE_BUTTON); - gtk_box_pack_start(GTK_BOX(cancel_hbox), cancel_image, FALSE, FALSE, 0); - - cancel_label = gtk_label_new_with_mnemonic(_("_Cancel")); - gtk_box_pack_end(GTK_BOX(cancel_hbox), cancel_label, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(cancel_button), "clicked", - G_CALLBACK(rssyl_props_cancel_cb), ritem); - - /* OK button */ - ok_button = gtk_button_new(); - gtk_container_add(GTK_CONTAINER(bbox), ok_button); - gtkut_widget_set_can_default(ok_button, TRUE); - - ok_align = gtk_alignment_new(0.5, 0.5, 0, 0); - gtk_container_add(GTK_CONTAINER(ok_button), ok_align); - - ok_hbox = gtk_hbox_new(FALSE, 2); - gtk_container_add(GTK_CONTAINER(ok_align), ok_hbox); - - ok_image = gtk_image_new_from_stock(GTK_STOCK_OK, - GTK_ICON_SIZE_BUTTON); - gtk_box_pack_start(GTK_BOX(ok_hbox), ok_image, FALSE, FALSE, 0); - - ok_label = gtk_label_new_with_mnemonic(_("_OK")); - gtk_box_pack_end(GTK_BOX(ok_hbox), ok_label, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(ok_button), "clicked", - G_CALLBACK(rssyl_props_ok_cb), ritem); - - /* Set some misc. stuff */ - gtk_window_set_title(GTK_WINDOW(feedprop->window), - g_strdup(_("Set feed properties")) ); - gtk_window_set_modal(GTK_WINDOW(feedprop->window), TRUE); - gtk_window_set_transient_for(GTK_WINDOW(feedprop->window), - GTK_WINDOW(mainwin->window) ); - - /* Attach callbacks to handle Enter and Escape keys */ - g_signal_connect(G_OBJECT(feedprop->window), "key_press_event", - G_CALLBACK(rssyl_props_key_press_cb), ritem); - - /* ...and voila! */ - gtk_widget_show_all(feedprop->window); - gtk_widget_grab_default(ok_button); - - /* Unselect the text in URL entry */ - gtk_editable_select_region(GTK_EDITABLE(feedprop->url), 0, 0); - - ritem->feedprop = feedprop; - - return feedprop; -} - -void rssyl_gtk_prop(RSSylFolderItem *ritem) -{ - g_return_if_fail(ritem != NULL); - - rssyl_gtk_prop_real(ritem); -} - -void rssyl_gtk_prop_store(RSSylFolderItem *ritem) -{ - gchar *url; - gint x, old_ri, old_ex, old_fetch_comments; - gboolean use_default_ri = FALSE, use_default_ex = FALSE; - - g_return_if_fail(ritem != NULL); - g_return_if_fail(ritem->feedprop != NULL); - - url = (gchar *)gtk_entry_get_text(GTK_ENTRY(ritem->feedprop->url)); - - if( strlen(url) ) { - if( ritem->url ) { - g_free(ritem->url); - } - ritem->url = g_strdup(url); - } - - use_default_ri = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(ritem->feedprop->default_refresh_interval)); - ritem->default_refresh_interval = use_default_ri; - debug_print("store: default is %s\n", ( use_default_ri ? "ON" : "OFF" ) ); - - /* Use default if checkbutton is set */ - if( use_default_ri ) - x = rssyl_prefs_get()->refresh; - else - x = gtk_spin_button_get_value_as_int( - GTK_SPIN_BUTTON(ritem->feedprop->refresh_interval) ); - - old_ri = ritem->refresh_interval; - ritem->refresh_interval = x; - - /* Update refresh interval setting if it has changed and has a sane value */ - if( old_ri != x && x >= 0 ) { - debug_print("RSSyl: GTK - refresh interval changed to %d , updating " - "timeout\n", ritem->refresh_interval); - /* Value of 0 means we do not want to update automatically */ - if( x > 0 ) - rssyl_start_refresh_timeout(ritem); - } - - old_fetch_comments = ritem->fetch_comments; - ritem->fetch_comments = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(ritem->feedprop->fetch_comments)); - - ritem->fetch_comments_for = gtk_spin_button_get_value_as_int( - GTK_SPIN_BUTTON(ritem->feedprop->fetch_comments_for)); - - if (!old_fetch_comments && ritem->fetch_comments) { - /* reset the RSSylFolderItem's mtime to be sure we get all - * available comments */ - ritem->item.mtime = 0; - } - - use_default_ex = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(ritem->feedprop->default_expired_num)); - ritem->default_expired_num = use_default_ex; - debug_print("store: default is %s\n", ( use_default_ex ? "ON" : "OFF" ) ); - - /* Use default if checkbutton is set */ - if( use_default_ex ) - x = rssyl_prefs_get()->expired; - else - x = gtk_spin_button_get_value_as_int( - GTK_SPIN_BUTTON(ritem->feedprop->expired_num) ); - - old_ex = ritem->expired_num; - ritem->expired_num = x; - - ritem->silent_update = - gtk_combo_box_get_active(GTK_COMBO_BOX(ritem->feedprop->silent_update)); - if( ritem->silent_update < 0 ) - ritem->silent_update = 0; - - ritem->ssl_verify_peer = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON - (ritem->feedprop->ssl_verify_peer_checkbtn));; - - rssyl_store_feed_props(ritem); - - debug_print("last_count %d, x %d, old_ex %d\n", ritem->last_count, x, old_ex); - - /* update if new setting is lower, or if it was changed from -1 */ - if( ritem->last_count != 0 && x != -1 && (old_ex > x || old_ex == -1) ) { - debug_print("RSSyl: GTK - expired_num has changed to %d, expiring\n", - ritem->expired_num); - rssyl_expire_items(ritem); - } -} - -GtkWidget *rssyl_feed_removal_dialog(gchar *name, GtkWidget **rmcache_widget) -{ - gchar *message; - GtkWidget *dialog, *vbox, *hbox, *image, *vbox2, *label, *cb, *aa, - *bno, *byes; - MainWindow *mainwin = mainwindow_get_mainwindow(); - - g_return_val_if_fail(name != NULL, NULL); - - dialog = gtk_dialog_new(); - gtk_window_set_title(GTK_WINDOW(dialog), _("Unsubscribe feed")); - gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG); - - vbox = GTK_DIALOG(dialog)->vbox; - - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); - - /* Question icon */ - image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, - GTK_ICON_SIZE_DIALOG); - gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.30); - gtk_misc_set_padding(GTK_MISC(image), 12, 0); - gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); - - vbox2 = gtk_vbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0); - - /* Dialog text label */ - label = gtk_label_new(""); - gtk_misc_set_alignment(GTK_MISC(label), 0.1, 0); - gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); - gtk_misc_set_padding(GTK_MISC(label), 0, 12); - message = g_markup_printf_escaped("%s" - "\n\n%s '%s' ?", _("Unsubscribe feed"), - _("Do you really want to remove feed"), name); - gtk_label_set_markup(GTK_LABEL(label), message); - g_free(message); - gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); - - /* Remove cache checkbutton */ - cb = gtk_check_button_new_with_mnemonic(_("Remove cached entries")); - gtk_button_set_focus_on_click(GTK_BUTTON(cb), FALSE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), TRUE); - gtk_box_pack_start(GTK_BOX(vbox2), cb, FALSE, FALSE, 0); - *rmcache_widget = cb; - - aa = GTK_DIALOG(dialog)->action_area; - gtk_button_box_set_layout(GTK_BUTTON_BOX(aa), GTK_BUTTONBOX_END); - - bno = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_dialog_add_action_widget(GTK_DIALOG(dialog), bno, GTK_RESPONSE_NO); - gtkut_widget_set_can_default(bno, TRUE); - - byes = gtk_button_new_with_mnemonic(_("_Unsubscribe")); - gtk_dialog_add_action_widget(GTK_DIALOG(dialog), byes, GTK_RESPONSE_YES); - - gtk_widget_grab_default(bno); - gtk_window_set_transient_for(GTK_WINDOW(dialog), - GTK_WINDOW(mainwin->window) ); - return dialog; } diff --git a/src/plugins/rssyl/rssyl_gtk.h b/src/plugins/rssyl/rssyl_gtk.h index a670a6b36..6a97fc892 100644 --- a/src/plugins/rssyl/rssyl_gtk.h +++ b/src/plugins/rssyl/rssyl_gtk.h @@ -7,27 +7,11 @@ #include "rssyl.h" -struct _RSSylFeedProp { - GtkWidget *window; - GtkWidget *url; - GtkWidget *default_refresh_interval; - GtkWidget *refresh_interval; - GtkWidget *default_expired_num; - GtkWidget *expired_num; - GtkWidget *fetch_comments; - GtkWidget *fetch_comments_for; - GtkWidget *silent_update; - GtkWidget *ssl_verify_peer_checkbtn; -}; - -typedef struct _RSSylFeedProp RSSylFeedProp; - void rssyl_gtk_init(void); void rssyl_gtk_done(void); -void rssyl_gtk_prop(RSSylFolderItem *ritem); -void rssyl_gtk_prop_store(RSSylFolderItem *ritem); - GtkWidget *rssyl_feed_removal_dialog(gchar *name, GtkWidget **rmcache_widget); +void rssyl_gtk_prop(RFolderItem *ritem); + #endif /* __RSSYL_GTK */ diff --git a/src/plugins/rssyl/rssyl_parse_feed.c b/src/plugins/rssyl/rssyl_parse_feed.c new file mode 100644 index 000000000..88dc40612 --- /dev/null +++ b/src/plugins/rssyl/rssyl_parse_feed.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feed.h" +#include "libfeed/feeditem.h" +#include "libfeed/date.h" +#include "parse822.h" +#include "rssyl.h" +#include "rssyl_add_item.h" +#include "rssyl_deleted.h" +#include "rssyl_feed.h" +#include "rssyl_parse_feed.h" +#include "rssyl_prefs.h" +#include "strutils.h" + +static void rssyl_foreach_parse_func(gpointer data, gpointer user_data) +{ + FeedItem *feed_item = (FeedItem *)data; + RFolderItem *ritem = (RFolderItem *)user_data; + + rssyl_add_item(ritem, feed_item); +} + +struct _RSSylExpireItemsCtx { + gboolean exists; + FeedItem *item; +}; + +typedef struct _RSSylExpireItemsCtx RSSylExpireItemsCtx; + +static void expire_items_func(gpointer data, gpointer user_data) +{ + RSSylExpireItemsCtx *ctx = (RSSylExpireItemsCtx *)user_data; + FeedItem *item = (FeedItem *)data; + gchar *id = NULL, *id2 = NULL; + + if( (id = feed_item_get_id(item)) == NULL ) + id = feed_item_get_url(item); + + if( id == NULL ) + return; + + if( (id2 = feed_item_get_id(ctx->item)) == NULL ) + id2 = feed_item_get_url(ctx->item); + + if( id2 == NULL ) + return; + + /* Simply check ID, as we should have up-to-date items right now. */ + if( !strcmp(id, id2) ) + ctx->exists = TRUE; +} + +static void rssyl_expire_items(RFolderItem *ritem, Feed *feed) +{ + FeedItem *item = NULL; + GSList *i = NULL; + RSSylExpireItemsCtx *ctx = NULL; + RFeedCtx *fctx; + + debug_print("RSSyl: rssyl_expire_items()\n"); + + g_return_if_fail(ritem != NULL); + g_return_if_fail(ritem->items != NULL); + g_return_if_fail(feed != NULL); + + ctx = malloc( sizeof(RSSylExpireItemsCtx) ); + + /* Check each locally stored item, if it is still in the upstream + * feed - xnay it if not. */ + for( i = ritem->items; i != NULL; i = i->next ) { + item = (FeedItem *)i->data; + + /* Do not expire comments, they expire with their parents */ + if (feed_item_get_parent_id(item) != NULL) + continue; + + ctx->exists = FALSE; + ctx->item = item; + feed_foreach_item(feed, expire_items_func, ctx); + + if( !ctx->exists ) { + fctx = (RFeedCtx *)item->data; + /* TODO: expire item's comments (items with our parent_id) */ + g_remove(fctx->path); + } + } + + g_free(ctx); +} + +/* ------------------------------------------------------------------------- + * rssyl_parse_feed() */ + +gboolean rssyl_parse_feed(RFolderItem *ritem, Feed *feed) +{ + gchar *tmp = NULL, *tmp2 = NULL; + gint i = 1; + + g_return_val_if_fail(ritem != NULL, FALSE); + g_return_val_if_fail(feed != NULL, FALSE); + g_return_val_if_fail(feed->title != NULL, FALSE); + + debug_print("RSSyl: parse_feed\n"); + + /* Set the last_update timestamp here, so it is the same for all items */ + ritem->last_update = time(NULL); + + /* If the upstream feed changed its title, change name of our folder + * accordingly even if user has renamed it before. This makes sure that + * user will be aware of the upstream title change. */ + if( !ritem->ignore_title_rename && + (ritem->official_title == NULL || + strcmp(feed->title, ritem->official_title)) ) { + g_free(ritem->official_title); + ritem->official_title = g_strdup(feed->title); + + tmp = rssyl_format_string(feed->title, TRUE, TRUE); + + tmp2 = g_strdup(tmp); + while (folder_item_rename(&ritem->item, tmp2) != 0 && i < 20) { + g_free(tmp2); + tmp2 = g_strdup_printf("%s__%d", tmp, ++i); + debug_print("RSSyl: couldn't rename, trying '%s'\n", tmp2); + } + /* TODO: handle case when i reaches 20 */ + + g_free(tmp); + g_free(tmp2); + + /* FIXME: update name in properties */ + /* FIXME: store feed properties */ + } + + folder_item_update_freeze(); + + /* Read contents of folder, so we can check for duplicates/updates */ + rssyl_folder_read_existing(ritem); + + if( claws_is_exiting() ) { + debug_print("RSSyl: Claws-Mail is exiting, bailing out\n"); + log_print(LOG_PROTOCOL, RSSYL_LOG_ABORTED_EXITING, ritem->url); + folder_item_update_thaw(); + return TRUE; + } + + /* Populate the ->deleted_items list so that we can check it when + * adding each item. */ + ritem->deleted_items = rssyl_deleted_update(ritem); + + /* Parse each item in the feed, adding or updating existing items if + * necessary */ + if( feed_n_items(feed) > 0 ) + feed_foreach_item(feed, rssyl_foreach_parse_func, (gpointer)ritem); + + if( !ritem->keep_old && !ritem->fetching_comments ) { + rssyl_folder_read_existing(ritem); + rssyl_expire_items(ritem, feed); + } + + rssyl_deleted_free(ritem->deleted_items); + + folder_item_scan(&ritem->item); + folder_item_update_thaw(); + + if( !ritem->fetching_comments ) + log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATED, ritem->url); + + return TRUE; +} diff --git a/src/plugins/rssyl/rssyl_parse_feed.h b/src/plugins/rssyl/rssyl_parse_feed.h new file mode 100644 index 000000000..5e4a3f32f --- /dev/null +++ b/src/plugins/rssyl/rssyl_parse_feed.h @@ -0,0 +1,16 @@ +#ifndef __RSSYL_PARSE_FEED +#define __RSSYL_PARSE_FEED + +#include + +#include "rssyl.h" +#include "libfeed/feed.h" + +#define RSSYL_TMP_TEMPLATE "rssylXXXXXX" + +#define RSSYL_TEXT_START "" +#define RSSYL_TEXT_END "" + +gboolean rssyl_parse_feed(RFolderItem *ritem, Feed *feed); + +#endif /* __RSSYL_PARSE_FEED */ diff --git a/src/plugins/rssyl/rssyl_prefs.c b/src/plugins/rssyl/rssyl_prefs.c index 97b2b83fc..6b8b05065 100644 --- a/src/plugins/rssyl/rssyl_prefs.c +++ b/src/plugins/rssyl/rssyl_prefs.c @@ -22,39 +22,43 @@ #ifdef HAVE_CONFIG_H # include "config.h" -#include "claws-features.h" #endif +/* Global includes */ #include #include -#include "common/defs.h" -#include "common/utils.h" -#include "gtk/gtkutils.h" -#include "prefs_gtk.h" +/* Claws Mail includes */ +#include +#include +#include +/* Local includes */ +#include "rssyl.h" #include "rssyl_prefs.h" +#include "rssyl_feed.h" -static RSSylPrefsPage rssyl_prefs_page; -RSSylPrefs rssyl_prefs; +static RPrefsPage rssyl_gtk_prefs_page; +RPrefs rssyl_prefs; static void destroy_rssyl_prefs_page(PrefsPage *page); static void create_rssyl_prefs_page(PrefsPage *page, GtkWindow *window, gpointer data); static void save_rssyl_prefs(PrefsPage *page); - -static PrefParam param[] = { - { "refresh_interval", RSSYL_PREF_DEFAULT_REFRESH, &rssyl_prefs.refresh, P_INT, - NULL, NULL, NULL }, - { "expired_keep", RSSYL_PREF_DEFAULT_EXPIRED, &rssyl_prefs.expired, P_INT, - NULL, NULL, NULL }, - { "refresh_on_startup", "FALSE", &rssyl_prefs.refresh_on_startup, P_BOOL, - NULL, NULL, NULL }, - { "cookies_path", "", &rssyl_prefs.cookies_path, P_STRING, - NULL, NULL, NULL }, - { "ssl_verify_peer", "TRUE", &rssyl_prefs.ssl_verify_peer, P_BOOL, - NULL, NULL, NULL }, - { 0, 0, 0, 0, 0, 0, 0 } +static void rssyl_apply_prefs(void); + + static PrefParam param[] = { + { "refresh_interval", PREF_DEFAULT_REFRESH, &rssyl_prefs.refresh, P_INT, + NULL, NULL, NULL }, + { "refresh_on_startup", "FALSE", &rssyl_prefs.refresh_on_startup, P_BOOL, + NULL, NULL, NULL }, + { "refresh_enabled", "TRUE", &rssyl_prefs.refresh_enabled, P_BOOL, + NULL, NULL, NULL }, + { "cookies_path", "", &rssyl_prefs.cookies_path, P_STRING, + NULL, NULL, NULL }, + { "ssl_verify_peer", "TRUE", &rssyl_prefs.ssl_verify_peer, P_BOOL, + NULL, NULL, NULL }, + { 0, 0, 0, 0, 0, 0, 0 } }; void rssyl_prefs_init(void) @@ -71,98 +75,119 @@ void rssyl_prefs_init(void) prefs_read_config(param, PREFS_BLOCK_NAME, rcpath, NULL); g_free(rcpath); - rssyl_prefs_page.page.path = path; - rssyl_prefs_page.page.create_widget = create_rssyl_prefs_page; - rssyl_prefs_page.page.destroy_widget = destroy_rssyl_prefs_page; - rssyl_prefs_page.page.save_page = save_rssyl_prefs; - rssyl_prefs_page.page.weight = 30.0; + rssyl_gtk_prefs_page.page.path = path; + rssyl_gtk_prefs_page.page.create_widget = create_rssyl_prefs_page; + rssyl_gtk_prefs_page.page.destroy_widget = destroy_rssyl_prefs_page; + rssyl_gtk_prefs_page.page.save_page = save_rssyl_prefs; + rssyl_gtk_prefs_page.page.weight = 30.0; - prefs_gtk_register_page((PrefsPage *) &rssyl_prefs_page); + prefs_gtk_register_page((PrefsPage *) &rssyl_gtk_prefs_page); } void rssyl_prefs_done(void) { - prefs_gtk_unregister_page((PrefsPage *) &rssyl_prefs_page); + prefs_gtk_unregister_page((PrefsPage *) &rssyl_gtk_prefs_page); +} + +/* Toggle the refresh timeout spinbutton sensitivity after the + * checkbutton was toggled. */ +static gboolean +rssyl_refresh_enabled_toggled_cb(GtkToggleButton *tb, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(data), + gtk_toggle_button_get_active(tb)); + return FALSE; } static void create_rssyl_prefs_page(PrefsPage *page, GtkWindow *window, gpointer data) { - RSSylPrefsPage *prefs_page = (RSSylPrefsPage *) page; + int row = 0; + RPrefsPage *prefs_page = (RPrefsPage *) page; GtkWidget *table; - GtkWidget *refresh; - GtkWidget *expired; + GtkWidget *refresh, *refresh_enabled; + GtkWidget *label; GtkWidget *refresh_on_startup; + GtkObject *refresh_adj; GtkWidget *cookies_path; - GtkWidget *ssl_verify_peer_checkbtn; - GtkWidget *label; - GtkObject *refresh_adj, *expired_adj; + GtkWidget *ssl_verify_peer; +#if !(GTK_CHECK_VERSION(2, 12, 0)) + GtkTooltips *tooltips; + + tooltips = gtk_tooltips_new(); + gtk_tooltips_enable(tooltips); +#endif - table = gtk_table_new(RSSYL_NUM_PREFS, 2, FALSE); - gtk_container_set_border_width(GTK_CONTAINER(table), 6); + table = gtk_table_new(3, 2, FALSE); + gtk_container_set_border_width(GTK_CONTAINER(table), 5); gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW); gtk_table_set_col_spacings(GTK_TABLE(table), 8); - label = gtk_label_new(_("Default refresh interval in minutes")); - gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); - gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, - GTK_FILL, 0, 0, 0); + /* Refresh interval */ + refresh_enabled = gtk_check_button_new_with_label( + _("Default refresh interval in minutes")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(refresh_enabled), + rssyl_prefs.refresh_enabled); + gtk_table_attach(GTK_TABLE(table), refresh_enabled, 0, 1, row, row+1, + GTK_FILL | GTK_EXPAND, 0, 0, 0); refresh_adj = gtk_adjustment_new(rssyl_prefs.refresh, - 0, 100000, 1, 10, 0); + 1, 100000, 1, 10, 0); refresh = gtk_spin_button_new(GTK_ADJUSTMENT(refresh_adj), 1, 0); - gtk_table_attach(GTK_TABLE(table), refresh, 1, 2, 0, 1, + gtk_widget_set_sensitive(GTK_WIDGET(refresh), rssyl_prefs.refresh_enabled); + gtk_table_attach(GTK_TABLE(table), refresh, 1, 2, row, row+1, GTK_FILL, 0, 0, 0); - CLAWS_SET_TIP(refresh, - _("Set to 0 to disable automatic refreshing")); - label = gtk_label_new(_("Default number of expired items to keep")); - gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); - gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, - GTK_FILL, 0, 0, 0); - - expired_adj = gtk_adjustment_new(rssyl_prefs.expired, - -1, 100000, 1, 10, 0); - expired = gtk_spin_button_new(GTK_ADJUSTMENT(expired_adj), 1, 0); - gtk_table_attach(GTK_TABLE(table), expired, 1, 2, 1, 2, - GTK_FILL, 0, 0, 0); - CLAWS_SET_TIP(expired, - _("Set to -1 to keep expired items")); + g_signal_connect(G_OBJECT(refresh_enabled), "toggled", + G_CALLBACK(rssyl_refresh_enabled_toggled_cb), refresh); + row++; + /* Whether to refresh all feeds on CM startup */ refresh_on_startup = gtk_check_button_new_with_label( _("Refresh all feeds on application start")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(refresh_on_startup), rssyl_prefs.refresh_on_startup); - gtk_table_attach(GTK_TABLE(table), refresh_on_startup, 0, 2, 3, 4, + gtk_table_attach(GTK_TABLE(table), refresh_on_startup, 0, 2, row, row+1, GTK_FILL | GTK_EXPAND, 0, 0, 0); + row++; + /* Path to cookies file for libcurl to use */ label = gtk_label_new(_("Path to cookies file")); - gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, - GTK_FILL, 0, 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1, + GTK_FILL | GTK_EXPAND, 0, 0, 0); gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); - cookies_path = gtk_entry_new (); + cookies_path = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(cookies_path), rssyl_prefs.cookies_path); - gtk_table_attach(GTK_TABLE(table), cookies_path, 1, 2, 4, 5, - GTK_FILL | GTK_EXPAND, 0, 0, 0); - CLAWS_SET_TIP(cookies_path, + gtk_table_attach(GTK_TABLE(table), cookies_path, 1, 2, row, row+1, + GTK_FILL, 0, 0, 0); +#if !(GTK_CHECK_VERSION(2, 12, 0)) + gtk_tooltips_set_tip(tooltips, cookies_path, + _("Path to Netscape-style cookies.txt file containing your cookies"), NULL); +#else + gtk_widget_set_tooltip_text(cookies_path, _("Path to Netscape-style cookies.txt file containing your cookies")); +#endif - ssl_verify_peer_checkbtn = gtk_check_button_new_with_label( - _("Verify SSL certificate validity for new feeds")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ssl_verify_peer_checkbtn), + row++; + + /* Whether to verify SSL peer certificate */ + ssl_verify_peer = gtk_check_button_new_with_label( + _("Verify SSL certificates validity for new feeds")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ssl_verify_peer), rssyl_prefs.ssl_verify_peer); - gtk_table_attach(GTK_TABLE(table), ssl_verify_peer_checkbtn, 0, 2, 5, 6, + gtk_table_attach(GTK_TABLE(table), ssl_verify_peer, 0, 2, row, row+1, GTK_FILL | GTK_EXPAND, 0, 0, 0); gtk_widget_show_all(table); + /* Store pointers to relevant widgets */ prefs_page->page.widget = table; + prefs_page->refresh_enabled = refresh_enabled; prefs_page->refresh = refresh; - prefs_page->expired = expired; prefs_page->refresh_on_startup = refresh_on_startup; prefs_page->cookies_path = cookies_path; - prefs_page->ssl_verify_peer_checkbtn = ssl_verify_peer_checkbtn; + prefs_page->ssl_verify_peer = ssl_verify_peer; } static void destroy_rssyl_prefs_page(PrefsPage *page) @@ -172,23 +197,25 @@ static void destroy_rssyl_prefs_page(PrefsPage *page) static void save_rssyl_prefs(PrefsPage *page) { - RSSylPrefsPage *prefs_page = (RSSylPrefsPage *)page; + RPrefsPage *prefs_page = (RPrefsPage *)page; PrefFile *pref_file; gchar *rc_file_path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL); + /* Grab values from GTK widgets */ + rssyl_prefs.refresh_enabled = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(prefs_page->refresh_enabled)); rssyl_prefs.refresh = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON(prefs_page->refresh)); - rssyl_prefs.expired = gtk_spin_button_get_value_as_int( - GTK_SPIN_BUTTON(prefs_page->expired)); rssyl_prefs.refresh_on_startup = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(prefs_page->refresh_on_startup)); g_free(rssyl_prefs.cookies_path); rssyl_prefs.cookies_path = g_strdup(gtk_entry_get_text( - GTK_ENTRY(prefs_page->cookies_path))); + GTK_ENTRY(prefs_page->cookies_path))); rssyl_prefs.ssl_verify_peer = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(prefs_page->ssl_verify_peer_checkbtn)); + GTK_TOGGLE_BUTTON(prefs_page->ssl_verify_peer)); + /* Store prefs in rc file */ pref_file = prefs_write_open(rc_file_path); g_free(rc_file_path); @@ -201,14 +228,56 @@ static void save_rssyl_prefs(PrefsPage *page) return; } - if (fprintf(pref_file->fp, "\n") < 0) { - FILE_OP_ERROR(rc_file_path, "fprintf"); - prefs_file_close_revert(pref_file); - } else - prefs_file_close(pref_file); + fprintf(pref_file->fp, "\n"); + prefs_file_close(pref_file); + + rssyl_apply_prefs(); } -RSSylPrefs *rssyl_prefs_get(void) +RPrefs *rssyl_prefs_get(void) { return &rssyl_prefs; } + +static void rssyl_start_default_refresh_timeouts_func(FolderItem *item, + gpointer data) +{ + RFolderItem *ritem = (RFolderItem *)item; + guint prefs_interval = GPOINTER_TO_UINT(data); + + if( !IS_RSSYL_FOLDER_ITEM(item) ) + return; + + if( folder_item_parent(item) == NULL || ritem->url == NULL ) + return; + + /* Feeds which use default refresh interval */ + if( ritem->default_refresh_interval ) { + /* Start a new timer if the default value has changed + * (ritem->refresh_interval should contain previous default + * value in this case). */ + if( ritem->refresh_interval != prefs_interval ) { + ritem->refresh_interval = prefs_interval; + rssyl_feed_start_refresh_timeout(ritem); + } + } +} + +static void rssyl_start_default_refresh_timeouts(void) +{ + RPrefs *rsprefs = rssyl_prefs_get(); + + folder_func_to_all_folders( + (FolderItemFunc)rssyl_start_default_refresh_timeouts_func, + GUINT_TO_POINTER(rsprefs->refresh)); +} + +/* rssyl_apply_prefs(): + * Do what's needed to start using newly set preferences */ +static void rssyl_apply_prefs(void) +{ + /* Update refresh timeouts for feeds which use default interval. */ + rssyl_start_default_refresh_timeouts(); + + /* Nothing else here, so far... */ +} diff --git a/src/plugins/rssyl/rssyl_prefs.h b/src/plugins/rssyl/rssyl_prefs.h index 15248cecd..51325f68a 100644 --- a/src/plugins/rssyl/rssyl_prefs.h +++ b/src/plugins/rssyl/rssyl_prefs.h @@ -3,34 +3,31 @@ #define PREFS_BLOCK_NAME "rssyl" -#define RSSYL_NUM_PREFS 4 +#define PREF_DEFAULT_REFRESH "180" -#define RSSYL_PREF_DEFAULT_REFRESH "180" -#define RSSYL_PREF_DEFAULT_EXPIRED "-1" +typedef struct _RPrefs RPrefs; -typedef struct _RSSylPrefs RSSylPrefs; - -struct _RSSylPrefs { +struct _RPrefs { + gboolean refresh_enabled; gint refresh; - gint expired; gboolean refresh_on_startup; gchar *cookies_path; gboolean ssl_verify_peer; }; -typedef struct _RSSylPrefsPage RSSylPrefsPage; +typedef struct _RPrefsPage RPrefsPage; -struct _RSSylPrefsPage { +struct _RPrefsPage { PrefsPage page; + GtkWidget *refresh_enabled; GtkWidget *refresh; - GtkWidget *expired; GtkWidget *refresh_on_startup; GtkWidget *cookies_path; - GtkWidget *ssl_verify_peer_checkbtn; + GtkWidget *ssl_verify_peer; }; void rssyl_prefs_init(void); void rssyl_prefs_done(void); -RSSylPrefs *rssyl_prefs_get(void); +RPrefs *rssyl_prefs_get(void); #endif /* __RSSYL_PREFS */ diff --git a/src/plugins/rssyl/rssyl_subscribe.c b/src/plugins/rssyl/rssyl_subscribe.c new file mode 100644 index 000000000..3bdbc7344 --- /dev/null +++ b/src/plugins/rssyl/rssyl_subscribe.c @@ -0,0 +1,151 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * - DESCRIPTION HERE + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feed.h" +#include "libfeed/feeditem.h" +#include "rssyl.h" +#include "rssyl_add_item.h" +#include "rssyl_feed.h" +#include "rssyl_update_feed.h" +#include "rssyl_subscribe_gtk.h" +#include "strutils.h" + +static void rssyl_subscribe_foreach_func(gpointer data, gpointer user_data) +{ + RFolderItem *ritem = (RFolderItem *)user_data; + FeedItem *feed_item = (FeedItem *)data; + + g_return_if_fail(ritem != NULL); + g_return_if_fail(feed_item != NULL); + + rssyl_add_item(ritem, feed_item); +} + +gboolean rssyl_subscribe(FolderItem *parent, const gchar *url, + gboolean verbose) +{ + gchar *myurl = NULL, *tmpname = NULL, *tmpname2 = NULL; + RFetchCtx *ctx; + FolderItem *new_item; + RFolderItem *ritem; + gint i = 1; + RSubCtx *sctx; + + g_return_val_if_fail(parent != NULL, FALSE); + g_return_val_if_fail(url != NULL, FALSE); + + log_print(LOG_PROTOCOL, RSSYL_LOG_SUBSCRIBING, url); + + myurl = my_normalize_url(url); + + /* Fetch the feed. */ + ctx = rssyl_prep_fetchctx_from_url(myurl); + g_free(myurl); + g_return_val_if_fail(ctx != NULL, FALSE); + + rssyl_fetch_feed(ctx, verbose); + + debug_print("RSSyl: fetch success == %s\n", + ctx->success ? "TRUE" : "FALSE"); + + if (!ctx->success) { + /* User notification was already handled inside rssyl_fetch_feed(), + * let's just return quietly. */ + feed_free(ctx->feed); + g_free(ctx->error); + g_free(ctx); + return FALSE; + } + + if (verbose) { + sctx = g_new0(RSubCtx, 1); + sctx->feed = ctx->feed; + + debug_print("RSSyl: Calling subscribe dialog routine...\n"); + rssyl_subscribe_dialog(sctx); + + if (sctx->feed == NULL) { + debug_print("RSSyl: User cancelled subscribe.\n"); + g_free(sctx); + return FALSE; + } + } + + /* OK, feed is succesfully fetched and correct, let's add it to CM. */ + + /* Create a folder for it. */ + tmpname = rssyl_format_string(ctx->feed->title, TRUE, TRUE); + tmpname2 = g_strdup(tmpname); + while (folder_find_child_item_by_name(parent, tmpname2) != 0 && i < 20) { + debug_print("RSSyl: Folder '%s' already exists, trying another name\n", + tmpname2); + g_free(tmpname2); + tmpname2 = g_strdup_printf("%s__%d", tmpname, ++i); + } + /* TODO: handle cases where i reaches 20 */ + + folder_item_update_freeze(); + + new_item = folder_create_folder(parent, tmpname2); + g_free(tmpname); + g_free(tmpname2); + + if (!new_item) { + if (verbose) + alertpanel_error(_("Couldn't create folder for new feed '%s'."), + myurl); + feed_free(ctx->feed); + g_free(ctx->error); + g_free(ctx); + g_free(myurl); + return FALSE; + } + + debug_print("RSSyl: Adding '%s'\n", ctx->feed->url); + + ritem = (RFolderItem *)new_item; + ritem->url = g_strdup(ctx->feed->url); + + if (feed_n_items(ctx->feed) > 0) + feed_foreach_item(ctx->feed, rssyl_subscribe_foreach_func, (gpointer)ritem); + + folder_item_scan(new_item); + folder_write_list(); + folder_item_update_thaw(); + + return TRUE; +} diff --git a/src/plugins/rssyl/rssyl_subscribe.h b/src/plugins/rssyl/rssyl_subscribe.h new file mode 100644 index 000000000..52b80f97d --- /dev/null +++ b/src/plugins/rssyl/rssyl_subscribe.h @@ -0,0 +1,6 @@ +#ifndef __RSSYL_SUBSCRIBE_H +#define __RSSYL_SUBSCRIBE_H + +gboolean rssyl_subscribe(FolderItem *parent, const gchar *url, gboolean verbose); + +#endif /* __RSSYL_SUBSCRIBE_H */ diff --git a/src/plugins/rssyl/rssyl_subscribe_gtk.c b/src/plugins/rssyl/rssyl_subscribe_gtk.c new file mode 100644 index 000000000..3f6344003 --- /dev/null +++ b/src/plugins/rssyl/rssyl_subscribe_gtk.c @@ -0,0 +1,109 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 1999-2004 Hiroyuki Yamamoto + * This file (C) 2005 Andrej Kacian + * + * - Dialog which appears when manually subscribing a new feed. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include + +/* Local includes */ +#include "libfeed/feed.h" +#include "rssyl_subscribe_gtk.h" + +void rssyl_subscribe_dialog(RSubCtx *ctx) { + GtkWidget *win, *vbox, *titleframe, *titlelabel; +#if !(GTK_CHECK_VERSION(2, 12, 0)) + GtkTooltips *tooltips; +#endif + gint ret; + gchar *newtitle; + + g_return_if_fail(ctx != NULL); + g_return_if_fail(ctx->feed != NULL); + + /* Create window */ + win = gtk_dialog_new_with_buttons(_("Subscribe new feed?"), + GTK_WINDOW(mainwindow_get_mainwindow()->window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(win), GTK_RESPONSE_ACCEPT); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(win)); + +#if !(GTK_CHECK_VERSION(2, 12, 0)) + tooltips = gtk_tooltips_new(); + gtk_tooltips_enable(tooltips); +#endif + + /* Feed title */ + titleframe = gtk_frame_new(NULL); + gtk_container_set_border_width(GTK_CONTAINER(titleframe), 5); + gtk_frame_set_label_align(GTK_FRAME(titleframe), 0.05, 0.5); + gtk_frame_set_shadow_type(GTK_FRAME(titleframe), GTK_SHADOW_ETCHED_OUT); + gtk_box_pack_start(GTK_BOX(vbox), titleframe, FALSE, FALSE, 0); + + titlelabel = gtk_label_new(_("Feed folder:")); + gtk_label_set_use_markup(GTK_LABEL(titlelabel), TRUE); + gtk_misc_set_padding(GTK_MISC(titlelabel), 5, 0); + gtk_frame_set_label_widget(GTK_FRAME(titleframe), titlelabel); + + ctx->title = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(ctx->title), feed_get_title(ctx->feed)); + gtk_entry_set_activates_default(GTK_ENTRY(ctx->title), TRUE); +#if !(GTK_CHECK_VERSION(2, 12, 0)) + gtk_tooltips_set_tip(tooltips, ctx->title, + _("Instead of using official title, you can enter a different folder " + "name for the feed."), NULL); +#else + gtk_widget_set_tooltip_text(ctx->title, + _("Instead of using official title, you can enter a different folder " + "name for the feed.")); +#endif + gtk_container_add(GTK_CONTAINER(titleframe), ctx->title); + + gtk_widget_show_all(vbox); + + ret = gtk_dialog_run(GTK_DIALOG(win)); + + if (ret == GTK_RESPONSE_ACCEPT) { + /* Modify ctx->feed based on user changes in dialog */ + newtitle = (gchar *)gtk_entry_get_text(GTK_ENTRY(ctx->title)); + if (strcmp(feed_get_title(ctx->feed), newtitle)) { + debug_print("RSSyl: Using feed title '%s'\n", newtitle); + feed_set_title(ctx->feed, newtitle); + } + } else { + /* Destroy the feed to signal outside that user cancelled subscribing */ + feed_free(ctx->feed); + ctx->feed = NULL; + } + + gtk_widget_destroy(win); +} diff --git a/src/plugins/rssyl/rssyl_subscribe_gtk.h b/src/plugins/rssyl/rssyl_subscribe_gtk.h new file mode 100644 index 000000000..2d40b1363 --- /dev/null +++ b/src/plugins/rssyl/rssyl_subscribe_gtk.h @@ -0,0 +1,15 @@ +#ifndef __RSSYL_SUBSCRIBE_GTK_H +#define __RSSYL_SUBSCRIBE_GTK_H + +#include "libfeed/feed.h" + +struct _RSubCtx { + Feed *feed; + GtkWidget *title; +}; + +typedef struct _RSubCtx RSubCtx; + +void rssyl_subscribe_dialog(RSubCtx *ctx); + +#endif /* __RSSYL_SUBSCRIBE_GTK_H */ diff --git a/src/plugins/rssyl/rssyl_update_comments.c b/src/plugins/rssyl/rssyl_update_comments.c new file mode 100644 index 000000000..c54142da4 --- /dev/null +++ b/src/plugins/rssyl/rssyl_update_comments.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include +#include + +/* Claws Mail includes */ +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feed.h" +#include "rssyl.h" +#include "rssyl_feed.h" +#include "rssyl_parse_feed.h" +#include "rssyl_update_feed.h" +#include "parse822.h" + +static void rssyl_update_reference_func(gpointer data, gpointer user_data) +{ + FeedItem *item = (FeedItem *)data; + gchar *parent_id = (gchar *)user_data; + + g_return_if_fail(item != NULL); + g_return_if_fail(user_data != NULL); + + feed_item_set_parent_id(item, parent_id); +} + +void rssyl_update_comments(RFolderItem *ritem) +{ + FolderItem *item = &ritem->item; + FeedItem *fi = NULL; + RFetchCtx *ctx = NULL; + DIR *dp; + struct dirent *d; + gint num; + gchar *path, *msg, *fname; + MainWindow *mainwin = mainwindow_get_mainwindow(); + + g_return_if_fail(ritem != NULL); + + if( ritem->fetch_comments == FALSE ) + return; + + path = folder_item_get_path(item); + g_return_if_fail(path != NULL); + + debug_print("RSSyl: starting to parse comments, path is '%s'\n", path); + + if( (dp = opendir(path)) == NULL ) { + FILE_OP_ERROR(item->path, "opendir"); + g_free(path); + return; + } + + ritem->fetching_comments = TRUE; + + while( (d = readdir(dp)) != NULL ) { + if (claws_is_exiting()) { + closedir(dp); + g_free(path); + debug_print("RSSyl: bailing out, app is exiting\n"); + } + + if( (num = to_number(d->d_name)) > 0 && d->d_type == DT_REG ) { + debug_print("RSSyl: starting to parse '%s'\n", d->d_name); + + fname = g_strdup_printf("%s%c%s", path, G_DIR_SEPARATOR, d->d_name); + if( (fi = rssyl_parse_folder_item_file(fname)) != NULL ) { + if( feed_item_get_comments_url(fi) && feed_item_get_id(fi) && + (ritem->fetch_comments_max_age == -1 || + time(NULL) - feed_item_get_date_modified(fi) <= ritem->fetch_comments_max_age*86400)) { + msg = g_strdup_printf(_("Updating comments for '%s'..."), + feed_item_get_title(fi)); + debug_print("RSSyl: updating comments for '%s' (%s)\n", + feed_item_get_title(fi), feed_item_get_comments_url(fi)); + STATUSBAR_PUSH(mainwin, msg); + + ctx = rssyl_prep_fetchctx_from_url(feed_item_get_comments_url(fi)); + g_return_if_fail(ctx != NULL); + feed_set_ssl_verify_peer(ctx->feed, ritem->ssl_verify_peer); + + rssyl_fetch_feed(ctx, FALSE); + + if( ctx->success && feed_n_items(ctx->feed) > 0 ) { + g_free(ctx->feed->title); + ctx->feed->title = g_strdup(ritem->official_title); + + feed_foreach_item(ctx->feed, rssyl_update_reference_func, + feed_item_get_id(fi)); + + if( !rssyl_parse_feed(ritem, ctx->feed) ) { + debug_print("RSSyl: Error processing comments feed\n"); + log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PROC, ctx->feed->url); + } + } + } + + STATUSBAR_POP(mainwin); + + feed_item_free(fi); + } + + g_free(fname); + } + } + + closedir(dp); + g_free(path); + + ritem->fetching_comments = FALSE; + + debug_print("RSSyl: rssyl_update_comments() is done\n"); +} diff --git a/src/plugins/rssyl/rssyl_update_comments.h b/src/plugins/rssyl/rssyl_update_comments.h new file mode 100644 index 000000000..3ce56e426 --- /dev/null +++ b/src/plugins/rssyl/rssyl_update_comments.h @@ -0,0 +1,12 @@ + + +#ifndef __RSSYL_UPDATE_COMMENTS +#define __RSSYL_UPDATE_COMMENTS + +#include + +#include "rssyl.h" + +void rssyl_update_comments(RFolderItem *ritem); + +#endif /* __RSSYL_UPDATE_COMMENTS */ diff --git a/src/plugins/rssyl/rssyl_update_feed.c b/src/plugins/rssyl/rssyl_update_feed.c new file mode 100644 index 000000000..8a97f90f6 --- /dev/null +++ b/src/plugins/rssyl/rssyl_update_feed.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include +#include + +/* Claws Mail includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local includes */ +#include "libfeed/feed.h" +#include "rssyl.h" +#include "rssyl_deleted.h" +#include "rssyl_feed.h" +#include "rssyl_parse_feed.h" +#include "rssyl_prefs.h" +#include "rssyl_update_comments.h" + +/* rssyl_fetch_feed_thr() */ + +static void *rssyl_fetch_feed_thr(void *arg) +{ + RFetchCtx *ctx = (RFetchCtx *)arg; + + /* Fetch and parse the feed. */ + ctx->response_code = feed_update(ctx->feed, -1); + + /* Signal main thread that we're done here. */ + ctx->ready = TRUE; + + return NULL; +} + +/* rssyl_fetch_feed() */ +RFetchCtx *rssyl_fetch_feed(RFetchCtx *ctx, gboolean verbose) +{ +#ifdef USE_PTHREAD + pthread_t pt; +#endif + + g_return_if_fail(ctx != NULL); + +#ifdef USE_PTHREAD + if( pthread_create(&pt, PTHREAD_CREATE_JOINABLE, rssyl_fetch_feed_thr, + (void *)ctx) != 0 ) { + /* Bummer, couldn't create thread. Continue non-threaded. */ + rssyl_fetch_feed_thr(ctx); + } else { + /* Thread created, let's wait until it finishes. */ + debug_print("RSSyl: waiting for thread to finish (timeout: %ds)\n", + feed_get_timeout(ctx->feed)); + while( !ctx->ready ) { + claws_do_idle(); + } + + debug_print("RSSyl: thread finished\n"); + pthread_join(pt, NULL); + } +#else + debug_print("RSSyl: no pthreads available, running non-threaded fetch\n"); + rssyl_fetch_feed_thr(ctx); +#endif + + if( ctx->response_code == FEED_ERR_INIT ) { + debug_print("RSSyl: libfeed reports init error from libcurl\n"); + ctx->error = g_strdup("Internal error"); + } else if( ctx->response_code == FEED_ERR_FETCH ) { + debug_print("RSSyl: libfeed reports some other error from libcurl\n"); + ctx->error = g_strdup(ctx->feed->fetcherr); + } else if( ctx->response_code >= 400 && ctx->response_code < 500 ) { + switch( ctx->response_code ) { + case 401: + ctx->error = g_strdup(_("401 (Authorisation required)")); + break; + case 403: + ctx->error = g_strdup(_("403 (Unauthorised)")); + break; + case 404: + ctx->error = g_strdup(_("404 (Not found)")); + break; + default: + ctx->error = g_strdup_printf(_("Error %d"), ctx->response_code); + break; + } + } + + /* Here we handle "imperfect" conditions. If verbose is TRUE, we also + * display error dialogs for user. We always log the error. */ + if( ctx->error != NULL ) { + /* libcurl wasn't happy */ + debug_print("RSSyl: Error: %s\n", ctx->error); + if( verbose ) + alertpanel_error(C_("First parameter is URL, second is error text", + "Error fetching feed at\n%s:\n\n%s"), + feed_get_url(ctx->feed), ctx->error); + + log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_FETCH, ctx->feed->url, ctx->error); + + ctx->success = FALSE; + } else { + if( ctx->feed == NULL || feed_get_title(ctx->feed) == NULL ) { + /* libcurl was happy, but libfeed wasn't */ + debug_print("RSSyl: Error reading feed\n"); + if( verbose ) + alertpanel_error(_("No valid feed found at\n%s"), + feed_get_url(ctx->feed)); + log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_NOFEED, ctx->feed->url); + ctx->success = FALSE; + } + } +} + +RFetchCtx *rssyl_prep_fetchctx_from_item(RFolderItem *ritem) +{ + RFetchCtx *ctx = NULL; + + g_return_val_if_fail(ritem != NULL, NULL); + + ctx = g_new0(RFetchCtx, 1); + ctx->feed = feed_new(ritem->url); + ctx->error = NULL; + ctx->success = TRUE; + ctx->ready = FALSE; + + feed_set_timeout(ctx->feed, prefs_common.io_timeout_secs); + feed_set_cookies_path(ctx->feed, rssyl_prefs_get()->cookies_path); + feed_set_ssl_verify_peer(ctx->feed, ritem->ssl_verify_peer); + + return ctx; +} + +RFetchCtx *rssyl_prep_fetchctx_from_url(gchar *url) +{ + RFetchCtx *ctx = NULL; + + g_return_val_if_fail(url != NULL, NULL); + + ctx = g_new0(RFetchCtx, 1); + ctx->feed = feed_new(url); + ctx->error = NULL; + ctx->success = TRUE; + ctx->ready = FALSE; + + feed_set_timeout(ctx->feed, prefs_common.io_timeout_secs); + feed_set_cookies_path(ctx->feed, rssyl_prefs_get()->cookies_path); + feed_set_ssl_verify_peer(ctx->feed, rssyl_prefs_get()->ssl_verify_peer); + + return ctx; +} + +/* rssyl_update_feed() */ + +gboolean rssyl_update_feed(RFolderItem *ritem, gboolean verbose) +{ + RFetchCtx *ctx = NULL; + MainWindow *mainwin = mainwindow_get_mainwindow(); + gchar *msg = NULL; + gboolean success = FALSE; + + g_return_val_if_fail(ritem != NULL, FALSE); + g_return_val_if_fail(ritem->url != NULL, FALSE); + + debug_print("RSSyl: starting to update '%s' (%s)\n", + ritem->item.name, ritem->url); + + log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATING, ritem->url); + + msg = g_strdup_printf(_("Updating feed '%s'..."), ritem->item.name); + STATUSBAR_PUSH(mainwin, msg); + g_free(msg); + + GTK_EVENTS_FLUSH(); + + /* Prepare context for fetching the feed file */ + ctx = rssyl_prep_fetchctx_from_item(ritem); + g_return_val_if_fail(ctx != NULL, FALSE); + + /* Fetch the feed file */ + rssyl_fetch_feed(ctx, verbose); + + debug_print("RSSyl: fetch done; success == %s\n", + ctx->success ? "TRUE" : "FALSE"); + + debug_print("RSSyl: STARTING TO PARSE FEED\n"); + if( ctx->success && !(ctx->success = rssyl_parse_feed(ritem, ctx->feed)) ) { + /* both libcurl and libfeed were happy, but we weren't */ + debug_print("RSSyl: Error processing feed\n"); + if( verbose ) + alertpanel_error(_("Couldn't process feed at\n%s\n\nPlease contact developers, this should not happen."), + feed_get_url(ctx->feed)); + + log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PROC, ctx->feed->url); + } + + debug_print("RSSyl: FEED PARSED\n"); + + STATUSBAR_POP(mainwin); + + if( claws_is_exiting() ) { + feed_free(ctx->feed); + g_free(ctx->error); + g_free(ctx); + return success; + } + + if( ritem->fetch_comments ) + rssyl_update_comments(ritem); + + /* Prune our deleted items list of items which are no longer in + * upstream feed. */ + rssyl_deleted_expire(ritem, ctx->feed); + + /* Clean up. */ + success = ctx->success; + feed_free(ctx->feed); + g_free(ctx->error); + g_free(ctx); + + return success; +} + +static gboolean rssyl_update_recursively_func(GNode *node, gpointer data) +{ + FolderItem *item; + RFolderItem *ritem; + + g_return_val_if_fail(node->data != NULL, FALSE); + + item = FOLDER_ITEM(node->data); + ritem = (RFolderItem *)item; + + if( ritem->url != NULL ) { + debug_print("RSSyl: Updating feed '%s'\n", item->name); + rssyl_update_feed(ritem, FALSE); + } else + debug_print("RSSyl: Updating in folder '%s'\n", item->name); + + return FALSE; +} + +void rssyl_update_recursively(FolderItem *item) +{ + g_return_if_fail(item != NULL); + g_return_if_fail(item->folder != NULL); + + if( item->folder->klass != rssyl_folder_get_class() ) + return; + + debug_print("Recursively updating '%s'\n", item->name); + + g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + rssyl_update_recursively_func, NULL); +} + +void rssyl_update_all_func(FolderItem *item, gpointer data) +{ + /* Only try to refresh our feed folders */ + if( !IS_RSSYL_FOLDER_ITEM(item) ) + return; + + if( folder_item_parent(item) == NULL ) + rssyl_update_recursively(item); +} + +void rssyl_update_all_feeds(void) +{ + if (prefs_common.work_offline && + !inc_offline_should_override(TRUE, + _("Claws Mail needs network access in order to update your feeds.")) ) { + return; + } + + folder_func_to_all_folders((FolderItemFunc)rssyl_update_all_func, NULL); +} diff --git a/src/plugins/rssyl/rssyl_update_feed.h b/src/plugins/rssyl/rssyl_update_feed.h new file mode 100644 index 000000000..f72d04ad3 --- /dev/null +++ b/src/plugins/rssyl/rssyl_update_feed.h @@ -0,0 +1,19 @@ +#ifndef __RSSYL_UPDATE_FEED +#define __RSSYL_UPDATE_FEED + +#include + +#include "rssyl.h" + +void rssyl_fetch_feed(RFetchCtx *ctx, gboolean verbose); + +RFetchCtx *rssyl_prep_fetchctx_from_url(gchar *url); +RFetchCtx *rssyl_prep_fetchctx_from_item(RFolderItem *ritem); + +gboolean rssyl_update_feed(RFolderItem *ritem, gboolean verbose); + +void rssyl_update_recursively(FolderItem *item); + +void rssyl_update_all_feeds(void); + +#endif /* __RSSYL_UPDATE_FEED */ diff --git a/src/plugins/rssyl/rssyl_update_format.c b/src/plugins/rssyl/rssyl_update_format.c new file mode 100644 index 000000000..828445075 --- /dev/null +++ b/src/plugins/rssyl/rssyl_update_format.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2006 Andrej Kacian + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include + +/* Claws Mail includes */ +#include +#include +#include + +/* Local includes */ +#include "old_feeds.h" +#include "rssyl.h" +#include "rssyl_feed.h" +#include "strutils.h" + +struct _RUpdateFormatCtx { + FolderItem *o_prev; + FolderItem *o_parent; + FolderItem *n_prev; + FolderItem *n_parent; + Folder *n_first; + GSList *oldfeeds; + GSList *oldroots; + gboolean reached_first_new; +}; + +typedef struct _RUpdateFormatCtx RUpdateFormatCtx; + +extern FolderClass rssyl_class; + +static void rssyl_update_format_move_contents(FolderItem *olditem, + FolderItem *newitem); +static gchar *_old_rssyl_item_get_path(Folder *folder, FolderItem *item); +static void _delete_old_roots_func(gpointer data, gpointer user_data); + +static void rssyl_update_format_func(FolderItem *item, gpointer data) +{ + RFolderItem *ritem; + RUpdateFormatCtx *ctx = (RUpdateFormatCtx *)data; + Folder *f = NULL; + FolderItem *new_item = NULL; + gchar *name; + OldRFeed *of; + + if( !IS_RSSYL_FOLDER_ITEM(item) ) + return; + + /* Do not do anything once we reached first new folder + * (which we created earlier in this process) */ + if( ctx->reached_first_new ) + return; + + if( item->folder == ctx->n_first ) { + ctx->reached_first_new = TRUE; + debug_print("RSSyl: (FORMAT) reached first new folder\n"); + return; + } + + debug_print("RSSyl: (FORMAT) item '%s'\n", item->name); + + if( folder_item_parent(item) == NULL ) { + /* Root rssyl folder */ + ctx->oldroots = g_slist_prepend(ctx->oldroots, item); + + /* Create its counterpart */ + name = rssyl_strreplace(folder_item_get_name(item), " (RSSyl)", ""); + debug_print("RSSyl: (FORMAT) adding new root folder '%s'\n", name); + f = folder_new(rssyl_folder_get_class(), name, NULL); + g_free(name); + g_return_if_fail(f != NULL); + folder_add(f); + + folder_write_list(); + + new_item = FOLDER_ITEM(f->node->data); + + /* If user has more than one old rssyl foldertrees, keep the n_first + * pointer at the beginning of first one. */ + if (ctx->n_first == NULL) + ctx->n_first = f; + + ctx->n_parent = new_item; + } else { + /* Non-root folder */ + + if (folder_item_parent(item) == ctx->o_prev) { + /* We went one step deeper in folder hierarchy, adjust pointers + * to parents */ + ctx->o_parent = ctx->o_prev; + ctx->n_parent = ctx->n_prev; + } else if (folder_item_parent(item) != ctx->o_parent) { + /* We are not in same folder anymore, which can only mean we have + * moved up in the hierarchy. Find a correct parent */ + while (folder_item_parent(item) != ctx->o_parent) { + ctx->o_parent = folder_item_parent(ctx->o_parent); + ctx->n_parent = folder_item_parent(ctx->n_parent); + if (ctx->o_parent == NULL) { + /* This shouldn't happen, unless we are magically moved to a + * completely different folder structure */ + debug_print("RSSyl: MISHAP WHILE UPGRADING STORAGE FORMAT: couldn't find folder parent\n"); + alertpanel_error(_("Internal problem while upgrading storage format. This should not happen. Please report this, with debug output attached.\n")); + return; + } + } + } else { + /* We have remained in the same subfolder, nothing to do here */ + } + + debug_print("RSSyl: (FORMAT) adding folder '%s'\n", item->name); + new_item = folder_create_folder(ctx->n_parent, item->name); + + if (new_item == NULL) { + debug_print("RSSyl: (FORMAT) couldn't add folder '%s', skipping it\n", + item->name); + return; + } + + of = rssyl_old_feed_get_by_name(ctx->oldfeeds, item->name); + if (of != NULL && of->url != NULL) { + /* Folder with an actual subscribed feed */ + debug_print("RSSyl: (FORMAT) making '%s' a feed with URL '%s'\n", + item->name, of->url); + + ritem = (RFolderItem *)new_item; + ritem->url = g_strdup(of->url); + + rssyl_feed_start_refresh_timeout(ritem); + + /* TODO: copy feed preferences from old structure */ + ritem->official_title = g_strdup(of->official_name); + ritem->default_refresh_interval = + (of->default_refresh_interval != 0 ? TRUE : FALSE); + ritem->refresh_interval = of->refresh_interval; + ritem->keep_old = (of->expired_num > -1 ? TRUE : FALSE); + ritem->fetch_comments = + (of->fetch_comments != 0 ? TRUE : FALSE); + ritem->fetch_comments_max_age = of->fetch_comments_for; + ritem->silent_update = of->silent_update; + } + + rssyl_update_format_move_contents(item, new_item); + + /* Store folderlist with the new folder */ + folder_item_scan(new_item); + folder_write_list(); + } + + ctx->o_prev = item; + ctx->n_prev = new_item; +} + + +void rssyl_update_format() +{ + RUpdateFormatCtx *ctx = NULL; + GSList *oldfeeds; + gchar *old_feeds_xml = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, + RSSYL_DIR, G_DIR_SEPARATOR_S, "feeds.xml", NULL); + + if (!g_file_test(old_feeds_xml, + G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) { + g_free(old_feeds_xml); + return; + } + + debug_print("RSSyl: Old format found, updating.\n"); + + oldfeeds = rssyl_old_feed_metadata_parse(old_feeds_xml); + + /* We find all rssyl root folders and perform magic on each */ + ctx = g_new0(RUpdateFormatCtx, 1); + ctx->o_prev = NULL; + ctx->o_parent = NULL; + ctx->n_prev = NULL; + ctx->n_parent = NULL; + ctx->n_first = NULL; + ctx->oldfeeds = oldfeeds; + ctx->oldroots = NULL; + ctx->reached_first_new = FALSE; + + folder_item_update_freeze(); + + /* Go through all RSSyl folders, making new copies */ + folder_func_to_all_folders((FolderItemFunc)rssyl_update_format_func, ctx); + + g_slist_foreach(ctx->oldroots, _delete_old_roots_func, NULL); + g_slist_free(ctx->oldroots); + + folder_item_update_thaw(); + + g_free(ctx); + + g_remove(old_feeds_xml); + g_free(old_feeds_xml); +} + +static void _delete_old_roots_func(gpointer data, gpointer user_data) +{ + FolderItem *item = (FolderItem *)data; + + folder_destroy(item->folder); +} + +/* Copy each item in a feed to the new directory */ +static void rssyl_update_format_move_contents(FolderItem *olditem, + FolderItem *newitem) +{ + gchar *oldpath, *newpath, *fname, *fpath, *nfpath; + GDir *d = NULL; + GError *error = NULL; + + oldpath = _old_rssyl_item_get_path(NULL, olditem); + newpath = folder_item_get_path(newitem); + + if ((d = g_dir_open(oldpath, 0, &error)) == NULL) { + debug_print("RSSyl: (FORMAT) couldn't open dir '%s': %s\n", oldpath, + error->message); + g_error_free(error); + return; + } + + debug_print("RSSyl: (FORMAT) moving contents of '%s' to '%s'\n", + oldpath, newpath); + + while ((fname = (gchar *)g_dir_read_name(d)) != NULL) { + fpath = g_strconcat(oldpath, G_DIR_SEPARATOR_S, fname, NULL); + if (to_number(fname) > 0 && g_file_test(fpath, G_FILE_TEST_IS_REGULAR)) { + nfpath = g_strconcat(newpath, G_DIR_SEPARATOR_S, fname, NULL); + move_file(fpath, nfpath, FALSE); + g_free(nfpath); + } + g_remove(fpath); + g_free(fpath); + } + + g_dir_close(d); + g_rmdir(oldpath); + + g_free(oldpath); + g_free(newpath); +} + +static gchar *_old_rssyl_item_get_path(Folder *folder, FolderItem *item) +{ + gchar *result, *tmp; + + if (folder_item_parent(item) == NULL) + return g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, NULL); + + tmp = rssyl_strreplace(item->name, "/", "\\"); + result = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR, + G_DIR_SEPARATOR_S, tmp, NULL); + g_free(tmp); + return result; +} diff --git a/src/plugins/rssyl/rssyl_update_format.h b/src/plugins/rssyl/rssyl_update_format.h new file mode 100644 index 000000000..7e3a858ca --- /dev/null +++ b/src/plugins/rssyl/rssyl_update_format.h @@ -0,0 +1,6 @@ +#ifndef __RSSYL_UPDATE_FORMAT +#define __RSSYL_UPDATE_FORMAT + +void rssyl_update_format(); + +#endif /* __RSSYL_UPDATE_FORMAT */ diff --git a/src/plugins/rssyl/strreplace.c b/src/plugins/rssyl/strreplace.c deleted file mode 100644 index 7d4322cfd..000000000 --- a/src/plugins/rssyl/strreplace.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client - * Copyright (C) 2005 Andrej Kacian - * - * - a strreplace function (something like sed's s/foo/bar/g) - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include -#include - -#include "common/utils.h" - -gchar *rssyl_strreplace(const gchar *source, gchar *pattern, - gchar *replacement) -{ - gchar *new, *w_new; - const gchar *c; - guint count = 0, final_length; - size_t len_pattern, len_replacement; - -/* - debug_print("RSSyl: ======= strreplace: '%s': '%s'->'%s'\n", source, pattern, - replacement); -*/ - - if( source == NULL || pattern == NULL ) { - debug_print("RSSyl: source or pattern is NULL!!!\n"); - return NULL; - } - - if( !g_utf8_validate(source, -1, NULL) ) { - debug_print("RSSyl: source is not an UTF-8 encoded text\n"); - return NULL; - } - - if( !g_utf8_validate(pattern, -1, NULL) ) { - debug_print("RSSyl: pattern is not an UTF-8 encoded text\n"); - return NULL; - } - - len_pattern = strlen(pattern); - len_replacement = strlen(replacement); - - c = source; - while( ( c = g_strstr_len(c, strlen(c), pattern) ) ) { - count++; - c += len_pattern; - } - -/* - debug_print("RSSyl: ==== count = %d\n", count); -*/ - - final_length = strlen(source) - - ( count * len_pattern ) - + ( count * len_replacement ); - - new = malloc(final_length + 1); - w_new = new; - memset(new, '\0', final_length + 1); - - c = source; - - while( *c != '\0' ) { - if( !memcmp(c, pattern, len_pattern) ) { - gboolean break_after_rep = FALSE; - int i; - if (*(c + len_pattern) == '\0') - break_after_rep = TRUE; - for (i = 0; i < len_replacement; i++) { - *w_new = replacement[i]; - w_new++; - } - if (break_after_rep) - break; - c = c + len_pattern; - } else { - *w_new = *c; - w_new++; - c++; - } - } - return new; -} - -gchar *rssyl_sanitize_string(const gchar *str, gboolean strip_nl) -{ - gchar *new = NULL; - const gchar *c = str; - gchar *new_ptr; - if( str == NULL ) - return NULL; - - new_ptr = new = malloc(strlen(str) + 1); - if (new == NULL) - return NULL; - memset(new, '\0', strlen(str) + 1); - - while( *c != '\0' ) { - if( !g_ascii_isspace(*c) || *c == ' ' || (!strip_nl && *c == '\n') ) { - *new_ptr = *c; - new_ptr++; - } - c++; - } - - return new; -} diff --git a/src/plugins/rssyl/strreplace.h b/src/plugins/rssyl/strreplace.h deleted file mode 100644 index 37c29dc03..000000000 --- a/src/plugins/rssyl/strreplace.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef __STRREPLACE_H -#define __STRREPLACE_H - -gchar *rssyl_strreplace(const gchar *source, gchar *pattern, - gchar *replacement); -gchar *rssyl_sanitize_string(const gchar *str, gboolean strip_nl); - -#endif /* __STRREPLACE_H */ diff --git a/src/plugins/rssyl/strutils.c b/src/plugins/rssyl/strutils.c new file mode 100644 index 000000000..3d9f1c53f --- /dev/null +++ b/src/plugins/rssyl/strutils.c @@ -0,0 +1,289 @@ +/* + * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2005 Andrej Kacian + * + * - a strreplace function (something like sed's s/foo/bar/g) + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Global includes */ +#include +#include +#include + +/* Claws Mail includes */ +#include + +/* Local includes */ +/* (shouldn't be any) */ + +gchar *rssyl_strreplace(gchar *source, gchar *pattern, + gchar *replacement) +{ + gchar *new, *w_new = NULL, *c; + guint count = 0, final_length; + size_t len_pattern, len_replacement; + + /* + debug_print("RSSyl: ======= strreplace: '%s': '%s'->'%s'\n", source, pattern, + replacement); + */ + + if( source == NULL || pattern == NULL ) { + debug_print("RSSyl: source or pattern is NULL!!!\n"); + return source; + } + + if( !g_utf8_validate(source, -1, NULL) ) { + debug_print("RSSyl: source is not an UTF-8 encoded text\n"); + return source; + } + + if( !g_utf8_validate(pattern, -1, NULL) ) { + debug_print("RSSyl: pattern is not an UTF-8 encoded text\n"); + return source; + } + + len_pattern = strlen(pattern); + len_replacement = strlen(replacement); + + c = source; + while( ( c = g_strstr_len(c, strlen(c), pattern) ) ) { + count++; + c += len_pattern; + } + + /* + debug_print("RSSyl: ==== count = %d\n", count); + */ + + final_length = strlen(source) + - ( count * len_pattern ) + + ( count * len_replacement ); + + new = malloc(final_length + 1); + w_new = new; + memset(new, '\0', final_length + 1); + + c = source; + + while( *c != '\0' ) { + if( !memcmp(c, pattern, len_pattern) ) { + gboolean break_after_rep = FALSE; + int i; + if (*(c + len_pattern) == '\0') + break_after_rep = TRUE; + for (i = 0; i < len_replacement; i++) { + *w_new = replacement[i]; + w_new++; + } + if (break_after_rep) + break; + c = c + len_pattern; + } else { + *w_new = *c; + w_new++; + c++; + } + } + return new; +} + +typedef struct _RSSyl_HTMLSymbol RSSyl_HTMLSymbol; +struct _RSSyl_HTMLSymbol +{ + gchar *const key; + gchar *const val; +}; + +static RSSyl_HTMLSymbol symbol_list[] = { + { "<", "<" }, + { ">", ">" }, + { "&", "&" }, + { """, "\"" }, + { "‘", "'" }, + { "’", "'" }, + { "“", "\"" }, + { "”", "\"" }, + { " ", " " }, + { "™", "(TM)" }, + { "™", "(TM)" }, + { "'", "'" }, + { "…", "..." }, + { "…", "..." }, + { "—", "-" }, + { NULL, NULL } +}; + +static RSSyl_HTMLSymbol tag_list[] = { + { "", "\"" }, + { "", "\"" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { NULL, NULL } +}; + +gchar *rssyl_replace_html_stuff(gchar *text, + gboolean symbols, gboolean tags) +{ + gchar *tmp = NULL, *wtext = NULL; + gint i; + + g_return_val_if_fail(text != NULL, NULL); + + wtext = g_strdup(text); + + /* Ugly, needlessly traverses the string again and again. Probably + * could use a rewrite. */ + if( symbols ) { + for( i = 0; symbol_list[i].key != NULL; i++ ) { + if( g_strstr_len(text, strlen(text), symbol_list[i].key) ) { + tmp = rssyl_strreplace(wtext, symbol_list[i].key, symbol_list[i].val); + wtext = g_strdup(tmp); + g_free(tmp); + } + } + } + + if( tags ) { + for( i = 0; tag_list[i].key != NULL; i++ ) { + if( g_strstr_len(text, strlen(text), symbol_list[i].key) ) { + tmp = rssyl_strreplace(wtext, tag_list[i].key, tag_list[i].val); + wtext = g_strdup(tmp); + g_free(tmp); + } + } + } + + return wtext; +} + +static gchar *rssyl_sanitize_string(gchar *str, gboolean strip_nl) +{ + gchar *new = NULL, *c = str, *n = NULL; + + if( str == NULL ) + return NULL; + + n = new = malloc(strlen(str) + 1); + memset(new, '\0', strlen(str) + 1); + + while( *c != '\0' ) { + if( !isspace(*c) || *c == ' ' || (!strip_nl && *c == '\n') ) { + *n = *c; + n++; + } + c++; + } + + return new; +} + +/* rssyl_format_string() + * - return value needs to be freed + */ +gchar *rssyl_format_string(gchar *str, gboolean replace_html, + gboolean strip_nl) +{ + gchar *res = NULL, *tmp = NULL; + + g_return_val_if_fail(str != NULL, NULL); + + if (replace_html) + tmp = rssyl_replace_html_stuff(str, TRUE, TRUE); + else + tmp = g_strdup(str); + + res = rssyl_sanitize_string(tmp, strip_nl); + g_free(tmp); + + g_strstrip(res); + + return res; +} + +/* this functions splits a string into an array of string, by + * returning an array of pointers to positions of the delimiter + * in the original string and replacing this delimiter with a + * NULL. It does not duplicate memory, hence you should only + * free the array and not its elements, and you should not + * free the original string before you're done with the array. + * maybe could be part of the core (utils.c). + */ +gchar **strsplit_no_copy(gchar *str, char delimiter) +{ + gchar **array = g_new(gchar *, 1); + int i = 0; + gchar *cur = str, *next; + + array[i] = cur; + i++; + while ((next = strchr(cur, delimiter)) != NULL) { + *(next) = '\0'; + array = g_realloc(array, (sizeof(gchar *)) * (i + 1)); + array[i] = next + 1; + cur = next + 1; + i++; + } + array = g_realloc(array, (sizeof(gchar *)) * (i + 1)); + array[i] = NULL; + return array; +} + +/* This is a very dumb function - it just strips <, > and everything between + * them. */ +void strip_html(gchar *str) +{ + gchar *p = str; + gboolean intag = FALSE; + + while (*p) { + if (*p == '<') + intag = TRUE; + else if (*p == '>') + intag = FALSE; + + if (*p == '<' || *p == '>' || intag) + memmove(p, p + 1, strlen(p)); + else + p++; + } +} + +gchar *my_normalize_url(const gchar *url) +{ + gchar *myurl = NULL; + + if (!strncmp(url, "feed://", 7)) + myurl = g_strdup(url+7); + else if (!strncmp(url, "feed:", 5)) + myurl = g_strdup(url+5); + else + myurl = g_strdup(url); + + return myurl; +} diff --git a/src/plugins/rssyl/strutils.h b/src/plugins/rssyl/strutils.h new file mode 100644 index 000000000..2976e6f9c --- /dev/null +++ b/src/plugins/rssyl/strutils.h @@ -0,0 +1,19 @@ +#ifndef __STRUTILS_H +#define __STRUTILS_H + +gchar *rssyl_strreplace(gchar *source, gchar *pattern, + gchar *replacement); + +gchar *rssyl_replace_html_stuff(gchar *text, + gboolean symbols, gboolean tags); + +gchar *rssyl_format_string(gchar *str, gboolean replace_html, + gboolean strip_nl); + +gchar **strsplit_no_copy(gchar *str, char delimiter); + +void strip_html(gchar *str); + +gchar *my_normalize_url(const gchar *url); + +#endif /* __STRUTILS_H */ -- 2.25.1