talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit 9c88c1282e9576f04c1f7e27ac384aeeb79549eb
parent 2df9692fb1804bffaf617585c16fa830be381a39
Author: Colin Leroy <colin@colino.net>
Date:   Sat, 16 Feb 2013 10:35:15 +0000

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 :)

Diffstat:
MChangeLog | 44++++++++++++++++++++++++++++++++++++++++++++
MPATCHSETS | 1+
Mconfigure.ac | 20++++++++++++++++++--
Msrc/common/defs.h | 1+
Msrc/common/w32_account.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.c | 9+++++++++
Msrc/plugins/Makefile.am | 5+++++
Asrc/plugins/rssyl/Makefile.am | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/claws.def | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/date.c | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/date.h | 10++++++++++
Asrc/plugins/rssyl/feed.c | 1864+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/feed.h | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/feedprops.c | 419+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/feedprops.h | 25+++++++++++++++++++++++++
Asrc/plugins/rssyl/opml.c | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/opml.h | 9+++++++++
Asrc/plugins/rssyl/parsers.c | 534+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/parsers.h | 13+++++++++++++
Dsrc/plugins/rssyl/placeholder.txt | 1-
Asrc/plugins/rssyl/plugin.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/plugin.def | 10++++++++++
Asrc/plugins/rssyl/rssyl.c | 669+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_cb_gtk.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_cb_gtk.h | 19+++++++++++++++++++
Asrc/plugins/rssyl/rssyl_cb_menu.c | 384+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_cb_menu.h | 20++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_gtk.c | 686+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_gtk.h | 32++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_prefs.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/rssyl_prefs.h | 34++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/strreplace.c | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/rssyl/strreplace.h | 8++++++++
Asrc/plugins/rssyl/version.rc | 36++++++++++++++++++++++++++++++++++++
Msrc/plugins/vcalendar/Makefile.am | 2++
Msrc/plugins/vcalendar/Makefile.in | 5+++++
Msrc/plugins/vcalendar/libical/libical/icalversion.h | 2+-
Msrc/plugins/vcalendar/plugin.c | 1-
Msrc/prefs_common.c | 3+++
Msrc/prefs_common.h | 1+
41 files changed, 6259 insertions(+), 5 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,47 @@ +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 diff --git a/PATCHSETS b/PATCHSETS @@ -4567,3 +4567,4 @@ ( 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 diff --git a/configure.ac b/configure.ac @@ -12,7 +12,7 @@ MINOR_VERSION=9 MICRO_VERSION=0 INTERFACE_AGE=0 BINARY_AGE=0 -EXTRA_VERSION=69 +EXTRA_VERSION=70 EXTRA_RELEASE= EXTRA_GTK2_VERSION= @@ -1028,7 +1028,7 @@ if test x"$ac_cv_enable_fancy_plugin" = xyes; then 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], @@ -1045,6 +1045,21 @@ if test x"$ac_cv_enable_vcalendar_plugin" = xyes; then 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], @@ -1141,6 +1156,7 @@ src/plugins/trayicon/Makefile 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 diff --git a/src/common/defs.h b/src/common/defs.h @@ -112,6 +112,7 @@ # 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" diff --git a/src/common/w32_account.c b/src/common/w32_account.c @@ -36,8 +36,11 @@ #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])) @@ -147,3 +150,62 @@ w32_is_administrator (void) 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; +} diff --git a/src/main.c b/src/main.c @@ -1355,6 +1355,15 @@ int main(int argc, char *argv[]) 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(); diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am @@ -22,6 +22,10 @@ if BUILD_VCALENDAR_PLUGIN vcalendar_dir = vcalendar endif +if BUILD_RSSYL_PLUGIN +rssyl_dir = rssyl +endif + if BUILD_TRAYICON_PLUGIN trayicon_dir = trayicon endif @@ -48,5 +52,6 @@ SUBDIRS = $(demo_dir) \ $(pgpcore_dir) \ $(pgpmime_dir) \ $(pgpinline_dir) \ + $(rssyl_dir) \ $(smime_dir) \ $(vcalendar_dir) diff --git a/src/plugins/rssyl/Makefile.am b/src/plugins/rssyl/Makefile.am @@ -0,0 +1,86 @@ +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 + diff --git a/src/plugins/rssyl/claws.def b/src/plugins/rssyl/claws.def @@ -0,0 +1,99 @@ +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 diff --git a/src/plugins/rssyl/date.c b/src/plugins/rssyl/date.c @@ -0,0 +1,167 @@ +/** + * @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); +} diff --git a/src/plugins/rssyl/date.h b/src/plugins/rssyl/date.h @@ -0,0 +1,10 @@ +#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 */ diff --git a/src/plugins/rssyl/feed.c b/src/plugins/rssyl/feed.c @@ -0,0 +1,1864 @@ +/* + * 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[] = { + { "&lt;", "<" }, + { "&gt;", ">" }, + { "&amp;", "&" }, + { "&quot;", "\"" }, + { "&lsquo;", "'" }, + { "&rsquo;", "'" }, + { "&ldquo;", "\"" }, + { "&rdquo;", "\"" }, + { "&nbsp;", " " }, + { "&trade;", "(TM)" }, + { "&#153;", "(TM)" }, + { "&#39;", "'" }, + { "&hellip;", "..." }, + { "&mdash;", "-" }, + { "<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); +} diff --git a/src/plugins/rssyl/feed.h b/src/plugins/rssyl/feed.h @@ -0,0 +1,94 @@ +#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 */ diff --git a/src/plugins/rssyl/feedprops.c b/src/plugins/rssyl/feedprops.c @@ -0,0 +1,419 @@ +/* + * 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); +} diff --git a/src/plugins/rssyl/feedprops.h b/src/plugins/rssyl/feedprops.h @@ -0,0 +1,25 @@ +#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 */ diff --git a/src/plugins/rssyl/opml.c b/src/plugins/rssyl/opml.c @@ -0,0 +1,259 @@ +/* + * 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, "&", "&amp;"); + 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, "&", "&amp;"); + if (ritem->official_name != NULL) + tmpoffn = rssyl_strreplace(item->name, "&", "&amp;"); + 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); +} diff --git a/src/plugins/rssyl/opml.h b/src/plugins/rssyl/opml.h @@ -0,0 +1,9 @@ +#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 */ diff --git a/src/plugins/rssyl/parsers.c b/src/plugins/rssyl/parsers.c @@ -0,0 +1,534 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/parsers.h b/src/plugins/rssyl/parsers.h @@ -0,0 +1,13 @@ +#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 */ diff --git a/src/plugins/rssyl/placeholder.txt b/src/plugins/rssyl/placeholder.txt @@ -1 +0,0 @@ -Placeholder diff --git a/src/plugins/rssyl/plugin.c b/src/plugins/rssyl/plugin.c @@ -0,0 +1,90 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/plugin.def b/src/plugins/rssyl/plugin.def @@ -0,0 +1,10 @@ +EXPORTS + plugin_desc + plugin_done + plugin_init + plugin_licence + plugin_name + plugin_type + plugin_provides + plugin_version + diff --git a/src/plugins/rssyl/rssyl.c b/src/plugins/rssyl/rssyl.c @@ -0,0 +1,669 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/rssyl.h b/src/plugins/rssyl/rssyl.h @@ -0,0 +1,58 @@ +#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 */ diff --git a/src/plugins/rssyl/rssyl_cb_gtk.c b/src/plugins/rssyl/rssyl_cb_gtk.c @@ -0,0 +1,154 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/rssyl_cb_gtk.h b/src/plugins/rssyl/rssyl_cb_gtk.h @@ -0,0 +1,19 @@ +#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 */ diff --git a/src/plugins/rssyl/rssyl_cb_menu.c b/src/plugins/rssyl/rssyl_cb_menu.c @@ -0,0 +1,384 @@ +/* + * 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); +} diff --git a/src/plugins/rssyl/rssyl_cb_menu.h b/src/plugins/rssyl/rssyl_cb_menu.h @@ -0,0 +1,20 @@ +#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 */ diff --git a/src/plugins/rssyl/rssyl_gtk.c b/src/plugins/rssyl/rssyl_gtk.c @@ -0,0 +1,686 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/rssyl_gtk.h b/src/plugins/rssyl/rssyl_gtk.h @@ -0,0 +1,32 @@ +#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 */ diff --git a/src/plugins/rssyl/rssyl_prefs.c b/src/plugins/rssyl/rssyl_prefs.c @@ -0,0 +1,201 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/rssyl_prefs.h b/src/plugins/rssyl/rssyl_prefs.h @@ -0,0 +1,34 @@ +#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 */ diff --git a/src/plugins/rssyl/strreplace.c b/src/plugins/rssyl/strreplace.c @@ -0,0 +1,127 @@ +/* + * 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; +} diff --git a/src/plugins/rssyl/strreplace.h b/src/plugins/rssyl/strreplace.h @@ -0,0 +1,8 @@ +#ifndef __STRREPLACE_H +#define __STRREPLACE_H + +gchar *rssyl_strreplace(const gchar *source, gchar *pattern, + gchar *replacement); +gchar *rssyl_sanitize_string(const gchar *str, gboolean strip_nl); + +#endif /* __STRREPLACE_H */ diff --git a/src/plugins/rssyl/version.rc b/src/plugins/rssyl/version.rc @@ -0,0 +1,36 @@ +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 diff --git a/src/plugins/vcalendar/Makefile.am b/src/plugins/vcalendar/Makefile.am @@ -78,12 +78,14 @@ 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)"\" clean-local: diff --git a/src/plugins/vcalendar/Makefile.in b/src/plugins/vcalendar/Makefile.in @@ -258,6 +258,8 @@ LIBS = @LIBS@ 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@ @@ -296,6 +298,7 @@ PACKAGE_URL = @PACKAGE_URL@ 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@ @@ -434,12 +437,14 @@ vcalendar_la_LDFLAGS = \ 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 diff --git a/src/plugins/vcalendar/libical/libical/icalversion.h b/src/plugins/vcalendar/libical/libical/icalversion.h @@ -2,6 +2,6 @@ #define ICAL_VERSION_H #define ICAL_PACKAGE "claws-mail" -#define ICAL_VERSION "3.9.0cvs64" +#define ICAL_VERSION "3.9.0cvs69" #endif diff --git a/src/plugins/vcalendar/plugin.c b/src/plugins/vcalendar/plugin.c @@ -23,7 +23,6 @@ #include "claws-features.h" #endif -#include <stddef.h> #include <glib.h> #include <glib/gi18n.h> diff --git a/src/prefs_common.c b/src/prefs_common.c @@ -839,6 +839,9 @@ static PrefParam param[] = { #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}, diff --git a/src/prefs_common.h b/src/prefs_common.h @@ -378,6 +378,7 @@ struct _PrefsCommon /* Other */ #ifndef G_OS_WIN32 gchar *uri_cmd; + gchar *gtk_theme; #endif gchar *ext_editor_cmd; gboolean cmds_use_system_default;