New libravatar plugin
authorRicardo Mones <ricardo@mones.org>
Mon, 10 Mar 2014 23:33:43 +0000 (00:33 +0100)
committerRicardo Mones <ricardo@mones.org>
Mon, 10 Mar 2014 23:33:43 +0000 (00:33 +0100)
14 files changed:
configure.ac
src/plugins/Makefile.am
src/plugins/libravatar/Makefile.am [new file with mode: 0644]
src/plugins/libravatar/README [new file with mode: 0644]
src/plugins/libravatar/TODO [new file with mode: 0644]
src/plugins/libravatar/claws.def [new file with mode: 0644]
src/plugins/libravatar/libravatar.c [new file with mode: 0644]
src/plugins/libravatar/libravatar.h [new file with mode: 0644]
src/plugins/libravatar/libravatar_missing.c [new file with mode: 0644]
src/plugins/libravatar/libravatar_missing.h [new file with mode: 0644]
src/plugins/libravatar/libravatar_prefs.c [new file with mode: 0644]
src/plugins/libravatar/libravatar_prefs.h [new file with mode: 0644]
src/plugins/libravatar/plugin.def [new file with mode: 0644]
src/plugins/libravatar/version.rc [new file with mode: 0644]

index 9bbd9aec7bd53f4cc57732e3c7d6f4d904d768a6..474971f93f546474a2bebe2ac75c7ba6124c5e56 100644 (file)
@@ -964,6 +964,10 @@ AC_ARG_ENABLE(geolocation-plugin,
                [  --disable-geolocation-plugin    Do not build geolocation plugin],
                [enable_geolocation_plugin=$enableval], [enable_geolocation_plugin=auto])
 
+AC_ARG_ENABLE(libravatar-plugin,
+               [  --disable-libravatar-plugin     Do not build libravatar  plugin],
+               [enable_libravatar_plugin=$enableval], [enable_libravatar_plugin=auto])
+
 AC_ARG_ENABLE(mailmbox-plugin,
                [  --disable-mailmbox-plugin       Do not build mailmbox plugin],
                [enable_mailmbox_plugin=$enableval], [enable_mailmbox_plugin=auto])
@@ -1470,6 +1474,31 @@ else
        AC_MSG_RESULT(no)
 fi
 
+AC_MSG_CHECKING([whether to build libravatar plugin])
+if test x"$enable_libravatar_plugin" != xno; then
+       dependencies_missing=""
+
+       if test x"$HAVE_CURL" = xno; then
+               dependencies_missing="libcurl $dependencies_missing"
+       fi
+
+       if test x"$dependencies_missing" = x; then
+               PLUGINS="$PLUGINS libravatar"
+               AC_MSG_RESULT(yes)
+       elif test x"$enable_libravatar_plugin" = xauto; then
+               AC_MSG_RESULT(no)
+               AC_MSG_WARN("Plugin libravatar will not be built; missing $dependencies_missing")
+               enable_rssyl_plugin=no
+               MISSING_DEPS_PLUGINS="$MISSING_DEPS_PLUGINS libravatar"
+       else
+               AC_MSG_RESULT(no)
+               AC_MSG_ERROR("Plugin libravatar cannot be built; missing $dependencies_missing")
+       fi
+else
+       DISABLED_PLUGINS="$DISABLED_PLUGINS libravatar"
+       AC_MSG_RESULT(no)
+fi
+
 AC_MSG_CHECKING([whether to build mailmbox plugin])
 if test x"$enable_mailmbox_plugin" != xno; then
        PLUGINS="$PLUGINS mailmbox"
@@ -1830,6 +1859,7 @@ AM_CONDITIONAL(BUILD_FANCY_PLUGIN,                test x"$enable_fancy_plugin" != xno)
 AM_CONDITIONAL(BUILD_FETCHINFO_PLUGIN,         test x"$enable_fetchinfo_plugin" != xno)
 AM_CONDITIONAL(BUILD_GDATA_PLUGIN,             test x"$enable_gdata_plugin" != xno)
 AM_CONDITIONAL(BUILD_GEOLOCATION_PLUGIN,       test x"$enable_geolocation_plugin" != xno)
+AM_CONDITIONAL(BUILD_LIBRAVATAR_PLUGIN,                test x"$enable_libravatar_plugin" != xno)
 AM_CONDITIONAL(BUILD_MAILMBOX_PLUGIN,          test x"$enable_mailmbox_plugin" != xno)
 AM_CONDITIONAL(BUILD_NEWMAIL_PLUGIN,           test x"$enable_newmail_plugin" != xno)
 AM_CONDITIONAL(BUILD_NOTIFICATION_PLUGIN,      test x"$enable_notification_plugin" != xno)
@@ -1877,6 +1907,7 @@ src/plugins/fancy/Makefile
 src/plugins/fetchinfo/Makefile
 src/plugins/gdata/Makefile
 src/plugins/geolocation/Makefile
+src/plugins/libravatar/Makefile
 src/plugins/mailmbox/Makefile
 src/plugins/newmail/Makefile
 src/plugins/notification/Makefile
index d440d96bc8eae71f1d495a12f660290210b491b3..2914aa51a36ba14a4ab4e626f1412ab5ffefb611 100644 (file)
@@ -12,6 +12,7 @@ SUBDIRS = \
        fetchinfo \
        gdata \
        geolocation \
+       libravatar \
        mailmbox \
        newmail \
        notification \
