2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2014-2015 Ricardo Mones and the Claws Mail Team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
25 #include <glib/gi18n.h>
27 #include <curl/curl.h>
30 #include "libravatar.h"
31 #include "libravatar_prefs.h"
32 #include "libravatar_cache.h"
33 #include "libravatar_image.h"
34 #include "libravatar_missing.h"
35 #include "libravatar_federation.h"
36 #include "prefs_common.h"
37 #include "procheader.h"
42 /* indexes of keys are default_mode - 10 if applicable */
43 static const char *def_mode[] = {
44 "404", /* not used, only useful in web pages */
52 static guint update_hook_id;
53 static guint render_hook_id;
54 static gchar *cache_dir = NULL; /* dir-separator terminated */
56 static gboolean libravatar_header_update_hook(gpointer source, gpointer data)
58 AvatarCaptureData *acd = (AvatarCaptureData *)source;
60 debug_print("libravatar avatar_header_update invoked\n");
62 if (!strcmp(acd->header, "From:")) {
65 a = g_strdup(acd->content);
69 for (lower = a; *lower; lower++)
70 *lower = g_ascii_tolower(*lower);
72 debug_print("libravatar added '%s'\n", a);
73 procmsg_msginfo_add_avatar(acd->msginfo, AVATAR_LIBRAVATAR, a);
77 return FALSE; /* keep getting */
80 static gchar *federated_base_url_from_address(const gchar *address)
82 #if (defined USE_GNUTLS && GLIB_CHECK_VERSION(2,22,0))
83 gchar *base_url = NULL;
85 if (!libravatarprefs.allow_federated) {
86 debug_print("federated domains disabled by configuration\n");
90 base_url = federated_url_for_address(address);
91 if (base_url != NULL) {
97 return g_strdup(libravatarprefs.base_url);
100 static GtkWidget *image_widget_from_pixbuf(GdkPixbuf *picture)
102 GtkWidget *image = NULL;
105 image = gtk_image_new_from_pixbuf(picture);
106 g_object_unref(picture);
108 g_warning("null picture returns null widget");
113 static GtkWidget *image_widget_from_filename(const gchar *filename)
115 GdkPixbuf *picture = NULL;
116 GError *error = NULL;
119 gdk_pixbuf_get_file_info(filename, &w, &h);
121 if (w != AVATAR_SIZE || h != AVATAR_SIZE)
122 /* server can provide a different size from the requested in URL */
123 picture = gdk_pixbuf_new_from_file_at_scale(
124 filename, AVATAR_SIZE, AVATAR_SIZE, TRUE, &error);
125 else /* exact size */
126 picture = gdk_pixbuf_new_from_file(filename, &error);
129 g_warning("failed to load image '%s': %s", filename, error->message);
134 return image_widget_from_pixbuf(picture);
137 static gchar *cache_name_for_md5(const gchar *md5)
139 if (libravatarprefs.default_mode >= DEF_MODE_MM
140 && libravatarprefs.default_mode <= DEF_MODE_RETRO) {
141 /* cache dir for generated avatars */
142 return g_strconcat(cache_dir, def_mode[libravatarprefs.default_mode - 10],
143 G_DIR_SEPARATOR_S, md5, NULL);
145 /* default cache dir */
146 return g_strconcat(cache_dir, md5, NULL);
149 static size_t write_image_data_cb(void *ptr, size_t size, size_t nmemb, void *stream)
151 size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
152 debug_print("received %zu bytes from avatar server\n", written);
157 static GtkWidget *image_widget_from_url(const gchar *url, const gchar *md5)
159 GtkWidget *image = NULL;
160 AvatarImageFetch aif;
164 aif.filename = cache_name_for_md5(md5);
165 libravatar_image_fetch(&aif);
167 image = gtk_image_new_from_pixbuf(aif.pixbuf);
168 g_object_unref(aif.pixbuf);
170 g_free(aif.filename);
175 static gboolean is_recent_enough(const gchar *filename)
180 if (libravatarprefs.cache_icons) {
182 if (t != (time_t)-1 && !g_stat(filename, &s)) {
183 if (t - s.st_ctime <= libravatarprefs.cache_interval * 3600)
188 return FALSE; /* re-download */
191 static GtkWidget *image_widget_from_cached_md5(const gchar *md5)
193 GtkWidget *image = NULL;
196 filename = cache_name_for_md5(md5);
197 if (is_file_exist(filename) && is_recent_enough(filename)) {
198 debug_print("found cached image for %s\n", md5);
199 image = image_widget_from_filename(filename);
206 static gchar *libravatar_url_for_md5(const gchar *base, const gchar *md5)
208 if (libravatarprefs.default_mode >= DEF_MODE_404) {
209 return g_strdup_printf("%s/%s?s=%u&d=%s",
210 base, md5, AVATAR_SIZE,
211 def_mode[libravatarprefs.default_mode - 10]);
212 } else if (libravatarprefs.default_mode == DEF_MODE_URL) {
213 return g_strdup_printf("%s/%s?s=%u&d=%s",
214 base, md5, AVATAR_SIZE,
215 libravatarprefs.default_mode_url);
216 } else if (libravatarprefs.default_mode == DEF_MODE_NONE) {
217 return g_strdup_printf("%s/%s?s=%u",
218 base, md5, AVATAR_SIZE);
221 g_warning("invalid libravatar default mode: %d", libravatarprefs.default_mode);
225 static gboolean libravatar_image_render_hook(gpointer source, gpointer data)
227 AvatarRender *ar = (AvatarRender *)source;
228 GtkWidget *image = NULL;
229 gchar *a = NULL, *url = NULL;
232 debug_print("libravatar avatar_image_render invoked\n");
234 a = procmsg_msginfo_get_avatar(ar->full_msginfo, AVATAR_LIBRAVATAR);
238 md5_hex_digest(md5sum, a);
239 /* try missing cache */
240 if (is_missing_md5(libravatarmisses, md5sum)) {
244 image = image_widget_from_cached_md5(md5sum);
246 if (ar->image) /* previous plugin set one */
247 gtk_widget_destroy(ar->image);
249 ar->type = AVATAR_LIBRAVATAR;
252 /* not cached copy: try network */
253 if (prefs_common_get_prefs()->work_offline) {
254 debug_print("working off-line: libravatar network retrieval skipped\n");
257 base = federated_base_url_from_address(a);
258 url = libravatar_url_for_md5(base, md5sum);
260 image = image_widget_from_url(url, md5sum);
263 if (ar->image) /* previous plugin set one */
264 gtk_widget_destroy(ar->image);
266 ar->type = AVATAR_LIBRAVATAR;
274 return FALSE; /* keep rendering */
277 static gint cache_dir_init()
279 cache_dir = libravatar_cache_init(def_mode, DEF_MODE_MM - 10, DEF_MODE_RETRO - 10);
280 cm_return_val_if_fail (cache_dir != NULL, -1);
285 static gint missing_cache_init()
287 gchar *cache_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
288 LIBRAVATAR_CACHE_DIR, G_DIR_SEPARATOR_S,
289 LIBRAVATAR_MISSING_FILE, NULL);
291 libravatarmisses = missing_load_from_file(cache_file);
294 if (libravatarmisses == NULL)
300 static void missing_cache_done()
304 if (libravatarmisses != NULL) {
305 cache_file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
306 LIBRAVATAR_CACHE_DIR, G_DIR_SEPARATOR_S,
307 LIBRAVATAR_MISSING_FILE, NULL);
308 missing_save_to_file(libravatarmisses, cache_file);
310 g_hash_table_destroy(libravatarmisses);
314 static void unregister_hooks()
316 if (render_hook_id != -1) {
317 hooks_unregister_hook(AVATAR_IMAGE_RENDER_HOOKLIST,
321 if (update_hook_id != -1) {
322 hooks_unregister_hook(AVATAR_HEADER_UPDATE_HOOKLIST,
331 * @param error For storing the returned error message.
333 * @return 0 if initialization succeeds, -1 on failure.
335 gint plugin_init(gchar **error)
337 if (!check_plugin_version(MAKE_NUMERIC_VERSION(3,9,3,29),
338 VERSION_NUMERIC, _("Libravatar"), error))
340 /* get info from headers */
341 update_hook_id = hooks_register_hook(AVATAR_HEADER_UPDATE_HOOKLIST,
342 libravatar_header_update_hook,
344 if (update_hook_id == -1) {
345 *error = g_strdup(_("Failed to register avatar header update hook"));
348 /* get image for displaying */
349 render_hook_id = hooks_register_hook(AVATAR_IMAGE_RENDER_HOOKLIST,
350 libravatar_image_render_hook,
352 if (render_hook_id == -1) {
354 *error = g_strdup(_("Failed to register avatar image render hook"));
358 if (cache_dir_init() == -1) {
360 *error = g_strdup(_("Failed to create avatar image cache directory"));
363 /* preferences page */
364 libravatar_prefs_init();
366 curl_global_init(CURL_GLOBAL_DEFAULT);
368 if (missing_cache_init() == -1) {
370 *error = g_strdup(_("Failed to load missing items cache"));
373 debug_print("Libravatar plugin loaded\n");
379 * Destructor for the plugin.
380 * Unregister the callback function and frees matcher.
382 * @return Always TRUE.
384 gboolean plugin_done(void)
387 libravatar_prefs_done();
388 missing_cache_done();
389 if (cache_dir != NULL)
391 debug_print("Libravatar plugin unloaded\n");
397 * Get the name of the plugin.
399 * @return The plugin's name, maybe translated.
401 const gchar *plugin_name(void)
403 return _("Libravatar");
407 * Get the description of the plugin.
409 * @return The plugin's description, maybe translated.
411 const gchar *plugin_desc(void)
413 return _("Display libravatar profiles' images for mail messages. More\n"
414 "info about libravatar at http://www.libravatar.org/. If you have\n"
415 "a gravatar.com profile but not a libravatar one, those will also\n"
416 "be retrieved (when redirections are allowed in plugin config).\n"
417 "Plugin config page is available from main window at:\n"
418 "/Configuration/Preferences/Plugins/Libravatar.\n\n"
419 "This plugin uses libcurl to retrieve images, so if you're behind a\n"
420 "proxy please refer to curl(1) manpage for details on 'http_proxy'\n"
421 "configuration. More details about this and others on README file.\n\n"
422 "Feedback to <ricardo@mones.org> is welcome.\n");
426 * Get the kind of plugin.
428 * @return The "GTK2" constant.
430 const gchar *plugin_type(void)
436 * Get the license acronym the plugin is released under.
438 * @return The "GPL3+" constant.
440 const gchar *plugin_licence(void)
446 * Get the version of the plugin.
448 * @return The current version string.
450 const gchar *plugin_version(void)
456 * Get the features implemented by the plugin.
458 * @return A constant PluginFeature structure with the features.
460 struct PluginFeature *plugin_provides(void)
462 static struct PluginFeature features[] =
463 { {PLUGIN_OTHER, N_("Libravatar")},
464 {PLUGIN_NOTHING, NULL}};