+2013-02-16 [colin] 3.9.0cvs70
+
+ * src/main.c
+ * src/prefs_common.c
+ * src/prefs_common.h
+ * src/common/defs.h
+ * src/common/w32_account.c
+ Add an hidden preference to set the Windows theme
+ * src/plugins/Makefile.am
+ * src/plugins/rssyl/Makefile.am
+ * src/plugins/rssyl/claws.def
+ * src/plugins/rssyl/date.c
+ * src/plugins/rssyl/date.h
+ * src/plugins/rssyl/feed.c
+ * src/plugins/rssyl/feed.h
+ * src/plugins/rssyl/feedprops.c
+ * src/plugins/rssyl/feedprops.h
+ * src/plugins/rssyl/opml.c
+ * src/plugins/rssyl/opml.h
+ * src/plugins/rssyl/parsers.c
+ * src/plugins/rssyl/parsers.h
+ * src/plugins/rssyl/placeholder.txt
+ * src/plugins/rssyl/plugin.c
+ * src/plugins/rssyl/plugin.def
+ * src/plugins/rssyl/rssyl.c
+ * src/plugins/rssyl/rssyl.h
+ * src/plugins/rssyl/rssyl_cb_gtk.c
+ * src/plugins/rssyl/rssyl_cb_gtk.h
+ * src/plugins/rssyl/rssyl_cb_menu.c
+ * src/plugins/rssyl/rssyl_cb_menu.h
+ * src/plugins/rssyl/rssyl_gtk.c
+ * src/plugins/rssyl/rssyl_gtk.h
+ * src/plugins/rssyl/rssyl_prefs.c
+ * src/plugins/rssyl/rssyl_prefs.h
+ * src/plugins/rssyl/strreplace.c
+ * src/plugins/rssyl/strreplace.h
+ * src/plugins/rssyl/version.rc
+ Add RSSyl
+ * src/plugins/vcalendar/Makefile.am
+ * src/plugins/vcalendar/Makefile.in
+ * src/plugins/vcalendar/plugin.c
+ * src/plugins/vcalendar/libical/libical/icalversion.h
+ Fix Curl flags :)
+
2013-02-14 [paul] 3.9.0cvs69
* po/id_ID.po
( cvs diff -u -r 1.654.2.4643 -r 1.654.2.4644 configure.ac; ) > 3.9.0cvs67.patchset
( cvs diff -u -r 1.53.2.40 -r 1.53.2.41 po/POTFILES.in; cvs diff -u -r 1.21.2.10 -r 1.21.2.11 po/bg.po; cvs diff -u -r 1.1.2.20 -r 1.1.2.21 po/ca.po; cvs diff -u -r 1.9.2.27 -r 1.9.2.28 po/cs.po; cvs diff -u -r 1.58.2.53 -r 1.58.2.54 po/de.po; cvs diff -u -r 1.12.2.19 -r 1.12.2.20 po/en_GB.po; cvs diff -u -r 1.60.2.67 -r 1.60.2.68 po/es.po; cvs diff -u -r 1.1.2.30 -r 1.1.2.31 po/fi.po; cvs diff -u -r 1.42.2.60 -r 1.42.2.61 po/fr.po; cvs diff -u -r 1.5.2.24 -r 1.5.2.25 po/hu.po; cvs diff -u -r 1.1.2.3 -r 1.1.2.4 po/id_ID.po; cvs diff -u -r 1.34.2.31 -r 1.34.2.32 po/it.po; cvs diff -u -r 1.16.2.13 -r 1.16.2.14 po/ja.po; cvs diff -u -r 1.1.2.6 -r 1.1.2.7 po/lt.po; diff -u /dev/null po/merge-po-from-plugin.sh; cvs diff -u -r 1.28.2.16 -r 1.28.2.17 po/nl.po; cvs diff -u -r 1.10.2.19 -r 1.10.2.20 po/pl.po; cvs diff -u -r 1.50.2.46 -r 1.50.2.47 po/pt_BR.po; cvs diff -u -r 1.1.2.5 -r 1.1.2.6 po/pt_PT.po; cvs diff -u -r 1.17.2.23 -r 1.17.2.24 po/ru.po; cvs diff -u -r 1.2.2.38 -r 1.2.2.39 po/sk.po; cvs diff -u -r 1.17.2.29 -r 1.17.2.30 po/sr.po; cvs diff -u -r 1.4.2.14 -r 1.4.2.15 po/sv.po; cvs diff -u -r 1.1.2.7 -r 1.1.2.8 po/uk.po; cvs diff -u -r 1.5.2.32 -r 1.5.2.33 po/zh_CN.po; cvs diff -u -r 1.1.2.6 -r 1.1.2.7 po/zh_TW.po; ) > 3.9.0cvs68.patchset
( cvs diff -u -r 1.1.2.4 -r 1.1.2.5 po/id_ID.po; ) > 3.9.0cvs69.patchset
+( cvs diff -u -r 1.115.2.267 -r 1.115.2.268 src/main.c; cvs diff -u -r 1.204.2.214 -r 1.204.2.215 src/prefs_common.c; cvs diff -u -r 1.103.2.143 -r 1.103.2.144 src/prefs_common.h; cvs diff -u -r 1.9.2.62 -r 1.9.2.63 src/common/defs.h; cvs diff -u -r 1.1.2.2 -r 1.1.2.3 src/common/w32_account.c; cvs diff -u -r 1.8.2.10 -r 1.8.2.11 src/plugins/Makefile.am; diff -u /dev/null src/plugins/rssyl/Makefile.am; diff -u /dev/null src/plugins/rssyl/claws.def; diff -u /dev/null src/plugins/rssyl/date.c; diff -u /dev/null src/plugins/rssyl/date.h; diff -u /dev/null src/plugins/rssyl/feed.c; diff -u /dev/null src/plugins/rssyl/feed.h; diff -u /dev/null src/plugins/rssyl/feedprops.c; diff -u /dev/null src/plugins/rssyl/feedprops.h; diff -u /dev/null src/plugins/rssyl/opml.c; diff -u /dev/null src/plugins/rssyl/opml.h; diff -u /dev/null src/plugins/rssyl/parsers.c; diff -u /dev/null src/plugins/rssyl/parsers.h; cvs diff -u -r -1.1.2.1 -r -1.1.2.2 src/plugins/rssyl/placeholder.txt; diff -u /dev/null src/plugins/rssyl/plugin.c; diff -u /dev/null src/plugins/rssyl/plugin.def; diff -u /dev/null src/plugins/rssyl/rssyl.c; diff -u /dev/null src/plugins/rssyl/rssyl.h; diff -u /dev/null src/plugins/rssyl/rssyl_cb_gtk.c; diff -u /dev/null src/plugins/rssyl/rssyl_cb_gtk.h; diff -u /dev/null src/plugins/rssyl/rssyl_cb_menu.c; diff -u /dev/null src/plugins/rssyl/rssyl_cb_menu.h; diff -u /dev/null src/plugins/rssyl/rssyl_gtk.c; diff -u /dev/null src/plugins/rssyl/rssyl_gtk.h; diff -u /dev/null src/plugins/rssyl/rssyl_prefs.c; diff -u /dev/null src/plugins/rssyl/rssyl_prefs.h; diff -u /dev/null src/plugins/rssyl/strreplace.c; diff -u /dev/null src/plugins/rssyl/strreplace.h; diff -u /dev/null src/plugins/rssyl/version.rc; cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/plugins/vcalendar/Makefile.am; cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/plugins/vcalendar/Makefile.in; cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/plugins/vcalendar/plugin.c; cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/plugins/vcalendar/libical/libical/icalversion.h; ) > 3.9.0cvs70.patchset
MICRO_VERSION=0
INTERFACE_AGE=0
BINARY_AGE=0
-EXTRA_VERSION=69
+EXTRA_VERSION=70
EXTRA_RELEASE=
EXTRA_GTK2_VERSION=
fi
AM_CONDITIONAL(BUILD_FANCY_PLUGIN, test x"$ac_cv_enable_fancy_plugin" = xyes)
-dnl --- Fancy ---
+dnl --- vCalendar ---
AC_MSG_CHECKING([whether to build vCalendar plugin])
AC_ARG_ENABLE(vcalendar-plugin,
[ --disable-vcalendar-plugin do not build vcalendar plugin for ical rendering],
fi
AM_CONDITIONAL(BUILD_VCALENDAR_PLUGIN, test x"$ac_cv_enable_vcalendar_plugin" = xyes)
+dnl --- RSSyl ---
+AC_MSG_CHECKING([whether to build RSSyl plugin])
+AC_ARG_ENABLE(rssyl-plugin,
+ [ --disable-rssyl-plugin do not build rssyl plugin for RSS and Atom feeds],
+ [ac_cv_enable_rssyl_plugin=$enableval], [ac_cv_enable_rssyl_plugin=yes])
+if test x"$ac_cv_enable_rssyl_plugin" = xyes; then
+ AC_MSG_RESULT(yes)
+ PKG_CHECK_MODULES(LIBXML, "libxml-2.0",,
+ [AC_MSG_ERROR(libxml library is missing)])
+ PLUGINS="rssyl $PLUGINS"
+ AC_SUBST(LIBXML_CFLAGS)
+ AC_SUBST(LIBXML_LIBS)
+fi
+AM_CONDITIONAL(BUILD_RSSYL_PLUGIN, test x"$ac_cv_enable_rssyl_plugin" = xyes)
+
dnl --- Demo ---
AC_ARG_ENABLE(demo-plugin,
[ --enable-demo-plugin build demo plugin],
src/plugins/pgpcore/Makefile
src/plugins/pgpmime/Makefile
src/plugins/pgpinline/Makefile
+src/plugins/rssyl/Makefile
src/plugins/smime/Makefile
src/plugins/vcalendar/Makefile
src/plugins/vcalendar/libical/Makefile
# define ACTIONS_RC "actionswinrc"
# define COMMAND_HISTORY "command_history_win"
# define DEFAULT_SIGNATURE "signature.txt"
+# define DEFAULT_W32_GTK_THEME "MS-Windows"
#else
# define ACTIONS_RC "actionsrc"
# define COMMAND_HISTORY "command_history"
#include <stdlib.h>
#include <string.h>
+#include <windows.h>
+#include <sddl.h> // for ConvertSidToStringSid()
#include "w32lib.h"
+#include "utils.h"
#ifndef DIM
#define DIM(v) (sizeof(v)/sizeof((v)[0]))
return is_admin;
}
+/* Vomit bags are available under your seats */
+int w32_get_uid_equiv(void)
+{
+ HANDLE Thandle;
+ DWORD size;
+ PSID sid;
+ char *sidStr = NULL;
+ char *last_sep = NULL;
+ static int siduid = -1;
+ static BOOL (WINAPI *GetTokenInformation)(
+ HANDLE TokenHandle,
+ TOKEN_INFORMATION_CLASS TokenInformationClass,
+ LPVOID TokenInformation,
+ DWORD TokenInformationLength,
+ PDWORD ReturnLength
+ );
+ static BOOL (WINAPI *ConvertSidToStringSid)(
+ PSID Sid,
+ LPTSTR *StringSid
+ );
+ static int TokenLogonSid = 28 /* according to Google. */;
+
+ if (siduid == -1) {
+ void *handle = LoadLibrary ("advapi32.dll");
+ if (handle) {
+ GetTokenInformation = GetProcAddress (handle, "GetTokenInformation");
+ if (!GetTokenInformation) {
+ FreeLibrary (handle);
+ debug_print("can't resolve GetTokenInformation\n");
+ return -1;
+ }
+ }
+
+ if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &Thandle) == 0
+ && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &Thandle) == 0) {
+ debug_print("OpenProcessToken error %d\n", GetLastError());
+ return -1;
+ }
+
+ if (GetTokenInformation(Thandle, TokenLogonSid, &sid, sizeof(sid), &size) == 0) {
+ debug_print("GetTokenInformation error %d\n", GetLastError());
+ return -1;
+ }
+ if (ConvertSidToStringSid((PSID)sid, &sidStr) == 0) {
+ debug_print("ConvertSidToStringSid error %d\n", GetLastError());
+ return -1;
+ }
+
+ debug_print("SID %s\n", sidStr);
+ last_sep = strrchr(sidStr, '-');
+ if (last_sep != NULL) {
+ last_sep++;
+ siduid = strtol(last_sep, NULL, 10);
+ debug_print("Using UID equivalent %d\n", siduid);
+ }
+ LocalFree(sidStr);
+ }
+ return siduid;
+}
prefs_spelling_init();
#endif
+#ifdef G_OS_WIN32
+ if(strcmp(prefs_common.gtk_theme, DEFAULT_W32_GTK_THEME))
+ gtk_settings_set_string_property(gtk_settings_get_default(),
+ "gtk-theme-name",
+ prefs_common.gtk_theme,
+ "XProperty");
+#endif
+
+
sock_set_io_timeout(prefs_common.io_timeout_secs);
prefs_actions_read_config();
prefs_display_header_read_config();
vcalendar_dir = vcalendar
endif
+if BUILD_RSSYL_PLUGIN
+rssyl_dir = rssyl
+endif
+
if BUILD_TRAYICON_PLUGIN
trayicon_dir = trayicon
endif
$(pgpcore_dir) \
$(pgpmime_dir) \
$(pgpinline_dir) \
+ $(rssyl_dir) \
$(smime_dir) \
$(vcalendar_dir)
--- /dev/null
+EXTRA_DIST = claws.def plugin.def version.rc
+
+if OS_WIN32
+
+LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RC) \
+ `echo $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) | \
+ sed -e 's/-I/--include-dir /g;s/-D/--define /g'`
+
+%.lo : %.rc
+ $(LTRCCOMPILE) -i $< -o $@
+
+plugin_res = version.lo
+plugin_res_ldflag = -Wl,.libs/version.o
+
+export_symbols = -export-symbols $(srcdir)/plugin.def
+
+plugin_deps = libclaws.a $(plugin_res) plugin.def
+
+libclaws.a: claws.def
+ $(DLLTOOL) --output-lib $@ --def $<
+
+plugin_ldadd = -L. -lclaws
+
+else
+plugin_res =
+plugin_res_ldflag =
+export_symbols =
+plugin_deps =
+plugin_ldadd =
+endif
+
+if PLATFORM_WIN32
+no_undefined = -no-undefined
+else
+no_undefined =
+endif
+
+if CYGWIN
+cygwin_export_lib = -L$(top_builddir)/src -lclaws-mail
+else
+cygwin_export_lib =
+endif
+
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = rssyl.la
+
+INCLUDES= \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/common \
+ -I$(top_builddir)/src/common \
+ -I$(top_srcdir)/src/gtk
+
+rssyl_la_LDFLAGS = \
+ $(plugin_res_ldflag) $(no_undefined) $(export_symbols) \
+ -avoid-version -module \
+ $(GTK_LIBS)
+
+rssyl_la_DEPENDENCIES = $(plugin_deps)
+
+rssyl_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \
+ $(GTK_LIBS) $(LIBXML_LIBS) $(CURL_LIBS)
+
+AM_CPPFLAGS = \
+ -Wall \
+ $(CLAWS_MAIL_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GTK_CFLAGS) \
+ $(LIBXML_CFLAGS) \
+ $(CURL_CFLAGS) \
+ -DLOCALEDIR=\""$(localedir)"\"
+
+rssyl_la_SOURCES = \
+ date.c date.h \
+ feed.c feed.h \
+ feedprops.c feedprops.h \
+ opml.c opml.h \
+ parsers.c parsers.h \
+ plugin.c \
+ rssyl.c rssyl.h \
+ rssyl_cb_gtk.c rssyl_cb_gtk.h \
+ rssyl_cb_menu.c rssyl_cb_menu.h \
+ rssyl_gtk.c rssyl_gtk.h \
+ rssyl_prefs.c rssyl_prefs.h \
+ strreplace.c strreplace.h
+
--- /dev/null
+LIBRARY CLAWS-MAIL.EXE
+EXPORTS
+get_locale_dir
+check_plugin_version
+alertpanel_error
+alertpanel_full
+change_dir
+claws_do_idle
+claws_is_exiting
+claws_is_starting
+claws_unlink
+cm_menu_set_sensitive_full
+conv_encode_header_full
+conv_unmime_header
+copy_file
+debug_print_real
+debug_srcname
+dirent_is_regular_file
+file_exist
+filesel_select_file_open_with_filter
+file_strip_crs
+folder_add
+folder_create_folder
+folder_destroy
+folder_find_child_item_by_name
+folder_find_from_path
+folder_func_to_all_folders
+folder_get_class_from_string
+folder_get_xml
+folder_init
+folder_item_add_msg
+folder_item_append
+folder_item_get_identifier
+folder_item_get_msginfo
+folder_item_get_path
+folder_item_new
+folder_item_parent
+folder_item_prefs_save_config_recursive
+folder_item_remove
+folder_item_rename
+folder_item_scan
+folder_item_update_freeze
+folder_item_update_thaw
+folder_local_folder_destroy
+folder_new
+folder_register_class
+folder_scan_tree
+folder_set_xml
+folder_unregister_class
+folderview_get_selected_item
+folderview_register_popup
+folderview_set
+folderview_unregister_popup
+folderview_unselect
+folder_write_list
+get_rc_dir
+get_tmp_file
+gtk_cmctree_get_type
+gtk_cmctree_is_ancestor
+gtk_cmctree_node_get_row_data
+gtkut_widget_draw_now
+gtkut_window_new
+gtkut_widget_set_can_default
+inc_offline_should_override
+input_dialog
+is_dir_exist
+is_file_entry_exist
+log_error
+log_print
+log_warning
+main_window_cursor_normal
+main_window_cursor_wait
+mainwindow_get_mainwindow
+make_dir
+make_dir_hier
+mkstemp
+prefs_common_get_prefs
+prefs_file_close
+prefs_file_close_revert
+prefs_filtering_delete_path
+prefs_gtk_register_page
+prefs_gtk_unregister_page
+prefs_read_config
+prefs_set_block_label
+prefs_set_default
+prefs_toolbar_register_plugin_item
+prefs_toolbar_unregister_plugin_item
+prefs_write_open
+prefs_write_param
+procheader_date_parse
+procheader_parse_file
+procmsg_get_message_file
+procmsg_msginfo_unset_flags
+strtailchomp
+subst_for_shellsafe_filename
+summary_clear_all
+summary_show
+to_number
+trim_string
--- /dev/null
+/**
+ * @file common.c common routines for Liferea
+ *
+ * Copyright (C) 2003-2005 Lars Lindner <lars.lindner@gmx.net>
+ * Copyright (C) 2004,2005 Nathan J. Conrad <t98502@users.sourceforge.net>
+ * Copyright (C) 2004 Karl Soderstrom <ks@xanadunet.net>
+ *
+ * parts of the RFC822 timezone decoding were taken from the gmime
+ * source written by
+ *
+ * Authors: Michael Zucchi <notzed@helixcode.com>
+ * Jeffrey Stedfast <fejj@helixcode.com>
+ *
+ * Copyright 2000 Helix Code, Inc. (www.helixcode.com)
+ *
+ * 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
+
+/* this is needed for strptime() */
+#if !defined (__FreeBSD__)
+#define _XOPEN_SOURCE 600 /* glibc2 needs this */
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#ifdef USE_PTHREAD
+#include <pthread.h>
+#endif
+
+#include <time.h>
+#include <glib.h>
+#include <locale.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#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
+
+ the most specific format: YYYY-MM-DDThh:mm:ss.sTZD
+ */
+
+ /* full specified variant */
+ if(NULL != (pos = strptime((const char *)date, "%t%Y-%m-%dT%H:%M%t", &tm))) {
+ /* Parse seconds */
+ if (*pos == ':')
+ pos++;
+ if (isdigit(pos[0]) && !isdigit(pos[1])) {
+ tm.tm_sec = pos[0] - '0';
+ pos++;
+ } else if (isdigit(pos[0]) && isdigit(pos[1])) {
+ tm.tm_sec = 10*(pos[0]-'0') + pos[1] - '0';
+ pos +=2;
+ }
+ /* Parse timezone */
+ if (*pos == 'Z')
+ offset = 0;
+ else if ((*pos == '+' || *pos == '-') && isdigit(pos[1]) && isdigit(pos[2]) && strlen(pos) >= 3) {
+ offset = (10*(pos[1] - '0') + (pos[2] - '0')) * 60 * 60;
+
+ if (pos[3] == ':' && isdigit(pos[4]) && isdigit(pos[5]))
+ offset += (10*(pos[4] - '0') + (pos[5] - '0')) * 60;
+ else if (isdigit(pos[3]) && isdigit(pos[4]))
+ offset += (10*(pos[3] - '0') + (pos[4] - '0')) * 60;
+
+ offset *= (pos[0] == '+') ? 1 : -1;
+
+ }
+ success = TRUE;
+ /* only 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));
+ t = t - (t2 - t);
+
+ return t;
+ } else {
+ g_warning("internal error! time conversion error! mktime failed!\n");
+ }
+ } else {
+ g_warning("Invalid ISO8601 date format! Ignoring <dc:date> information!\n");
+ }
+
+ return 0;
+}
+
+gchar *dayofweek[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+gchar *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+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 */
+ }
+ 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);
+}
--- /dev/null
+#ifndef __DATE_H
+#define __DATE_H
+
+#include <time.h>
+#include <glib.h>
+
+time_t parseISO8601Date(gchar *date);
+gchar *createRFC822Date(const time_t *time);
+
+#endif /* __DATE_H */
--- /dev/null
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005-2008 Andrej Kacian <andrej@kacian.sk> 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 <glib.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#include <curl/curl.h>
+#include <curl/curlver.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <pthread.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#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;
+};
+
+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
+ 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, 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;
+
+ *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)" },
+ { "'", "'" },
+ { "…", "..." },
+ { "—", "-" },
+ { "<cite>", "\"" },
+ { "</cite>", "\"" },
+ { "<i>", "" },
+ { "</i>", "" },
+ { "<em>", "" },
+ { "</em>", ""},
+ { "<b>", "" },
+ { "</b>", "" },
+ { "<nobr>", "" },
+ { "</nobr>", "" },
+ { "<wbr>", "" },
+ { 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("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=UTF-8\">");
+ } 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("<p>URL: <a href=\"%s\">%s</a></p>\n<br>\n",
+ tmpurl, tmpurl);
+
+ err |= (fprintf(f, "<html><head>"
+ "%s\n"
+ "<base href=\"%s\">\n"
+ "</head><body>\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, "<p><a href=\"%s\">Attached media file</a> [%s] (%s)</p>\n",
+ fitem->media->url, fitem->media->type, tmpid);
+
+ g_free(tmpid);
+ }
+
+ if( fitem->media )
+ err |= (fprintf(f,
+ "<p><a href=\"%s\">Attached media file</a> [%s] (%ld bytes)</p>\n",
+ fitem->media->url, fitem->media->type, fitem->media->size) < 0);
+
+ err |= (fprintf(f, "</body></html>\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, &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, &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, &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);
+}
--- /dev/null
+#ifndef __FEED_H
+#define __FEED_H
+
+#include <libxml/parser.h>
+
+#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 "<!-- RSSyl text start -->"
+#define RSSYL_TEXT_END "<!-- 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, 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 */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - 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 <glib.h>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#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);
+
+ 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;
+
+ 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);
+}
--- /dev/null
+#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"
+
+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 */
--- /dev/null
+/*
+ * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client
+ * This file (C) 2008 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - Export feed list to OPML
+ *
+ * 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 <errno.h>
+#include <glib.h>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#include "log.h"
+#include "folder.h"
+#include "folderview.h"
+
+#include "date.h"
+#include "feed.h"
+#include "rssyl.h"
+#include "strreplace.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 {
+ FILE *f;
+ gint depth;
+};
+
+typedef struct _RSSylOpmlExportCtx RSSylOpmlExportCtx;
+
+static void rssyl_opml_export_func(FolderItem *item, gpointer data)
+{
+ RSSylOpmlExportCtx *ctx = (RSSylOpmlExportCtx *)data;
+ RSSylFolderItem *ritem = (RSSylFolderItem *)item;
+ gboolean isfolder = FALSE, err = FALSE;
+ gboolean haschildren = FALSE;
+ gchar *indent = NULL, *xmlurl = NULL;
+ gchar *tmpoffn = NULL, *tmpurl = NULL, *tmpname = NULL;
+ gint depth;
+
+ if( !IS_RSSYL_FOLDER_ITEM(item) )
+ return;
+
+ if( folder_item_parent(item) == NULL )
+ return;
+
+ /* Check for depth and adjust indentation */
+ depth = _folder_depth(item);
+ if( depth < ctx->depth ) {
+ for( ctx->depth--; depth <= ctx->depth; ctx->depth-- ) {
+ indent = g_strnfill(ctx->depth, '\t');
+ err |= (fprintf(ctx->f, "%s</outline>\n", indent) < 0);
+ g_free(indent);
+ }
+ }
+ ctx->depth = depth;
+
+ if( ritem->url == NULL ) {
+ isfolder = TRUE;
+ } else {
+ tmpurl = rssyl_strreplace(ritem->url, "&", "&");
+ xmlurl = g_strdup_printf("xmlUrl=\"%s\"", tmpurl);
+ g_free(tmpurl);
+ }
+
+ if( g_node_n_children(item->node) )
+ haschildren = TRUE;
+
+ indent = g_strnfill(ctx->depth, '\t');
+
+ tmpname = rssyl_strreplace(item->name, "&", "&");
+ if (ritem->official_name != NULL)
+ tmpoffn = rssyl_strreplace(item->name, "&", "&");
+ else
+ tmpoffn = g_strdup(tmpname);
+
+ err |= (fprintf(ctx->f,
+ "%s<outline title=\"%s\" text=\"%s\" description=\"%s\" type=\"%s\" %s%s>\n",
+ indent, tmpname, tmpoffn, tmpoffn,
+ (isfolder ? "folder" : "rss"),
+ (xmlurl ? xmlurl : ""), (haschildren ? "" : "/")) < 0);
+
+ g_free(indent);
+ g_free(xmlurl);
+ g_free(tmpname);
+ g_free(tmpoffn);
+
+ if( err ) {
+ log_warning(LOG_PROTOCOL,
+ "Error while writing '%s' to feed export list.\n",
+ item->name);
+ debug_print("Error while writing '%s' to feed_export list.\n",
+ item->name);
+ }
+}
+
+void rssyl_opml_export(void)
+{
+ FILE *f;
+ gchar *opmlfile, *tmpdate, *indent;
+ time_t tt = time(NULL);
+ RSSylOpmlExportCtx *ctx = NULL;
+ gboolean err = FALSE;
+
+ opmlfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
+ G_DIR_SEPARATOR_S, RSSYL_OPML_FILE, NULL);
+
+ if( g_file_test(opmlfile, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR ) )
+ g_remove(opmlfile);
+
+ if( (f = g_fopen(opmlfile, "w")) == NULL ) {
+ log_warning(LOG_PROTOCOL,
+ "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");
+ g_free(opmlfile);
+ return;
+ }
+
+ tmpdate = createRFC822Date(&tt);
+
+ /* Write OPML header */
+ err |= (fprintf(f,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<opml version=\"1.1\">\n"
+ "\t<head>\n"
+ "\t\t<title>RSSyl Feed List Export</title>\n"
+ "\t\t<dateCreated>%s</dateCreated>\n"
+ "\t</head>\n"
+ "\t<body>\n",
+ tmpdate) < 0);
+ g_free(tmpdate);
+
+ ctx = g_new0(RSSylOpmlExportCtx, 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</outline>\n", indent) < 0);
+ g_free(indent);
+ }
+
+ err |= (fprintf(f,
+ "\t</body>\n"
+ "</opml>\n") < 0);
+
+ if( err ) {
+ log_warning(LOG_PROTOCOL, "Error during writing feed export file.\n");
+ debug_print("Error during writing feed export file.");
+ }
+
+ debug_print("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);
+}
--- /dev/null
+#ifndef __RSSYL_OPML
+#define __RSSYL_OPML
+
+#include "rssyl.h"
+
+void rssyl_opml_export(void);
+void rssyl_opml_import(const gchar *opmlfile, FolderItem *parent);
+
+#endif /* __RSSYL_OPML */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - 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 <glib.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/HTMLtree.h>
+
+#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") &&
+ (!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")
+ && !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") ) {
+ 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;
+}
--- /dev/null
+#ifndef __PARSERS_H
+#define __PARSERS_H
+
+#include <glib.h>
+#include <libxml/parser.h>
+
+#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 */
+++ /dev/null
-Placeholder
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - generic s-c plugin stuff
+ *
+ * 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 <glib.h>
+#include <glib/gi18n.h>
+
+#include "common/version.h"
+#include "claws.h"
+#include <curl/curl.h>
+
+#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) )
+ return -1;
+
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+ rssyl_init();
+
+ return 0;
+}
+
+gboolean plugin_done(void)
+{
+ rssyl_done();
+ return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+ return PLUGIN_NAME;
+}
+
+const gchar *plugin_desc(void)
+{
+ return _("This plugin allows you to create a mailbox tree where you can add "
+ "newsfeeds in RSS 1.0, RSS 2.0 or Atom format.\n\n"
+ "Each newsfeed will create a folder with appropriate entries, fetched "
+ "from the web. You can read them, and delete or keep old entries.");
+}
+
+const gchar *plugin_type(void)
+{
+ return "GTK2";
+}
+
+const gchar *plugin_licence(void)
+{
+ return "GPL2+";
+}
+
+const gchar *plugin_version(void)
+{
+ return VERSION;
+}
+
+struct PluginFeature *plugin_provides(void)
+{
+ static struct PluginFeature features[] =
+ { {PLUGIN_FOLDERCLASS, N_("RSS feed")},
+ {PLUGIN_NOTHING, NULL}};
+ return features;
+}
--- /dev/null
+EXPORTS
+ plugin_desc
+ plugin_done
+ plugin_init
+ plugin_licence
+ plugin_name
+ plugin_type
+ plugin_provides
+ plugin_version
+
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - s-c folderclass callback handler functions
+ *
+ * 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 <glib.h>
+#include <glib/gi18n.h>
+
+#ifdef G_OS_WIN32
+# include <w32lib.h>
+#endif
+
+#include <glib.h>
+#include <curl/curl.h>
+
+#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"
+#include "rssyl.h"
+#include "rssyl_gtk.h"
+#include "rssyl_prefs.h"
+#include "strreplace.h"
+
+static gint rssyl_create_tree(Folder *folder);
+
+static gboolean existing_tree_found = FALSE;
+
+static void rssyl_init_read_func(FolderItem *item, gpointer data)
+{
+ if( !IS_RSSYL_FOLDER_ITEM(item) )
+ return;
+
+ existing_tree_found = TRUE;
+
+ if( folder_item_parent(item) == NULL )
+ return;
+
+ rssyl_get_feed_props((RSSylFolderItem *)item);
+}
+
+static void rssyl_make_rc_dir(void)
+{
+ gchar *rssyl_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
+ NULL);
+
+ if( !is_dir_exist(rssyl_dir) ) {
+ if( make_dir(rssyl_dir) < 0 ) {
+ g_warning("couldn't create directory %s\n", rssyl_dir);
+ }
+
+ debug_print("created directorty %s\n", rssyl_dir);
+ }
+
+ g_free(rssyl_dir);
+}
+
+static void rssyl_create_default_mailbox(void)
+{
+ Folder *root = NULL;
+ FolderItem *item;
+
+ rssyl_make_rc_dir();
+
+ root = folder_new(rssyl_folder_get_class(), RSSYL_DEFAULT_MAILBOX, NULL);
+
+ g_return_if_fail(root != NULL);
+ folder_add(root);
+
+ item = FOLDER_ITEM(root->node->data);
+
+ rssyl_subscribe_new_feed(item, RSSYL_DEFAULT_FEED, TRUE);
+}
+
+static gboolean rssyl_refresh_all_feeds_deferred(gpointer data)
+{
+ rssyl_refresh_all_feeds();
+ return FALSE;
+}
+
+static void rssyl_toolbar_cb_refresh_all(gpointer parent, const gchar *item_name, gpointer data)
+{
+ rssyl_refresh_all_feeds();
+}
+
+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 )
+ rssyl_create_default_mailbox();
+
+ prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "RSSyl",
+ _("Refresh all feeds"), rssyl_toolbar_cb_refresh_all, NULL);
+
+ rssyl_opml_export();
+
+ if( rssyl_prefs_get()->refresh_on_startup &&
+ claws_is_starting() )
+ g_timeout_add(2000, rssyl_refresh_all_feeds_deferred, NULL);
+}
+
+void rssyl_done(void)
+{
+ prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "RSSyl",
+ _("Refresh all feeds"));
+ rssyl_prefs_done();
+ rssyl_gtk_done();
+ if (!claws_is_exiting())
+ folder_unregister_class(rssyl_folder_get_class());
+}
+
+static gchar *rssyl_get_new_msg_filename(FolderItem *dest)
+{
+ gchar *destfile;
+ gchar *destpath;
+
+ destpath = folder_item_get_path(dest);
+ g_return_val_if_fail(destpath != NULL, NULL);
+
+ if( !is_dir_exist(destpath) )
+ make_dir_hier(destpath);
+
+ for( ; ; ) {
+ destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
+ dest->last_num + 1);
+ if( is_file_entry_exist(destfile) ) {
+ dest->last_num++;
+ g_free(destfile);
+ } else
+ break;
+ }
+
+ g_free(destpath);
+
+ return destfile;
+}
+
+static void rssyl_get_last_num(Folder *folder, FolderItem *item)
+{
+ gchar *path;
+ DIR *dp;
+ struct dirent *d;
+ gint max = 0;
+ gint num;
+
+ g_return_if_fail(item != NULL);
+
+ 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 ) {
+ FILE_OP_ERROR(item->path, "opendir");
+ return;
+ }
+
+ while( (d = readdir(dp)) != NULL ) {
+ if( (num = to_number(d->d_name)) > 0 && dirent_is_regular_file(d) ) {
+ if( max < num )
+ max = num;
+ }
+ }
+ closedir(dp);
+
+ debug_print("Last number in dir %s = %d\n", item->path, max);
+ 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;
+
+ debug_print("RSSyl: new_folder\n");
+
+ rssyl_make_rc_dir();
+
+ folder = g_new0(RSSylFolder, 1);
+ FOLDER(folder)->klass = &rssyl_class;
+ folder_init(FOLDER(folder), name);
+
+ return FOLDER(folder);
+}
+
+static void rssyl_destroy_folder(Folder *_folder)
+{
+ RSSylFolder *folder = (RSSylFolder *)_folder;
+
+ folder_local_folder_destroy(LOCAL_FOLDER(folder));
+}
+
+static gint rssyl_scan_tree(Folder *folder)
+{
+ g_return_val_if_fail(folder != NULL, -1);
+
+ folder->outbox = NULL;
+ folder->draft = NULL;
+ folder->queue = NULL;
+ folder->trash = NULL;
+
+ debug_print("RSSyl: scanning tree\n");
+ rssyl_create_tree(folder);
+
+ return 0;
+}
+
+static gint rssyl_create_tree(Folder *folder)
+{
+ FolderItem *rootitem;
+ GNode *rootnode;
+
+ rssyl_make_rc_dir();
+
+ if( !folder->node ) {
+ rootitem = folder_item_new(folder, folder->name, NULL);
+ rootitem->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");
+ return 0;
+}
+
+static FolderItem *rssyl_item_new(Folder *folder)
+{
+ RSSylFolderItem *ritem;
+
+ debug_print("RSSyl: item_new\n");
+
+ ritem = g_new0(RSSylFolderItem, 1);
+
+ ritem->url = NULL;
+ ritem->default_refresh_interval = TRUE;
+ ritem->default_expired_num = TRUE;
+ ritem->fetch_comments = FALSE;
+ ritem->fetch_comments_for = -1;
+ 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->contents = NULL;
+ ritem->feedprop = NULL;
+
+ return (FolderItem *)ritem;
+}
+
+static void rssyl_item_destroy(Folder *folder, FolderItem *item)
+{
+ RSSylFolderItem *ritem = (RSSylFolderItem *)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(item);
+}
+
+static FolderItem *rssyl_create_folder(Folder *folder,
+ FolderItem *parent, const gchar *name)
+{
+ gchar *path = NULL, *tmp;
+ 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);
+ g_free(path);
+
+ return newitem;
+}
+
+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;
+}
+
+static gint rssyl_rename_folder(Folder *folder, FolderItem *item,
+ const gchar *name)
+{
+ gchar *oldname = NULL, *oldpath = NULL, *newpath = NULL;
+ RSSylFolderItem *ritem = 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);
+ g_return_val_if_fail(name != NULL, -1);
+
+ debug_print("RSSyl: renaming folder '%s' to '%s'\n", item->path, 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) {
+ 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(item->name);
+ item->name = g_strdup(name);
+
+ folder_write_list();
+
+ return 0;
+}
+
+static gint rssyl_remove_folder(Folder *folder, FolderItem *item)
+{
+ 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(item->stype == F_NORMAL, -1);
+
+ debug_print("RSSyl: removing folder item %s\n", item->path);
+
+ folder_item_remove(item);
+
+ return 0;
+}
+
+static gint rssyl_get_num_list(Folder *folder, FolderItem *item,
+ MsgNumberList **list, gboolean *old_uids_valid)
+{
+ gchar *path;
+ 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;
+
+ *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 ) {
+ FILE_OP_ERROR(item->path, "opendir");
+ return -1;
+ }
+
+ 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);
+
+ return nummsgs;
+}
+
+static gboolean rssyl_scan_required(Folder *folder, FolderItem *item)
+{
+ return TRUE;
+}
+
+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);
+
+ g_free(snum);
+
+ return file;
+}
+
+static MsgInfo *rssyl_get_msginfo(Folder *folder, FolderItem *item, gint num)
+{
+ MsgInfo *msginfo = NULL;
+ 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);
+
+ file = rssyl_fetch_msg(folder, item, num);
+ g_return_val_if_fail(file != NULL, NULL);
+
+ flags.perm_flags = MSG_NEW | MSG_UNREAD;
+ flags.tmp_flags = 0;
+
+ msginfo = rssyl_parse_feed_item_to_msginfo(file, flags, TRUE, TRUE, item);
+
+ if( msginfo )
+ msginfo->msgnum = num;
+
+ g_free(file);
+
+ return msginfo;
+}
+
+static gint rssyl_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
+ GHashTable *relation)
+{
+ gchar *destfile;
+ GSList *cur;
+ MsgFileInfo *fileinfo;
+
+ g_return_val_if_fail(dest != NULL, -1);
+ g_return_val_if_fail(file_list != NULL, -1);
+
+ if( dest->last_num < 0 ) {
+ rssyl_get_last_num(folder, dest);
+ if( dest->last_num < 0 ) return -1;
+ }
+
+ for( cur = file_list; cur != NULL; cur = cur->next ) {
+ fileinfo = (MsgFileInfo *)cur->data;
+
+ destfile = rssyl_get_new_msg_filename(dest);
+ g_return_val_if_fail(destfile != NULL, -1);
+
+#ifdef G_OS_UNIX
+ if( link(fileinfo->file, destfile) < 0 )
+#endif
+ 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_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;
+
+ g_return_val_if_fail(file != NULL, -1);
+
+ fileinfo.msginfo = NULL;
+ fileinfo.file = (gchar *)file;
+ fileinfo.flags = flags;
+ file_list.data = &fileinfo;
+ file_list.next = NULL;
+
+ ret = rssyl_add_msgs(folder, dest, &file_list, NULL);
+ return ret;
+}
+
+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;
+ gchar *file, *tmp;
+
+ g_return_val_if_fail(item != NULL, -1);
+
+ file = rssyl_fetch_msg(folder, item, num);
+ g_return_val_if_fail(file != NULL, -1);
+
+ need_scan = rssyl_scan_required(folder, item);
+
+ /* are we doing a folder move ? */
+ tmp = g_strdup_printf("%s.tmp", file);
+ if (is_file_exist(tmp)) {
+ claws_unlink(tmp);
+ g_free(tmp);
+ g_free(file);
+ return 0;
+ }
+ g_free(tmp);
+ if( claws_unlink(file) < 0 ) {
+ FILE_OP_ERROR(file, "unlink");
+ g_free(file);
+ return -1;
+ }
+
+ if( !need_scan )
+ item->mtime = time(NULL);
+
+ g_free(file);
+ return 0;
+}
+
+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;
+}
+
+/************************************************************************/
+
+FolderClass *rssyl_folder_get_class()
+{
+ if( rssyl_class.idstr == NULL ) {
+ rssyl_class.type = F_UNKNOWN;
+ rssyl_class.idstr = "rssyl";
+ rssyl_class.uistr = "RSSyl";
+
+ /* Folder functions */
+ rssyl_class.new_folder = rssyl_new_folder;
+ rssyl_class.destroy_folder = rssyl_destroy_folder;
+ rssyl_class.set_xml = folder_set_xml;
+ rssyl_class.get_xml = folder_get_xml;
+ rssyl_class.scan_tree = rssyl_scan_tree;
+ rssyl_class.create_tree = rssyl_create_tree;
+
+ /* FolderItem functions */
+ rssyl_class.item_new = rssyl_item_new;
+ rssyl_class.item_destroy = rssyl_item_destroy;
+ rssyl_class.item_get_path = rssyl_item_get_path;
+ rssyl_class.create_folder = rssyl_create_folder;
+ rssyl_class.rename_folder = rssyl_rename_folder;
+ rssyl_class.remove_folder = rssyl_remove_folder;
+ rssyl_class.get_num_list = rssyl_get_num_list;
+ rssyl_class.scan_required = rssyl_scan_required;
+
+ /* 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.add_msg = rssyl_add_msg;
+ rssyl_class.add_msgs = rssyl_add_msgs;
+ rssyl_class.remove_msg = rssyl_remove_msg;
+ rssyl_class.remove_msgs = NULL;
+// rssyl_class.change_flags = rssyl_change_flags;
+ rssyl_class.change_flags = NULL;
+ rssyl_class.subscribe = rssyl_subscribe_uri;
+ debug_print("RSSyl: registered folderclass\n");
+ }
+
+ return &rssyl_class;
+}
--- /dev/null
+#ifndef __RSSYL_H
+#define __RSSYL_H
+
+#include <glib.h>
+
+#include <folder.h>
+
+#define PLUGIN_NAME (_("RSSyl"))
+
+/* Name of directory in rcdir where RSSyl will store its data. */
+#define RSSYL_DIR "RSSyl"
+
+/* Default RSSyl mailbox name */
+#define RSSYL_DEFAULT_MAILBOX _("My Feeds")
+
+/* 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;
+
+ gchar *url;
+ gchar *official_name;
+
+ 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 silent_update;
+
+ struct _RSSylFeedProp *feedprop;
+};
+
+typedef struct _RSSylFolderItem RSSylFolderItem;
+
+struct _RSSylRefreshCtx {
+ RSSylFolderItem *ritem;
+ guint id;
+};
+
+typedef struct _RSSylRefreshCtx RSSylRefreshCtx;
+
+void rssyl_init(void);
+void rssyl_done(void);
+
+FolderClass *rssyl_folder_get_class(void);
+
+#define IS_RSSYL_FOLDER_ITEM(item) \
+ (item->folder->klass == rssyl_folder_get_class())
+
+#endif /* __RSSYL_H */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - 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 <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#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;
+}
--- /dev/null
+#ifndef __RSSYL_GTK_CB
+#define __RSSYL_GTK_CB
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+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 */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - callback handler functions for folderview rssyl context menu 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"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#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"
+#include "rssyl_gtk.h"
+
+void rssyl_new_feed_cb(GtkAction *action, gpointer data)
+{
+ FolderView *folderview = (FolderView *)data;
+ GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree);
+ FolderItem *item;
+ gchar *new_feed;
+
+ debug_print("RSSyl: new_feed_cb\n");
+
+ 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);
+
+ new_feed = input_dialog(_("Subscribe feed"),
+ _("Input the URL of the news feed you wish to subscribe:"),
+ "");
+ g_return_if_fail(new_feed != NULL);
+
+ rssyl_subscribe_new_feed(item, new_feed, TRUE);
+
+ g_free(new_feed);
+}
+
+void rssyl_new_folder_cb(GtkAction *action, gpointer 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;
+
+ if (!folderview->selected) return;
+
+ item = gtk_cmctree_node_get_row_data(ctree, folderview->selected);
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+
+ new_folder = input_dialog(_("New folder"),
+ _("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."),
+ G_DIR_SEPARATOR);
+ 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;
+ }
+
+ 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);
+ 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;
+ }
+
+ folder_write_list();
+}
+
+void rssyl_remove_folder_cb(GtkAction *action, gpointer data)
+{
+ FolderView *folderview = (FolderView *)data;
+ GtkCMCTree *ctree = GTK_CMCTREE(folderview->ctree);
+ FolderItem *item;
+ gchar *message, *name;
+ AlertValue avalue;
+ gchar *old_path;
+ gchar *old_id;
+
+ 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);
+ AUTORELEASE_STR(name, {g_free(name); return;});
+ message = g_strdup_printf
+ (_("All folders and messages under '%s' will be permanently deleted. "
+ "Recovery will not be possible.\n\n"
+ "Do you really want to delete?"), name);
+ avalue = alertpanel_full(_("Delete folder"), message,
+ GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL, FALSE,
+ NULL, ALERT_WARNING, G_ALERTDEFAULT);
+ g_free(message);
+ if (avalue != G_ALERTALTERNATE) return;
+
+ Xstrdup_a(old_path, item->path, return);
+ old_id = folder_item_get_identifier(item);
+
+ if (folderview->opened == folderview->selected ||
+ gtk_cmctree_is_ancestor(ctree,
+ folderview->selected,
+ folderview->opened)) {
+ summary_clear_all(folderview->summaryview);
+ folderview->opened = NULL;
+ }
+
+ if (item->folder->klass->remove_folder(item->folder, item) < 0) {
+ folder_item_scan(item);
+ alertpanel_error(_("Can't remove the folder '%s'."), name);
+ g_free(old_id);
+ return;
+ }
+
+ folder_write_list();
+
+ prefs_filtering_delete_path(old_id);
+ g_free(old_id);
+
+}
+
+void rssyl_refresh_cb(GtkAction *action, gpointer data)
+{
+ FolderView *folderview = (FolderView *)data;
+ FolderItem *item;
+ RSSylFolderItem *ritem;
+
+ item = folderview_get_selected_item(folderview);
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+
+ ritem = (RSSylFolderItem *)item;
+
+ 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;
+ }
+
+ main_window_cursor_wait(mainwindow_get_mainwindow());
+ rssyl_update_feed(ritem);
+ main_window_cursor_normal(mainwindow_get_mainwindow());
+}
+
+void rssyl_refresh_all_cb(GtkAction *action, gpointer data)
+{
+ main_window_cursor_wait(mainwindow_get_mainwindow());
+ rssyl_refresh_all_feeds();
+ main_window_cursor_normal(mainwindow_get_mainwindow());
+}
+
+void rssyl_prop_cb(GtkAction *action, gpointer data)
+{
+ FolderView *folderview = (FolderView *)data;
+ FolderItem *item;
+ RSSylFolderItem *ritem;
+
+ item = folderview_get_selected_item(folderview);
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->folder != NULL);
+
+ debug_print("RSSyl: rssyl_prop_cb() for '%s'\n", item->name);
+
+ ritem = (RSSylFolderItem *)item;
+ rssyl_get_feed_props(ritem);
+ rssyl_gtk_prop(ritem);
+}
+
+void rssyl_rename_cb(GtkAction *action, gpointer data)
+{
+ FolderView *folderview = (FolderView *)data;
+ FolderItem *item;
+ gchar *new_folder;
+ gchar *name;
+ gchar *message;
+
+ 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;});
+
+ if (strchr(new_folder, G_DIR_SEPARATOR) != NULL) {
+ alertpanel_error(_("'%c' can't be included in folder name."),
+ G_DIR_SEPARATOR);
+ 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;
+ }
+
+ 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_recursive(item);
+ folder_write_list();
+}
+
+void rssyl_import_feed_list_cb(GtkAction *action, gpointer data)
+{
+ FolderView *folderview = (FolderView *)data;
+ debug_print("RSSyl: rssyl_import_feed_cb\n");
+
+ FolderItem *item;
+ gchar *opmlfile = NULL;
+
+ item = folderview_get_selected_item(folderview);
+ 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;
+ }
+
+ rssyl_opml_import(opmlfile, item);
+}
--- /dev/null
+#ifndef __RSSYL_MENU_CB
+#define __RSSYL_MENU_CB
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#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);
+
+#endif /* __RSSYL_MENU_FEED */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - GUI handling functions
+ *
+ * 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 <glib.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#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"
+
+#include "feed.h"
+#include "feedprops.h"
+#include "rssyl.h"
+#include "rssyl_cb_gtk.h"
+#include "rssyl_cb_menu.h"
+#include "rssyl_gtk.h"
+#include "rssyl_prefs.h"
+
+static char *rssyl_popup_menu_labels[] =
+{
+ N_("_Refresh feed"),
+ N_("Refresh _all feeds"),
+ N_("Subscribe _new feed..."),
+ N_("_Unsubscribe feed..."),
+ N_("Feed pr_operties..."),
+ N_("Import feed list..."),
+ N_("Rena_me..."),
+ N_("_Create new folder..."),
+ N_("_Delete folder..."),
+ N_("Remove folder _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) },
+
+};
+
+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", "NewFolder", "FolderViewPopup/NewFolder", GTK_UI_MANAGER_MENUITEM)
+ 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", "RemoveMailbox", "FolderViewPopup/RemoveMailbox", GTK_UI_MANAGER_MENUITEM)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorRSS5", "FolderViewPopup/---", 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 );
+ SET_SENS("FolderViewPopup/FeedProperties", folder_item_parent(item) != NULL && ritem->url );
+ SET_SENS("FolderViewPopup/RenameFolder", folder_item_parent(item) != NULL );
+ 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 );
+
+#undef SET_SENS
+}
+
+static FolderViewPopup rssyl_popup =
+{
+ "rssyl",
+ "<rssyl>",
+ rssyl_popup_entries,
+ G_N_ELEMENTS(rssyl_popup_entries),
+ NULL, 0,
+ NULL, 0, 0, NULL,
+ rssyl_add_menuitems,
+ 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;
+ Folder *folder;
+
+ path = input_dialog(_("Add RSS folder tree"),
+ _("Enter name for a new RSS folder tree."),
+ RSSYL_DEFAULT_MAILBOX);
+ if( !path ) return;
+
+ if( folder_find_from_path(path) ) {
+ alertpanel_error(_("The mailbox '%s' already exists."), path);
+ g_free(path);
+ return;
+ }
+
+ base = g_path_get_basename(path);
+ folder = folder_new(folder_get_class_from_string("rssyl"),
+ base, path);
+ g_free(base);
+
+ if( folder->klass->create_tree(folder) < 0 ) {
+ alertpanel_error(_("Creation of folder tree failed.\n"
+ "Maybe some files already exist, or you don't have the permission "
+ "to write there?"));
+ folder_destroy(folder);
+ return;
+ }
+
+ folder_add(folder);
+ folder_scan_tree(folder, TRUE);
+
+ folderview_set(mainwin->folderview);
+}
+
+static GtkActionEntry mainwindow_add_mailbox[] = {{
+ "File/AddMailbox/RSSyl",
+ NULL, N_("RSSyl..."), NULL, NULL, G_CALLBACK(rssyl_add_mailbox)
+}};
+
+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)
+
+
+ rssyl_fill_popup_menu_labels();
+ folderview_register_popup(&rssyl_popup);
+}
+
+void rssyl_gtk_done(void)
+{
+ MainWindow *mainwin = mainwindow_get_mainwindow();
+ FolderView *folderview = NULL;
+ FolderItem *fitem = NULL;
+
+ if (mainwin == NULL || claws_is_exiting())
+ return;
+
+ folderview = mainwin->folderview;
+ fitem = folderview->summaryview->folder_item;
+
+ if( fitem && IS_RSSYL_FOLDER_ITEM(fitem) ) {
+ folderview_unselect(folderview);
+ summary_clear_all(folderview->summaryview);
+ }
+
+ folderview_unregister_popup(&rssyl_popup);
+
+ 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, expired;
+ 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);
+
+ if( ritem->default_expired_num )
+ expired = rssyl_prefs_get()->expired;
+ else
+ expired = ritem->expired_num;
+
+ /* 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(_("<b>Source URL:</b>"));
+ 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(_("<b>Fetch comments on posts aged less than:</b>\n"
+ "<small>(In days; set to -1 to fetch all comments)"
+ "</small>"));
+ 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(_("<b>Refresh interval in minutes:</b>\n"
+ "<small>(Set to 0 to disable automatic refreshing for this feed)"
+ "</small>"));
+ 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(_("<b>Number of expired entries to keep:"
+ "</b>\n<small>(Set to -1 if you want to keep expired entries)"
+ "</small>"));
+ 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(_("<b>If an item changes, do not mark it as unread:</b>"));
+ 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 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);
+
+ /* 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;
+
+ 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("<span size='x-large'><b>%s</b></span>"
+ "\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;
+}
--- /dev/null
+#ifndef __RSSYL_GTK
+#define __RSSYL_GTK
+
+#include <gtk/gtk.h>
+
+#include <folder.h>
+
+#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;
+};
+
+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);
+
+#endif /* __RSSYL_GTK */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2004 Hiroyuki Yamamoto
+ * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - 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"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "common/defs.h"
+#include "common/utils.h"
+#include "gtk/gtkutils.h"
+#include "prefs_gtk.h"
+
+#include "rssyl_prefs.h"
+
+static RSSylPrefsPage rssyl_prefs_page;
+RSSylPrefs 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 },
+ { 0, 0, 0, 0, 0, 0, 0 }
+};
+
+void rssyl_prefs_init(void)
+{
+ static gchar *path[3];
+ gchar *rcpath;
+
+ path[0] = _("Plugins");
+ path[1] = "RSSyl"; /* We don't need this translated */
+ path[2] = NULL;
+
+ prefs_set_default(param);
+ rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+ 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;
+
+ prefs_gtk_register_page((PrefsPage *) &rssyl_prefs_page);
+}
+
+void rssyl_prefs_done(void)
+{
+ prefs_gtk_unregister_page((PrefsPage *) &rssyl_prefs_page);
+}
+
+static void create_rssyl_prefs_page(PrefsPage *page,
+ GtkWindow *window, gpointer data)
+{
+ RSSylPrefsPage *prefs_page = (RSSylPrefsPage *) page;
+ GtkWidget *table;
+ GtkWidget *refresh;
+ GtkWidget *expired;
+ GtkWidget *refresh_on_startup;
+ GtkWidget *cookies_path;
+ GtkWidget *label;
+ GtkObject *refresh_adj, *expired_adj;
+
+ table = gtk_table_new(RSSYL_NUM_PREFS, 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_adj = gtk_adjustment_new(rssyl_prefs.refresh,
+ 0, 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_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"));
+
+ 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_FILL | GTK_EXPAND, 0, 0, 0);
+
+ 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_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+ 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,
+ _("Path to Netscape-style cookies.txt file containing your cookies"));
+
+ gtk_widget_show_all(table);
+
+ prefs_page->page.widget = table;
+ prefs_page->refresh = refresh;
+ prefs_page->expired = expired;
+ prefs_page->refresh_on_startup = refresh_on_startup;
+ prefs_page->cookies_path = cookies_path;
+}
+
+static void destroy_rssyl_prefs_page(PrefsPage *page)
+{
+ /* Do nothing! */
+}
+
+static void save_rssyl_prefs(PrefsPage *page)
+{
+ RSSylPrefsPage *prefs_page = (RSSylPrefsPage *)page;
+ PrefFile *pref_file;
+ gchar *rc_file_path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+ COMMON_RC, NULL);
+
+ 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)));
+
+ pref_file = prefs_write_open(rc_file_path);
+ g_free(rc_file_path);
+
+ if( !pref_file || prefs_set_block_label(pref_file, PREFS_BLOCK_NAME) < 0 )
+ return;
+
+ if( prefs_write_param(param, pref_file->fp) < 0 ) {
+ g_warning("Failed to write RSSyl plugin configuration\n");
+ prefs_file_close_revert(pref_file);
+ 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);
+}
+
+RSSylPrefs *rssyl_prefs_get(void)
+{
+ return &rssyl_prefs;
+}
--- /dev/null
+#ifndef __RSSYL_PREFS
+#define __RSSYL_PREFS
+
+#define PREFS_BLOCK_NAME "rssyl"
+
+#define RSSYL_NUM_PREFS 4
+
+#define RSSYL_PREF_DEFAULT_REFRESH "180"
+#define RSSYL_PREF_DEFAULT_EXPIRED "-1"
+
+typedef struct _RSSylPrefs RSSylPrefs;
+
+struct _RSSylPrefs {
+ gint refresh;
+ gint expired;
+ gboolean refresh_on_startup;
+ gchar *cookies_path;
+};
+
+typedef struct _RSSylPrefsPage RSSylPrefsPage;
+
+struct _RSSylPrefsPage {
+ PrefsPage page;
+ GtkWidget *refresh;
+ GtkWidget *expired;
+ GtkWidget *refresh_on_startup;
+ GtkWidget *cookies_path;
+};
+
+void rssyl_prefs_init(void);
+void rssyl_prefs_done(void);
+RSSylPrefs *rssyl_prefs_get(void);
+
+#endif /* __RSSYL_PREFS */
--- /dev/null
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2005 Andrej Kacian <andrej@kacian.sk>
+ *
+ * - 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 <glib.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#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;
+}
--- /dev/null
+#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 */
--- /dev/null
+1 VERSIONINFO
+ FILEVERSION 0, 0, 0, 0
+ PRODUCTVERSION 0, 0, 0, 0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "000004b0"
+ BEGIN
+ VALUE "FileDescription", "RSSyl Plugin\0"
+ VALUE "FileVersion", "0.0.0.0\0"
+ VALUE "ProductVersion", "0.0.0.0 Win32\0"
+ VALUE "LegalCopyright", "GPL / © 1999-2008 Hiroyuki Yamamoto & The Claws Mail Team\0"
+ VALUE "CompanyName", "GNU / Free Software Foundation\0"
+ VALUE "ProductName", "Claws Mail\0"
+// VALUE "Comments", "\0"
+// VALUE "InternalName", "\0"
+// VALUE "LegalTrademarks", "\0"
+// VALUE "OriginalFilename", "\0"
+// VALUE "PrivateBuild", "\0"
+// VALUE "SpecialBuild", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0, 1200
+ END
+END
vcalendar_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \
$(GTK_LIBS) \
+ $(CURL_LIBS) \
$(top_builddir)/src/plugins/vcalendar/libical/libical/libical.la
AM_CPPFLAGS = \
$(CLAWS_MAIL_CFLAGS) \
$(GLIB_CFLAGS) \
$(GTK_CFLAGS) \
+ $(CURL_CFLAGS) \
-DLOCALEDIR=\""$(localedir)"\"
clean-local:
LIBSOUP_GNOME_CFLAGS = @LIBSOUP_GNOME_CFLAGS@
LIBSOUP_GNOME_LIBS = @LIBSOUP_GNOME_LIBS@
LIBTOOL = @LIBTOOL@
+LIBXML_CFLAGS = @LIBXML_CFLAGS@
+LIBXML_LIBS = @LIBXML_LIBS@
LIPO = @LIPO@
LN_S = @LN_S@
LTLIBICONV = @LTLIBICONV@
PACKAGE_VERSION = @PACKAGE_VERSION@
PASSCRYPT_KEY = @PASSCRYPT_KEY@
PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
PKG_CONFIG = @PKG_CONFIG@
PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
vcalendar_la_DEPENDENCIES = $(plugin_deps)
vcalendar_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \
$(GTK_LIBS) \
+ $(CURL_LIBS) \
$(top_builddir)/src/plugins/vcalendar/libical/libical/libical.la
AM_CPPFLAGS = \
$(CLAWS_MAIL_CFLAGS) \
$(GLIB_CFLAGS) \
$(GTK_CFLAGS) \
+ $(CURL_CFLAGS) \
-DLOCALEDIR=\""$(localedir)"\"
all: all-recursive
#define ICAL_VERSION_H
#define ICAL_PACKAGE "claws-mail"
-#define ICAL_VERSION "3.9.0cvs64"
+#define ICAL_VERSION "3.9.0cvs69"
#endif
#include "claws-features.h"
#endif
-#include <stddef.h>
#include <glib.h>
#include <glib/gi18n.h>
#ifndef G_OS_WIN32
{"uri_open_command", DEFAULT_BROWSER_CMD,
&SPECIFIC_PREFS.uri_cmd, P_STRING, NULL, NULL, NULL},
+#else
+ {"gtk_theme", DEFAULT_W32_GTK_THEME,
+ &SPECIFIC_PREFS.gtk_theme, P_STRING, NULL, NULL, NULL},
#endif
{"ext_editor_command", DEFAULT_EDITOR_CMD,
&SPECIFIC_PREFS.ext_editor_cmd, P_STRING, NULL, NULL, NULL},
/* Other */
#ifndef G_OS_WIN32
gchar *uri_cmd;
+ gchar *gtk_theme;
#endif
gchar *ext_editor_cmd;
gboolean cmds_use_system_default;