diff --git a/src/plugins/libravatar/Makefile.am b/src/plugins/libravatar/Makefile.am
new file mode 100644 (file)
index 0000000..e49d965
--- /dev/null
@@ -0,0 +1,74 @@
+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
+
+if BUILD_LIBRAVATAR_PLUGIN
+plugin_LTLIBRARIES = libravatar.la
+endif
+
+libravatar_la_LDFLAGS = \
+       $(plugin_res_ldflag) $(no_undefined) $(export_symbols) \
+       -avoid-version -module \
+       $(GTK_LIBS)
+
+libravatar_la_DEPENDENCIES = $(plugin_deps)
+
+libravatar_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \
+       $(GTK_LIBS)
+
+INCLUDES = \
+       -I$(top_srcdir)/src \
+       -I$(top_srcdir)/src/common \
+       -I$(top_builddir)/src/common \
+       -I$(top_srcdir)/src/gtk
+
+AM_CPPFLAGS = \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS)
+
+libravatar_la_SOURCES = \
+       libravatar.c libravatar.h \
+       libravatar_prefs.c libravatar_prefs.h \
+       libravatar_missing.c libravatar_missing.h
+
diff --git a/src/plugins/libravatar/README b/src/plugins/libravatar/README
new file mode 100644 (file)
index 0000000..fdd9e71
--- /dev/null
@@ -0,0 +1,39 @@
+Libravatar plugin for Claws Mail
+--------------------------------
+
+This plugin allows showing the profile picture associated to email
+addresses provided by https://www.libravatar.org/. You can read
+more about what is this at http://wiki.libravatar.org/description/.
+
+By default missing profiles in the libravatar site are also searched
+in http://gravatar.com, so it will also show pictures from gravatar
+profiles. This can be turned off by disallowing redirects.
+
+When profile is missing in both sites there's options to use a
+'generated' avatar instead. It's also possible to not generate
+anything. In that case no image will be added and the existing
+Face/X-Face machinery will operate as usual, allowing to show the
+avatar images provided by those headers when no profile picture
+exists in the servers.
+
+This plugin uses libcurl to download images, so proxy support is
+already implemented. You just need to set the “http_proxy” variable
+in your environment before launching Claws Mail (see ENVIRONMENT
+section in curl(1) manpage for details).
+
+This plugin saves retrieved images for later reuse instead of making
+a network request on every message. Cache directory is in
+~/.claws-mail/avatarcache. Does also save the missing ones in a
+special file to avoid filling the cache with empty files. You can
+control how much time will be kept there before trying to download
+them again. Missing items are valid for more time, 7 times your
+configured interval cache time. You can disable the image cache,
+but notice that write access to avatarcache is still required.
+Missing cache cannot be disabled.
+
+The TODO file contains more known issues/enhancements, read it before
+reporting bugs.
+
+-- 
+Ricardo Mones <ricardo@mones.org>
+
diff --git a/src/plugins/libravatar/TODO b/src/plugins/libravatar/TODO
new file mode 100644 (file)
index 0000000..c21ff67
--- /dev/null
@@ -0,0 +1,27 @@
+Enhancements, possibilities and random ideas
+--------------------------------------------
+
+- Retrieve federated domain records (!)
+- Support federated IDN domains (??) (Claws Mail itself doesn't support it
+  http://www.thewildbeast.co.uk/claws-mail/bugzilla/show_bug.cgi?id=1670)
+- Domain validation when searching for federated avatars, or at least
+  exclude some domains (eg.: localhost) (???)
+- Check box to enable/disable domain validation (?)
+  http://data.iana.org/TLD/tlds-alpha-by-domain.txt may be useful.
+- Button for checking custom default URL is not 404 (!) check on apply (?)
+- Make it run in cache-less mode if cache dir cannot be created (??)
+- Only cache "mystery man" once for all hashes (what if changes) (?)
+- Cache information label (size on disk/# of avatars/missing cached) (?)
+- Empty avatar/missing cache button(s) (?)
+- Run network retrieval in a separate thread started at header capture
+  hook (??)
+- Alternate proxy support (send avatar requests through a proxy which
+  is not the one in http_proxy environment variable) (??)
+- Limit avatar download size (??) or time (???)
+- Configure missing factor/time separately (??)
+
+As these are already known, please do not request enhancement bugs for any
+of the above unless you have a working patch to be attached to the bug ;-)
+
+Discussion on ML is welcome, specially for items with one or more question
+marks.
diff --git a/src/plugins/libravatar/claws.def b/src/plugins/libravatar/claws.def
new file mode 100644 (file)
index 0000000..e7533f6
--- /dev/null
@@ -0,0 +1,25 @@
+LIBRARY CLAWS-MAIL.EXE
+EXPORTS
+get_locale_dir
+check_plugin_version
+conv_codeset_strdup
+conv_get_locale_charset_str_no_utf8
+debug_print_real
+debug_srcname
+get_rc_dir
+hooks_register_hook
+hooks_unregister_hook
+line_has_quote_char
+pref_get_escaped_pref
+pref_get_unescaped_pref
+prefs_common
+prefs_file_close
+prefs_file_close_revert
+prefs_gtk_register_page
+prefs_gtk_unregister_page
+prefs_read_config
+prefs_set_block_label
+prefs_set_default
+prefs_write_open
+prefs_write_param
+prefs_common_get_prefs
diff --git a/src/plugins/libravatar/libravatar.c b/src/plugins/libravatar/libravatar.c
new file mode 100644 (file)
index 0000000..9f40a95
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail Team
+ * Copyright (C) 2014 Ricardo Mones
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <curl/curl.h>
+
+#include "version.h"
+#include "libravatar.h"
+#include "libravatar_prefs.h"
+#include "libravatar_missing.h"
+#include "prefs_common.h"
+#include "procheader.h"
+#include "procmsg.h"
+#include "utils.h"
+#include "md5.h"
+
+/* indexes of keys are default_mode - 10 if applicable */
+static const char *def_mode[] = {
+       "404",  /* not used, only useful in web pages */
+       "mm",
+       "identicon",
+       "monsterid",
+       "wavatar",
+       "retro"
+};
+
+static guint update_hook_id;
+static guint render_hook_id;
+static gchar *cache_dir = NULL; /* dir-separator terminated */
+static GHashTable *misses;
+
+static gboolean libravatar_header_update_hook(gpointer source, gpointer data)
+{
+       AvatarCaptureData *acd = (AvatarCaptureData *)source;
+
+       debug_print("libravatar avatar_header_update invoked\n");
+
+       if (!strcmp(acd->header, "From:")) {
+               gchar *a = g_strdup(acd->content);
+               extract_address(a);
+               debug_print("libravatar added '%s'\n", a);
+               procmsg_msginfo_add_avatar(acd->msginfo, AVATAR_LIBRAVATAR, a);
+       }
+
+       return FALSE; /* keep getting */
+}
+
+static gchar *federated_base_url_from_address(const gchar *address)
+{
+       /*
+          TODO: no federation supported right now
+          Details on http://wiki.libravatar.org/running_your_own/
+        */
+       return g_strdup(libravatarprefs.base_url);
+}
+
+static GtkWidget *image_widget_from_filename(const gchar *filename)
+{
+       GtkWidget *image = NULL;
+       GdkPixbuf *picture = NULL;
+       GError *error = NULL;
+       gint w, h;
+
+       gdk_pixbuf_get_file_info(filename, &w, &h);
+
+       if (w != AVATAR_SIZE || h != AVATAR_SIZE)
+               /* server can provide a different size from the requested in URL */
+               picture = gdk_pixbuf_new_from_file_at_scale(
+                               filename, AVATAR_SIZE, AVATAR_SIZE, TRUE, &error);
+       else    /* exact size */
+               picture = gdk_pixbuf_new_from_file(filename, &error);
+
+       if (error != NULL) {
+               g_warning("Failed to load image '%s': %s\n", filename, error->message);
+               g_error_free(error);
+       } else {
+               if (picture) {
+                       image = gtk_image_new_from_pixbuf(picture);
+                       g_object_unref(picture);
+               } else
+                       g_warning("Failed to load image '%s': no error returned!\n", filename);
+       }
+
+       return image;
+}
+
+static gchar *cache_name_for_md5(const gchar *md5)
+{
+       if (libravatarprefs.default_mode >= DEF_MODE_MM
+                       && libravatarprefs.default_mode <= DEF_MODE_RETRO) {
+               /* cache dir for generated avatars */
+               return g_strconcat(cache_dir, def_mode[libravatarprefs.default_mode - 10],
+                                  G_DIR_SEPARATOR_S, md5, NULL);
+       }
+       /* default cache dir */
+       return g_strconcat(cache_dir, md5, NULL);
+}
+
+static size_t write_image_data_cb(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+       size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
+       debug_print("received %zu bytes from avatar server\n", written);
+
+       return written;
+}
+
+static GtkWidget *image_widget_from_url(const gchar *url, const gchar *md5)
+{
+       GtkWidget *image = NULL;
+       gchar *filename;
+       FILE *file;
+       CURL *curl;
+
+       curl = curl_easy_init();
+       if (curl == NULL) {
+               g_warning("could not initialize curl to get image from url\n");
+               return NULL;
+       }
+       curl_easy_setopt(curl, CURLOPT_URL, url);
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_image_data_cb);
+
+       filename = cache_name_for_md5(md5);
+       file = fopen(filename, "wb");
+       if (file != NULL) {
+               long filesize;
+
+               if (libravatarprefs.allow_redirects) {
+                       long maxredirs = (libravatarprefs.default_mode == DEF_MODE_MM)? 2L: 1L;
+
+                       curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+                       curl_easy_setopt(curl, CURLOPT_MAXREDIRS, maxredirs);
+               }
+               curl_easy_setopt(curl, CURLOPT_FILE, file);
+               debug_print("retrieving URL to file: %s -> %s\n", url, filename);
+               curl_easy_perform(curl);
+               filesize = ftell(file);
+               fclose(file);
+
+               if (filesize < MIN_PNG_SIZE)
+                       debug_print("not enough data for an avatar image: %ld bytes\n", filesize);
+               else
+                       image = image_widget_from_filename(filename);
+
+               if (!libravatarprefs.cache_icons) {
+                       if (g_unlink(filename) < 0)
+                               g_warning("failed to delete cache file %s\n", filename);
+               }
+
+               if (filesize == 0)
+                       missing_add_md5(misses, md5);
+       } else {
+               g_warning("could not open '%s' for writting\n", filename);
+       }
+       curl_easy_cleanup(curl);
+       g_free(filename);
+
+       return image;
+}
+
+static gboolean is_recent_enough(const gchar *filename)
+{
+       struct stat s;
+       time_t t;
+
+       if (libravatarprefs.cache_icons) {
+               t = time(NULL);
+               if (t != (time_t)-1 && !g_stat(filename, &s)) {
+                       if (t - s.st_ctime <= libravatarprefs.cache_interval * 3600)
+                               return TRUE;
+               }
+       }
+
+       return FALSE; /* re-download */
+}
+
+static GtkWidget *image_widget_from_cached_md5(const gchar *md5)
+{
+       GtkWidget *image = NULL;
+       gchar *filename;
+
+       filename = cache_name_for_md5(md5);
+       if (is_file_exist(filename) && is_recent_enough(filename)) {
+               debug_print("found cached image for %s\n", md5);
+               image = image_widget_from_filename(filename);
+       }
+       g_free(filename);
+
+       return image;
+}
+
+static gchar *libravatar_url_for_md5(const gchar *base, const gchar *md5)
+{
+       if (libravatarprefs.default_mode >= DEF_MODE_404) {
+               return g_strdup_printf("%s/%s?s=%u&d=%s",
+                               base, md5, AVATAR_SIZE,
+                               def_mode[libravatarprefs.default_mode - 10]);
+       } else if (libravatarprefs.default_mode == DEF_MODE_URL) {
+               return g_strdup_printf("%s/%s?s=%u&d=%s",
+                               base, md5, AVATAR_SIZE,
+                               libravatarprefs.default_mode_url);
+       } else if (libravatarprefs.default_mode == DEF_MODE_NONE) {
+               return g_strdup_printf("%s/%s?s=%u",
+                               base, md5, AVATAR_SIZE);
+       }
+
+       g_warning("invalid libravatar default mode: %d\n", libravatarprefs.default_mode);
+       return NULL;
+}
+
+static gboolean libravatar_image_render_hook(gpointer source, gpointer data)
+{
+       AvatarRender *ar = (AvatarRender *)source;
+       GtkWidget *image = NULL;
+       gchar *a = NULL, *url = NULL;
+       gchar md5sum[33];
+
+       debug_print("libravatar avatar_image_render invoked\n");
+
+       a = procmsg_msginfo_get_avatar(ar->full_msginfo, AVATAR_LIBRAVATAR);
+       if (a != NULL) {
+               gchar *base;
+
+               md5_hex_digest(md5sum, a);
+               /* try missing cache */
+               if (is_missing_md5(misses, md5sum)) {
+                       return FALSE;
+               }
+               /* try disk cache */
+               image = image_widget_from_cached_md5(md5sum);
+               if (image != NULL) {
+                       if (ar->image) /* previous plugin set one */
+                               gtk_widget_destroy(ar->image);
+                       ar->image = image;
+                       return FALSE;
+               }
+               /* not cached copy: try network */
+               if (prefs_common.work_offline) {
+                       debug_print("working off-line: libravatar network retrieval skipped\n");
+                       return FALSE;
+               }
+               base = federated_base_url_from_address(a);
+               url = libravatar_url_for_md5(base, md5sum);
+               if (url != NULL) {
+                       image = image_widget_from_url(url, md5sum);
+                       g_free(url);
+                       if (image != NULL) {
+                               if (ar->image) /* previous plugin set one */
+                                       gtk_widget_destroy(ar->image);
+                               ar->image = image;
+                       }
+               }
+               g_free(base);
+
+               return TRUE;
+       }
+
+       return FALSE; /* keep rendering */
+}
+
+static gint cache_dir_init()
+{
+       gchar *subdir;
+       int i;
+
+       cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                               LIBRAVATAR_CACHE_DIR, G_DIR_SEPARATOR_S,
+                               NULL);
+       if (!is_dir_exist(cache_dir)) {
+               if (make_dir(cache_dir) < 0) {
+                       g_free(cache_dir);
+                       return -1;
+               }
+       }
+       for (i = DEF_MODE_MM; i <= DEF_MODE_RETRO; ++i) {
+               subdir = g_strconcat(cache_dir, def_mode[i - 10], NULL);
+               if (!is_dir_exist(subdir)) {
+                       if (make_dir(subdir) < 0) {
+                               g_warning("cannot create directory %s\n", subdir);
+                               g_free(subdir);
+                               return -1;
+                       }
+               }
+               g_free(subdir);
+       }
+
+       return 0;
+}
+
+static gint missing_cache_init()
+{
+       gchar *cache_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                                       LIBRAVATAR_CACHE_DIR, G_DIR_SEPARATOR_S,
+                                       LIBRAVATAR_MISSING_FILE, NULL);
+
+       misses = missing_load_from_file(cache_file);
+       g_free(cache_file);
+
+       if (misses == NULL)
+               return -1;
+
+       return 0;
+}
+
+static void missing_cache_done()
+{
+       gchar *cache_file;
+
+       if (misses != NULL) {
+               cache_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                                       LIBRAVATAR_CACHE_DIR, G_DIR_SEPARATOR_S,
+                                       LIBRAVATAR_MISSING_FILE, NULL);
+               missing_save_to_file(misses, cache_file);
+               g_free(cache_file);
+               g_hash_table_destroy(misses);
+       }
+}
+
+/**
+ * Initialize plugin.
+ *
+ * @param error  For storing the returned error message.
+ *
+ * @return 0 if initialization succeeds, -1 on failure.
+ */
+gint plugin_init(gchar **error)
+{
+       if (!check_plugin_version(MAKE_NUMERIC_VERSION(3,9,3,29),
+                                 VERSION_NUMERIC, _("Libravatar"), error))
+               return -1;
+       /* get info from headers */
+       update_hook_id = hooks_register_hook(AVATAR_HEADER_UPDATE_HOOKLIST,
+                                            libravatar_header_update_hook,
+                                            NULL);
+       if (update_hook_id == -1) {
+               *error = g_strdup(_("Failed to register avatar header update hook"));
+               return -1;
+       }
+       /* get image for displaying */
+       render_hook_id = hooks_register_hook(AVATAR_IMAGE_RENDER_HOOKLIST,
+                                            libravatar_image_render_hook,
+                                            NULL);
+       if (render_hook_id == -1) {
+               *error = g_strdup(_("Failed to register avatar image render hook"));
+               return -1;
+       }
+       /* cache dir */
+       if (cache_dir_init() == -1) {
+               *error = g_strdup(_("Failed to create avatar image cache directory"));
+               return -1;
+       }
+       /* preferences page */
+       libravatar_prefs_init();
+       /* curl library */
+       curl_global_init(CURL_GLOBAL_DEFAULT);
+       /* missing cache */
+       if (missing_cache_init() == -1) {
+               *error = g_strdup(_("Failed to load missing items cache"));
+               return -1;
+       }
+       debug_print("Libravatar plugin loaded\n");
+
+       return 0;
+}
+
+/**
+ * Destructor for the plugin.
+ * Unregister the callback function and frees matcher.
+ *
+ * @return Always TRUE.
+ */
+gboolean plugin_done(void)
+{
+       if (render_hook_id != -1) {
+               hooks_unregister_hook(AVATAR_IMAGE_RENDER_HOOKLIST,
+                                     render_hook_id);
+               render_hook_id = -1;
+       }
+       if (update_hook_id != -1) {
+               hooks_unregister_hook(AVATAR_HEADER_UPDATE_HOOKLIST,
+                                     update_hook_id);
+               update_hook_id = -1;
+       }
+       libravatar_prefs_done();
+       missing_cache_done();
+       if (cache_dir != NULL)
+               g_free(cache_dir);
+       debug_print("Libravatar plugin unloaded\n");
+
+       return TRUE;
+}
+
+/**
+ * Get the name of the plugin.
+ *
+ * @return The plugin's name, maybe translated.
+ */
+const gchar *plugin_name(void)
+{
+       return _("Libravatar");
+}
+
+/**
+ * Get the description of the plugin.
+ *
+ * @return The plugin's description, maybe translated.
+ */
+const gchar *plugin_desc(void)
+{
+       return _("Get and display libravatar images for mail messages.\n\n"
+                "Info about libravatar at http://www.libravatar.org/\n\n"
+                "Feedback to <ricardo@mones.org> is welcome.\n");
+}
+
+/**
+ * Get the kind of plugin.
+ *
+ * @return The "GTK2" constant.
+ */
+const gchar *plugin_type(void)
+{
+       return "GTK2";
+}
+
+/**
+ * Get the license acronym the plugin is released under.
+ *
+ * @return The "GPL3+" constant.
+ */
+const gchar *plugin_licence(void)
+{
+       return "GPL3+";
+}
+
+/**
+ * Get the version of the plugin.
+ *
+ * @return The current version string.
+ */
+const gchar *plugin_version(void)
+{
+       return VERSION;
+}
+
+/**
+ * Get the features implemented by the plugin.
+ *
+ * @return A constant PluginFeature structure with the features.
+ */
+struct PluginFeature *plugin_provides(void)
+{
+       static struct PluginFeature features[] =
+               { {PLUGIN_OTHER, N_("Libravatar")},
+                 {PLUGIN_NOTHING, NULL}};
+
+       return features;
+}
+
diff --git a/src/plugins/libravatar/libravatar.h b/src/plugins/libravatar/libravatar.h
new file mode 100644 (file)
index 0000000..9bb3c2e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail Team
+ * Copyright (C) 2014 Ricardo Mones
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBRAVATAR_H
+#define __LIBRAVATAR_H
+
+#include <glib.h>
+
+#include "version.h"
+#include "claws.h"
+#include "plugin.h"
+#include "utils.h"
+#include "hooks.h"
+#include "avatars.h"
+
+#define AVATAR_LIBRAVATAR 3
+#define AVATAR_SIZE 48
+#define LIBRAVATAR_CACHE_DIR "avatarcache"
+/* https://github.com/mathiasbynens/small/pull/19 */
+#define MIN_PNG_SIZE 67L
+#define MAX_URL_LENGTH 1024
+
+gint           plugin_init               (gchar **error);
+gboolean       plugin_done               (void);
+const gchar *  plugin_name               (void);
+const gchar *  plugin_desc               (void);
+const gchar *  plugin_type               (void);
+const gchar *  plugin_licence            (void);
+const gchar *  plugin_version            (void);
+struct PluginFeature *plugin_provides    (void);
+
+#endif
+
diff --git a/src/plugins/libravatar/libravatar_missing.c b/src/plugins/libravatar/libravatar_missing.c
new file mode 100644 (file)
index 0000000..6288dc7
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail Team
+ * Copyright (C) 2014 Ricardo Mones
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+
+#include "libravatar_missing.h"
+#include "libravatar_prefs.h"
+#include "utils.h"
+
+/**
+ * Loads the hash table of md5sum → time from the given filename.
+ *
+ * @param filename  Name of the hash table filename.
+ *
+ * @return A hash table with the entries not expired contained in
+ *         the given filename or NULL if failed to load.
+ */
+GHashTable *missing_load_from_file(const gchar *filename)
+{
+       FILE *file = fopen(filename, "r");
+       time_t t;
+       long long unsigned seen;
+       gchar md5sum[33];
+       GHashTable *table;
+       int r = 0, a = 0, d = 0;
+
+       if (file == NULL) {
+               if (!is_file_exist(filename)) { /* first run, return an empty table */
+                       return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+               }
+               g_warning("Cannot open %s for reading\n", filename);
+               return NULL;
+       }
+       t = time(NULL);
+       if (t == (time_t)-1) {
+               g_warning("Cannot get time!\n");
+               return NULL;
+       }
+
+       table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+       while ((r = fscanf(file, "%s %llu\n", md5sum, &seen)) != EOF) {
+               if (t - (time_t)seen <= LIBRAVATAR_MISSING_TIME) {
+                       time_t *value = g_malloc0(sizeof(time_t));
+                       if (value == NULL) {
+                               g_warning("Cannot allocate memory\n");
+                               g_hash_table_destroy(table);
+                               return NULL;
+                       }
+                       *value = (time_t)seen;
+                       g_hash_table_insert(table, g_strdup(md5sum), value);
+               } else
+                       d++;
+               a++;
+       }
+
+       if (fclose(file) != 0)
+               g_warning("Error closing %s\n", filename);
+
+       debug_print("Read %d missing avatar entries, %d obsolete entries discarded\n", a, d);
+       return table;
+}
+
+/**
+ * Saves a hash table item.
+ *
+ * @param key    Hash table key, a md5sum string.
+ * @param vakue  Hash table value, a time_t.
+ * @param data   User data, a pointer to the open FILE being written.
+ */
+static void missing_save_item(gpointer key, gpointer value, gpointer data)
+{
+       FILE *file = (FILE *)data;
+       gchar *line = g_strdup_printf("%s %llu\n", (gchar *)key, *((long long unsigned *)value));
+       if (fputs(line, file) < 0)
+               g_warning("Error saving missing item\n");
+       g_free(line);
+}
+
+/**
+ * Saves a hash table of md5sum → time to a given file name.
+ *
+ * @param table     The table to save.
+ * @param filename  The name of the file where table data will be saved.
+ *
+ * @return 0 on success, -1 if there was some problem saving.
+ */
+gint missing_save_to_file(GHashTable *table, const gchar *filename)
+{
+       FILE *file = fopen(filename, "w");
+
+       if (file == NULL) {
+               g_warning("Cannot open %s for writting\n", filename);
+               return -1;
+       }
+
+       g_hash_table_foreach(table, missing_save_item, (gpointer)file);
+       debug_print("Saved %u missing avatar entries\n", g_hash_table_size(table));
+
+       if (fclose(file) != 0) {
+               g_warning("Error closing %s\n", filename);
+               return -1;
+       }
+
+       return 0;
+}
+
+/**
+ * Adds a md5sum to a md5sum → time hash table.
+ * If the md5sum is already in the table its time is updated.
+ *
+ * @param table  The table to use.
+ * @param md5    The md5sum to add or update.
+ */
+void missing_add_md5(GHashTable *table, const gchar *md5)
+{
+       time_t t = time(NULL);
+
+       if (t == (time_t)-1) {
+               g_warning("Cannot get time!\n");
+               return;
+       }
+
+       time_t *seen = g_hash_table_lookup(table, md5);
+       if (seen == NULL) {
+               seen = g_malloc0(sizeof(time_t));
+               if (seen == NULL) {
+                       g_warning("Cannot allocate memory\n");
+                       return;
+               }
+               *seen = t;
+               g_hash_table_insert(table, g_strdup(md5), seen);
+               debug_print("New md5 %s added with time %llu\n", md5, (long long unsigned)t);
+       } else {
+               *seen = t; /* just update */
+               debug_print("Updated md5 %s with time %llu\n", md5, (long long unsigned)t);
+       }
+}
+
+/**
+ * Check if a md5sum is in hash table and not expired.
+ *
+ * @param table   The table to check against.
+ * @param md5     The md5sum to check.
+ *
+ * @return TRUE if the md5sum is in the table and is not expired,
+           FALSE otherwise.
+ */
+gboolean is_missing_md5(GHashTable *table, const gchar *md5)
+{
+       time_t t;
+       time_t *seen = (time_t *)g_hash_table_lookup(table, md5);
+
+       if (seen == NULL)
+               return FALSE;
+
+       t = time(NULL);
+       if (t != (time_t)-1) {
+               if (t - *seen <= LIBRAVATAR_MISSING_TIME) {
+                       debug_print("Found missing md5 %s\n", md5);
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
diff --git a/src/plugins/libravatar/libravatar_missing.h b/src/plugins/libravatar/libravatar_missing.h
new file mode 100644 (file)
index 0000000..191159b
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail Team
+ * Copyright (C) 2014 Ricardo Mones
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBRAVATAR_MISSING_H
+#define __LIBRAVATAR_MISSING_H
+
+#include <glib.h>
+
+#define LIBRAVATAR_MISSING_FILE "missing"
+/* multiply cache interval time pref for missing items */
+#define LIBRAVATAR_MISSING_TIME (libravatarprefs.cache_interval * 3600 * 7)
+
+GHashTable *missing_load_from_file     (const gchar *filename);
+gint missing_save_to_file              (GHashTable *table,
+                                        const gchar *filename);
+void missing_add_md5                   (GHashTable *table,
+                                        const gchar *md5);
+gboolean is_missing_md5                        (GHashTable *table,
+                                        const gchar *md5);
+
+#endif
diff --git a/src/plugins/libravatar/libravatar_prefs.c b/src/plugins/libravatar/libravatar_prefs.c
new file mode 100644 (file)
index 0000000..c14948d
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail Team
+ * Copyright (C) 2014 Ricardo Mones
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "libravatar.h"
+
+#include <gtk/gtk.h>
+#include <gtk/filesel.h>
+
+#include "defs.h"
+#include "libravatar_prefs.h"
+#include "prefs_common.h"
+#include "prefs_gtk.h"
+
+#define PREFS_BLOCK_NAME "Libravatar"
+#define NUM_DEF_BUTTONS 7
+/* cache interval goes from 1 hour to 30 days */
+#define INTERVAL_MIN_H 1.0
+#define INTERVAL_MAX_H 720.0
+
+LibravatarPrefs libravatarprefs;
+
+struct LibravatarPrefsPage
+{
+       PrefsPage page;
+
+       GtkWidget *cache_interval_spin;
+       GtkWidget *cache_icons_check;
+       GtkWidget *defm_radio[NUM_DEF_BUTTONS];
+       GtkWidget *defm_url_text;
+       GtkWidget *allow_redirects_check;
+};
+
+struct LibravatarPrefsPage libravatarprefs_page;
+
+static PrefParam param[] = {
+       { "base_url", "http://cdn.libravatar.org/avatar",
+         &libravatarprefs.base_url,
+          P_STRING, NULL, NULL, NULL },
+       { "cache_interval", "24",
+         &libravatarprefs.cache_interval,
+          P_INT, NULL, NULL, NULL },
+       { "cache_icons", "TRUE",
+         &libravatarprefs.cache_icons,
+          P_BOOL, NULL, NULL, NULL },
+       { "default_mode", "0",
+         &libravatarprefs.default_mode,
+          P_INT, NULL, NULL, NULL },
+       { "default_mode_url", "",
+         &libravatarprefs.default_mode_url,
+          P_STRING, NULL, NULL, NULL },
+       { "allow_redirects", "TRUE",
+         &libravatarprefs.allow_redirects,
+          P_BOOL, NULL, NULL, NULL },
+       {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
+};
+
+static GtkWidget *create_checkbox(gchar *label, gchar *hint)
+{
+       GtkWidget *cb = gtk_check_button_new_with_mnemonic(label);
+       CLAWS_SET_TIP(cb, hint);
+       gtk_widget_show(cb);
+
+       return cb;
+}
+
+static void cache_icons_check_toggled_cb(GtkToggleButton *button, gpointer data)
+{
+       gtk_widget_set_sensitive(libravatarprefs_page.cache_interval_spin,
+                                gtk_toggle_button_get_active(button));
+}
+
+static GtkWidget *p_create_frame_cache(struct LibravatarPrefsPage *page)
+{
+       GtkWidget *vbox, *checkbox, *lbl, *lbla, *spinner, *hbox;
+       GtkAdjustment *adj;
+
+       vbox =  gtk_vbox_new(FALSE, 6);
+
+       checkbox = create_checkbox(_("_Use cached icons"),
+                                  _("Keep icons on disk for reusing instead "
+                                    "of making another network request"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                                    libravatarprefs.cache_icons);
+       g_signal_connect(checkbox, "toggled",
+                        G_CALLBACK(cache_icons_check_toggled_cb), NULL);
+       page->cache_icons_check = checkbox;
+
+       lbl = gtk_label_new(_("Cache refresh interval"));
+       gtk_widget_show(lbl);
+       lbla = gtk_label_new(_("hours"));
+       gtk_widget_show(lbla);
+       adj = (GtkAdjustment *) gtk_adjustment_new(
+                                       libravatarprefs.cache_interval,
+                                       INTERVAL_MIN_H, INTERVAL_MAX_H, 1.0,
+                                       0.0, 0.0);
+       spinner = gtk_spin_button_new(adj, 1.0, 0);
+       gtk_widget_show(spinner);
+       gtk_widget_set_sensitive(spinner, libravatarprefs.cache_icons);
+       hbox = gtk_hbox_new(FALSE, 6);
+       gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(hbox), lbla, FALSE, FALSE, 0);
+       page->cache_interval_spin = spinner;
+
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+       return vbox;
+}
+
+static void default_mode_radio_button_cb(GtkToggleButton *button, gpointer data)
+{
+       gboolean is_url;
+
+       if (gtk_toggle_button_get_active(button) == TRUE) {
+               is_url = (*((guint *)data) == DEF_MODE_URL)? TRUE: FALSE;
+               gtk_widget_set_sensitive(libravatarprefs_page.defm_url_text, is_url);
+               if (is_url) /* custom URL requires following redirects */
+                       gtk_toggle_button_set_active(
+                               GTK_TOGGLE_BUTTON(libravatarprefs_page.allow_redirects_check),
+                               TRUE);
+               /* don't waste time with headers that won't be displayed */
+               prefs_common.enable_avatars = (*((guint *)data) == DEF_MODE_NONE)
+                                               ? AVATARS_ENABLE_BOTH: AVATARS_DISABLE;
+       }
+}
+
+static const guint radio_value[] = {
+       DEF_MODE_NONE,
+       DEF_MODE_MM,
+       DEF_MODE_IDENTICON,
+       DEF_MODE_MONSTERID,
+       DEF_MODE_WAVATAR,
+       DEF_MODE_RETRO,
+       DEF_MODE_URL
+};
+
+static GtkWidget *p_create_frame_missing(struct LibravatarPrefsPage *page)
+{
+       GtkWidget *vbox, *radio[NUM_DEF_BUTTONS], *hbox, *label, *entry;
+       gboolean enable = FALSE;
+       int i;
+       gchar *radio_label[] = {
+               _("None"),
+               _("Mystery man"),
+               _("Identicon"),
+               _("MonsterID"),
+               _("Wavatar"),
+               _("Retro"),
+               _("Custom URL")
+       };
+       gchar *radio_hint[] = {
+               _("A blank image"),
+               _("The unobtrusive low-contrast greyish silhouette"),
+               _("A generated geometric pattern"),
+               _("A generated full-body monster"),
+               _("A generated almost unique face"),
+               _("A generated 8-bit arcade-style pixelated image"),
+               _("Redirect to a user provided URL")
+       };
+
+       vbox =  gtk_vbox_new(FALSE, 6);
+
+       for (i = 0; i < NUM_DEF_BUTTONS; ++i) {
+               enable = (!enable && libravatarprefs.default_mode == radio_value[i])? TRUE: FALSE;
+               radio[i] = gtk_radio_button_new_with_label_from_widget(
+                               (i > 0)? GTK_RADIO_BUTTON(radio[i - 1]): NULL, radio_label[i]);
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[i]), enable);
+               gtk_box_pack_start(GTK_BOX(vbox), radio[i], FALSE, FALSE, 0);
+               g_signal_connect(radio[i], "toggled",
+                                G_CALLBACK(default_mode_radio_button_cb),
+                                (gpointer) &(radio_value[i]));
+               CLAWS_SET_TIP(radio[i], radio_hint[i]);
+               gtk_widget_show(radio[i]);
+               page->defm_radio[i] = radio[i];
+       }
+       if (!enable) { /* unknown value, go default */
+               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE);
+               libravatarprefs.default_mode = DEF_MODE_NONE;
+       }
+       /* don't waste time with headers that won't be displayed */
+       prefs_common.enable_avatars = (libravatarprefs.default_mode == DEF_MODE_NONE)
+                                               ? AVATARS_ENABLE_BOTH: AVATARS_DISABLE;
+
+       label = gtk_label_new(_("URL:"));
+       gtk_widget_show(label);
+       entry = gtk_entry_new_with_max_length(MAX_URL_LENGTH);
+       gtk_widget_show(entry);
+       gtk_entry_set_text(GTK_ENTRY(entry), libravatarprefs.default_mode_url);
+
+       hbox = gtk_hbox_new(FALSE, 6);
+       gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
+       gtk_widget_set_sensitive(entry,
+               (libravatarprefs.default_mode == DEF_MODE_URL)? TRUE: FALSE);
+       page->defm_url_text = entry;
+
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+       return vbox;
+}
+
+static GtkWidget *p_create_frame_network(struct LibravatarPrefsPage *page)
+{
+       GtkWidget *vbox, *checkbox;
+
+       vbox =  gtk_vbox_new(FALSE, 6);
+
+       checkbox = create_checkbox(_("_Allow redirects to other sites"),
+                                  _("Follow redirect responses received from "
+                                    "libravatar server to other avatar "
+                                    "services like gravatar.com"));
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox),
+                                    libravatarprefs.allow_redirects);
+       page->allow_redirects_check = checkbox;
+
+       gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 0);
+
+       return vbox;
+}
+
+/*
+  ┌─Icon cache───────────────────────────────────────────┐
+  │ [✔] Use cached icons                                 │
+  │ Cache refresh interval [ 24 |⬘] hours                │
+  └──────────────────────────────────────────────────────┘
+  ┌─Default missing icon mode────────────────────────────┐
+  │ (•) None                                             │
+  │ ( ) Mystery man                                      │
+  │ ( ) Identicon                                        │
+  │ ( ) MonsterID                                        │
+  │ ( ) Wavatar                                          │
+  │ ( ) Retro                                            │
+  │ ( ) Custom URL                                       │
+  │     URL: [_________________________________________] │
+  └──────────────────────────────────────────────────────┘
+  ┌─Network──────────────────────────────────────────────┐
+  │ [✔] Allow redirects                                  │
+  └──────────────────────────────────────────────────────┘
+ */
+static void libravatar_prefs_create_widget_func(PrefsPage * _page,
+                                               GtkWindow * window,
+                                               gpointer data)
+{
+       struct LibravatarPrefsPage *page = (struct LibravatarPrefsPage *) _page;
+       GtkWidget *vbox, *vbox1, *vbox2, *vbox3, *frame;
+
+       vbox1 = p_create_frame_cache(page);
+       vbox2 = p_create_frame_missing(page);
+       vbox3 = p_create_frame_network(page);
+
+       vbox = gtk_vbox_new(FALSE, 6);
+       gtk_container_set_border_width(GTK_CONTAINER(vbox), VBOX_BORDER);
+
+       PACK_FRAME (vbox, frame, _("Icon cache"));
+       gtk_container_set_border_width(GTK_CONTAINER(vbox1), 6);
+       gtk_container_add(GTK_CONTAINER(frame), vbox1);
+
+       PACK_FRAME (vbox, frame, _("Default missing icon mode"));
+       gtk_container_set_border_width(GTK_CONTAINER(vbox2), 6);
+       gtk_container_add(GTK_CONTAINER(frame), vbox2);
+
+       PACK_FRAME (vbox, frame, _("Network"));
+       gtk_container_set_border_width(GTK_CONTAINER(vbox3), 6);
+       gtk_container_add(GTK_CONTAINER(frame), vbox3);
+
+       gtk_widget_show_all(vbox);
+       page->page.widget = vbox;
+}
+
+static void libravatar_prefs_destroy_widget_func(PrefsPage *_page)
+{
+       /* nothing */
+}
+
+static void libravatar_save_config(void)
+{
+       PrefFile *pfile;
+       gchar *rcpath;
+
+       debug_print("Saving Libravatar Page\n");
+
+       rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
+       pfile = prefs_write_open(rcpath);
+       g_free(rcpath);
+       if (!pfile || (prefs_set_block_label(pfile, PREFS_BLOCK_NAME) < 0))
+               return;
+
+       if (prefs_write_param(param, pfile->fp) < 0) {
+               g_warning("Failed to write Libravatar configuration to file\n");
+               prefs_file_close_revert(pfile);
+               return;
+       }
+        if (fprintf(pfile->fp, "\n") < 0) {
+               FILE_OP_ERROR(rcpath, "fprintf");
+               prefs_file_close_revert(pfile);
+       } else
+               prefs_file_close(pfile);
+}
+
+static void libravatar_prefs_save_func(PrefsPage * _page)
+{
+       struct LibravatarPrefsPage *page = (struct LibravatarPrefsPage *) _page;
+       int i;
+
+       /* cache */
+       libravatarprefs.cache_icons = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->cache_icons_check));
+       /* cache interval */
+       libravatarprefs.cache_interval = gtk_spin_button_get_value_as_int(
+               GTK_SPIN_BUTTON(page->cache_interval_spin));
+       /* default mode */
+       for (i = 0; i < NUM_DEF_BUTTONS; ++i) {
+               if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->defm_radio[i]))) {
+                       libravatarprefs.default_mode = radio_value[i];
+                       break;
+               }
+       }
+       /* custom url */
+       if (libravatarprefs.default_mode_url != NULL) {
+               g_free(libravatarprefs.default_mode_url);
+       }
+       libravatarprefs.default_mode_url = gtk_editable_get_chars(
+               GTK_EDITABLE(page->defm_url_text), 0, -1);
+       /* redirects */
+       libravatarprefs.allow_redirects = gtk_toggle_button_get_active(
+               GTK_TOGGLE_BUTTON(page->allow_redirects_check));
+
+       libravatar_save_config();
+}
+
+void libravatar_prefs_init(void)
+{
+       static gchar *path[3];
+       gchar *rcpath;
+
+       path[0] = _("Plugins");
+       path[1] = _("Libravatar");
+       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);
+
+       libravatarprefs_page.page.path = path;
+       libravatarprefs_page.page.create_widget = libravatar_prefs_create_widget_func;
+       libravatarprefs_page.page.destroy_widget = libravatar_prefs_destroy_widget_func;
+       libravatarprefs_page.page.save_page = libravatar_prefs_save_func;
+
+       prefs_gtk_register_page((PrefsPage *) &libravatarprefs_page);
+}
+
+void libravatar_prefs_done(void)
+{
+       prefs_gtk_unregister_page((PrefsPage *) &libravatarprefs_page);
+}
+
diff --git a/src/plugins/libravatar/libravatar_prefs.h b/src/plugins/libravatar/libravatar_prefs.h
new file mode 100644 (file)
index 0000000..fe8ed6a
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2014 Hiroyuki Yamamoto and the Claws Mail Team
+ * Copyright (C) 2014 Ricardo Mones
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBRAVATAR_PREFS_H
+#define __LIBRAVATAR_PREFS_H
+
+#include <glib.h>
+
+typedef struct _LibravatarPrefs LibravatarPrefs;
+
+/* http://wiki.libravatar.org/api/ */
+enum
+{
+       DEF_MODE_NONE           = 0,
+       DEF_MODE_URL            = 1,
+       DEF_MODE_404            = 10, /* not used, only useful in web pages */
+       DEF_MODE_MM             = 11,
+       DEF_MODE_IDENTICON      = 12,
+       DEF_MODE_MONSTERID      = 13,
+       DEF_MODE_WAVATAR        = 14,
+       DEF_MODE_RETRO          = 15,
+};
+
+struct _LibravatarPrefs
+{
+       gchar           *base_url; /* hidden pref */
+       guint           cache_interval;
+       gboolean        cache_icons;
+       guint           default_mode;
+       gchar           *default_mode_url;
+       gboolean        allow_redirects;
+};
+
+extern LibravatarPrefs libravatarprefs;
+
+void libravatar_prefs_init(void);
+void libravatar_prefs_done(void);
+
+#endif
+
diff --git a/src/plugins/libravatar/plugin.def b/src/plugins/libravatar/plugin.def
new file mode 100644 (file)
index 0000000..8471df1
--- /dev/null
@@ -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/libravatar/version.rc b/src/plugins/libravatar/version.rc
new file mode 100644 (file)
index 0000000..5d93a3d
--- /dev/null
@@ -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", "Claws Mail Libravatar Plugin\0"
+            VALUE "FileVersion", "0.0.0.0\0"
+            VALUE "ProductVersion", "0.0.0.0 Win32\0"
+            VALUE "LegalCopyright", "GPL / © 1999-2014 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