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 */
54 static gulong update_hook_id
= HOOK_NONE
;
55 static gulong render_hook_id
= HOOK_NONE
;
56 static gchar
*cache_dir
= NULL
; /* dir-separator terminated */
58 static gboolean
libravatar_header_update_hook(gpointer source
, gpointer data
)
60 AvatarCaptureData
*acd
= (AvatarCaptureData
*)source
;
62 debug_print("libravatar avatar_header_update invoked\n");
64 if (!strcmp(acd
->header
, "From:")) {
67 a
= g_strdup(acd
->content
);
71 for (lower
= a
; *lower
; lower
++)
72 *lower
= g_ascii_tolower(*lower
);
74 debug_print("libravatar added '%s'\n", a
);
75 procmsg_msginfo_add_avatar(acd
->msginfo
, AVATAR_LIBRAVATAR
, a
);
79 return FALSE
; /* keep getting */
82 static gchar
*federated_base_url_from_address(const gchar
*address
)
84 #if defined USE_GNUTLS
85 gchar
*base_url
= NULL
;
87 if (!libravatarprefs
.allow_federated
) {
88 debug_print("federated domains disabled by configuration\n");
92 base_url
= federated_url_for_address(address
);
93 if (base_url
!= NULL
) {
99 return g_strdup(libravatarprefs
.base_url
);
102 static GtkWidget
*image_widget_from_pixbuf(GdkPixbuf
*picture
)
104 GtkWidget
*image
= NULL
;
107 image
= gtk_image_new_from_pixbuf(picture
);
108 g_object_unref(picture
);
110 g_warning("null picture returns null widget");
115 static GtkWidget
*image_widget_from_filename(const gchar
*filename
)
117 GdkPixbuf
*picture
= NULL
;
118 GError
*error
= NULL
;
121 gdk_pixbuf_get_file_info(filename
, &w
, &h
);
123 if (w
!= AVATAR_SIZE
|| h
!= AVATAR_SIZE
)
124 /* server can provide a different size from the requested in URL */
125 picture
= gdk_pixbuf_new_from_file_at_scale(
126 filename
, AVATAR_SIZE
, AVATAR_SIZE
, TRUE
, &error
);
127 else /* exact size */
128 picture
= gdk_pixbuf_new_from_file(filename
, &error
);
131 g_warning("failed to load image '%s': %s", filename
, error
->message
);
136 return image_widget_from_pixbuf(picture
);
139 static gchar
*cache_name_for_md5(const gchar
*md5
)
141 if (libravatarprefs
.default_mode
>= DEF_MODE_MM
142 && libravatarprefs
.default_mode
<= DEF_MODE_RETRO
) {
143 /* cache dir for generated avatars */
144 return g_strconcat(cache_dir
, def_mode
[libravatarprefs
.default_mode
- 10],
145 G_DIR_SEPARATOR_S
, md5
, NULL
);
147 /* default cache dir */
148 return g_strconcat(cache_dir
, md5
, NULL
);
151 static GtkWidget
*image_widget_from_url(const gchar
*url
, const gchar
*md5
)
153 GtkWidget
*image
= NULL
;
154 AvatarImageFetch aif
;
158 aif
.filename
= cache_name_for_md5(md5
);
159 libravatar_image_fetch(&aif
);
161 image
= gtk_image_new_from_pixbuf(aif
.pixbuf
);
162 g_object_unref(aif
.pixbuf
);
164 g_free(aif
.filename
);
169 static gboolean
is_recent_enough(const gchar
*filename
)
174 if (libravatarprefs
.cache_icons
) {
176 if (t
!= (time_t)-1 && !g_stat(filename
, &s
)) {
177 if (t
- s
.st_ctime
<= libravatarprefs
.cache_interval
* 3600)
182 return FALSE
; /* re-download */
185 static GtkWidget
*image_widget_from_cached_md5(const gchar
*md5
)
187 GtkWidget
*image
= NULL
;
190 filename
= cache_name_for_md5(md5
);
191 if (is_file_exist(filename
) && is_recent_enough(filename
)) {
192 debug_print("found cached image for %s\n", md5
);
193 image
= image_widget_from_filename(filename
);
200 static gchar
*libravatar_url_for_md5(const gchar
*base
, const gchar
*md5
)
202 if (libravatarprefs
.default_mode
>= DEF_MODE_404
) {
203 return g_strdup_printf("%s/%s?s=%u&d=%s",
204 base
, md5
, AVATAR_SIZE
,
205 def_mode
[libravatarprefs
.default_mode
- 10]);
206 } else if (libravatarprefs
.default_mode
== DEF_MODE_URL
) {
207 gchar
*escaped
= g_uri_escape_string(libravatarprefs
.default_mode_url
, "/", TRUE
);
208 gchar
*url
= g_strdup_printf("%s/%s?s=%u&d=%s",
209 base
, md5
, AVATAR_SIZE
, escaped
);
212 } else if (libravatarprefs
.default_mode
== DEF_MODE_NONE
) {
213 return g_strdup_printf("%s/%s?s=%u&d=404",
214 base
, md5
, AVATAR_SIZE
);
217 g_warning("invalid libravatar default mode: %d", libravatarprefs
.default_mode
);
221 static gboolean
libravatar_image_render_hook(gpointer source
, gpointer data
)
223 AvatarRender
*ar
= (AvatarRender
*)source
;
224 GtkWidget
*image
= NULL
;
225 gchar
*a
= NULL
, *url
= NULL
;
228 debug_print("libravatar avatar_image_render invoked\n");
230 a
= procmsg_msginfo_get_avatar(ar
->full_msginfo
, AVATAR_LIBRAVATAR
);
234 md5_hex_digest(md5sum
, a
);
235 /* try missing cache */
236 if (is_missing_md5(libravatarmisses
, md5sum
)) {
240 image
= image_widget_from_cached_md5(md5sum
);
242 if (ar
->image
) /* previous plugin set one */
243 gtk_widget_destroy(ar
->image
);
245 ar
->type
= AVATAR_LIBRAVATAR
;
248 /* not cached copy: try network */
249 if (prefs_common_get_prefs()->work_offline
) {
250 debug_print("working off-line: libravatar network retrieval skipped\n");
253 base
= federated_base_url_from_address(a
);
254 url
= libravatar_url_for_md5(base
, md5sum
);
256 image
= image_widget_from_url(url
, md5sum
);
259 if (ar
->image
) /* previous plugin set one */
260 gtk_widget_destroy(ar
->image
);
262 ar
->type
= AVATAR_LIBRAVATAR
;
270 return FALSE
; /* keep rendering */
273 static gint
cache_dir_init()
275 cache_dir
= libravatar_cache_init(def_mode
, DEF_MODE_MM
- 10, DEF_MODE_RETRO
- 10);
276 cm_return_val_if_fail (cache_dir
!= NULL
, -1);
281 static gint
missing_cache_init()
283 gchar
*cache_file
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
284 LIBRAVATAR_CACHE_DIR
, G_DIR_SEPARATOR_S
,
285 LIBRAVATAR_MISSING_FILE
, NULL
);
287 libravatarmisses
= missing_load_from_file(cache_file
);
290 if (libravatarmisses
== NULL
)
296 static void missing_cache_done()
300 if (libravatarmisses
!= NULL
) {
301 cache_file
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
302 LIBRAVATAR_CACHE_DIR
, G_DIR_SEPARATOR_S
,
303 LIBRAVATAR_MISSING_FILE
, NULL
);
304 missing_save_to_file(libravatarmisses
, cache_file
);
306 g_hash_table_destroy(libravatarmisses
);
310 static void unregister_hooks()
312 if (render_hook_id
!= HOOK_NONE
) {
313 hooks_unregister_hook(AVATAR_IMAGE_RENDER_HOOKLIST
,
315 render_hook_id
= HOOK_NONE
;
317 if (update_hook_id
!= HOOK_NONE
) {
318 hooks_unregister_hook(AVATAR_HEADER_UPDATE_HOOKLIST
,
320 update_hook_id
= HOOK_NONE
;
327 * @param error For storing the returned error message.
329 * @return 0 if initialization succeeds, -1 on failure.
331 gint
plugin_init(gchar
**error
)
333 if (!check_plugin_version(MAKE_NUMERIC_VERSION(3,9,3,29),
334 VERSION_NUMERIC
, _("Libravatar"), error
))
336 /* get info from headers */
337 update_hook_id
= hooks_register_hook(AVATAR_HEADER_UPDATE_HOOKLIST
,
338 libravatar_header_update_hook
,
340 if (update_hook_id
== HOOK_NONE
) {
341 *error
= g_strdup(_("Failed to register avatar header update hook"));
344 /* get image for displaying */
345 render_hook_id
= hooks_register_hook(AVATAR_IMAGE_RENDER_HOOKLIST
,
346 libravatar_image_render_hook
,
348 if (render_hook_id
== HOOK_NONE
) {
350 *error
= g_strdup(_("Failed to register avatar image render hook"));
354 if (cache_dir_init() == -1) {
356 *error
= g_strdup(_("Failed to create avatar image cache directory"));
359 /* preferences page */
360 libravatar_prefs_init();
362 curl_global_init(CURL_GLOBAL_DEFAULT
);
364 if (missing_cache_init() == -1) {
366 *error
= g_strdup(_("Failed to load missing items cache"));
369 debug_print("Libravatar plugin loaded\n");
375 * Destructor for the plugin.
376 * Unregister the callback function and frees matcher.
378 * @return Always TRUE.
380 gboolean
plugin_done(void)
383 libravatar_prefs_done();
384 missing_cache_done();
385 if (cache_dir
!= NULL
)
387 debug_print("Libravatar plugin unloaded\n");
393 * Get the name of the plugin.
395 * @return The plugin's name, maybe translated.
397 const gchar
*plugin_name(void)
399 return _("Libravatar");
403 * Get the description of the plugin.
405 * @return The plugin's description, maybe translated.
407 const gchar
*plugin_desc(void)
409 return _("Display libravatar profiles' images for mail messages. More\n"
410 "info about libravatar at http://www.libravatar.org/. If you have\n"
411 "a gravatar.com profile but not a libravatar one, those will also\n"
412 "be retrieved (when redirections are allowed in plugin config).\n"
413 "Plugin config page is available from main window at:\n"
414 "/Configuration/Preferences/Plugins/Libravatar.\n\n"
415 "This plugin uses libcurl to retrieve images, so if you're behind a\n"
416 "proxy please refer to curl(1) manpage for details on 'http_proxy'\n"
417 "configuration. More details about this and others on README file.\n\n"
418 "Feedback to <ricardo@mones.org> is welcome.\n");
422 * Get the kind of plugin.
424 * @return The "GTK3" constant.
426 const gchar
*plugin_type(void)
432 * Get the license acronym the plugin is released under.
434 * @return The "GPL3+" constant.
436 const gchar
*plugin_licence(void)
442 * Get the version of the plugin.
444 * @return The current version string.
446 const gchar
*plugin_version(void)
452 * Get the features implemented by the plugin.
454 * @return A constant PluginFeature structure with the features.
456 struct PluginFeature
*plugin_provides(void)
458 static struct PluginFeature features
[] =
459 { {PLUGIN_OTHER
, N_("Libravatar")},
460 {PLUGIN_NOTHING
, NULL
}};