Remove inclusion of sys/socket.h from nntp-thread.c
[claws.git] / src / plugins / libravatar / libravatar.c
blob54d933979013cf948069d3a9f45d0f88d54f408a
1 /*
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include <glib.h>
25 #include <glib/gi18n.h>
27 #include <curl/curl.h>
29 #include "version.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"
38 #include "procmsg.h"
39 #include "utils.h"
40 #include "md5.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 */
45 "mm",
46 "identicon",
47 "monsterid",
48 "wavatar",
49 "retro",
50 "robohash",
51 "pagan"
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:")) {
65 gchar *a, *lower;
67 a = g_strdup(acd->content);
68 extract_address(a);
70 /* string to lower */
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);
76 g_free(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");
89 goto default_url;
92 base_url = federated_url_for_address(address);
93 if (base_url != NULL) {
94 return base_url;
97 default_url:
98 #endif
99 return g_strdup(libravatarprefs.base_url);
102 static GtkWidget *image_widget_from_pixbuf(GdkPixbuf *picture)
104 GtkWidget *image = NULL;
106 if (picture) {
107 image = gtk_image_new_from_pixbuf(picture);
108 g_object_unref(picture);
109 } else
110 g_warning("null picture returns null widget");
112 return image;
115 static GtkWidget *image_widget_from_filename(const gchar *filename)
117 GdkPixbuf *picture = NULL;
118 GError *error = NULL;
119 gint w, h;
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);
130 if (error != NULL) {
131 g_warning("failed to load image '%s': %s", filename, error->message);
132 g_error_free(error);
133 return NULL;
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;
156 aif.url = url;
157 aif.md5 = md5;
158 aif.filename = cache_name_for_md5(md5);
159 libravatar_image_fetch(&aif);
160 if (aif.pixbuf) {
161 image = gtk_image_new_from_pixbuf(aif.pixbuf);
162 g_object_unref(aif.pixbuf);
164 g_free(aif.filename);
166 return image;
169 static gboolean is_recent_enough(const gchar *filename)
171 GStatBuf s;
172 time_t t;
174 if (libravatarprefs.cache_icons) {
175 t = time(NULL);
176 if (t != (time_t)-1 && !g_stat(filename, &s)) {
177 if (t - s.st_ctime <= libravatarprefs.cache_interval * 3600)
178 return TRUE;
182 return FALSE; /* re-download */
185 static GtkWidget *image_widget_from_cached_md5(const gchar *md5)
187 GtkWidget *image = NULL;
188 gchar *filename;
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);
195 g_free(filename);
197 return image;
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);
210 g_free(escaped);
211 return url;
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);
218 return NULL;
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;
226 gchar md5sum[33];
228 debug_print("libravatar avatar_image_render invoked\n");
230 a = procmsg_msginfo_get_avatar(ar->full_msginfo, AVATAR_LIBRAVATAR);
231 if (a != NULL) {
232 gchar *base;
234 md5_hex_digest(md5sum, a);
235 /* try missing cache */
236 if (is_missing_md5(libravatarmisses, md5sum)) {
237 return FALSE;
239 /* try disk cache */
240 image = image_widget_from_cached_md5(md5sum);
241 if (image != NULL) {
242 if (ar->image) /* previous plugin set one */
243 gtk_widget_destroy(ar->image);
244 ar->image = image;
245 ar->type = AVATAR_LIBRAVATAR;
246 return FALSE;
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");
251 return FALSE;
253 base = federated_base_url_from_address(a);
254 url = libravatar_url_for_md5(base, md5sum);
255 if (url != NULL) {
256 image = image_widget_from_url(url, md5sum);
257 g_free(url);
258 if (image != NULL) {
259 if (ar->image) /* previous plugin set one */
260 gtk_widget_destroy(ar->image);
261 ar->image = image;
262 ar->type = AVATAR_LIBRAVATAR;
265 g_free(base);
267 return TRUE;
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);
278 return 0;
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);
288 g_free(cache_file);
290 if (libravatarmisses == NULL)
291 return -1;
293 return 0;
296 static void missing_cache_done()
298 gchar *cache_file;
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);
305 g_free(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,
314 render_hook_id);
315 render_hook_id = HOOK_NONE;
317 if (update_hook_id != HOOK_NONE) {
318 hooks_unregister_hook(AVATAR_HEADER_UPDATE_HOOKLIST,
319 update_hook_id);
320 update_hook_id = HOOK_NONE;
325 * Initialize plugin.
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))
335 return -1;
336 /* get info from headers */
337 update_hook_id = hooks_register_hook(AVATAR_HEADER_UPDATE_HOOKLIST,
338 libravatar_header_update_hook,
339 NULL);
340 if (update_hook_id == HOOK_NONE) {
341 *error = g_strdup(_("Failed to register avatar header update hook"));
342 return -1;
344 /* get image for displaying */
345 render_hook_id = hooks_register_hook(AVATAR_IMAGE_RENDER_HOOKLIST,
346 libravatar_image_render_hook,
347 NULL);
348 if (render_hook_id == HOOK_NONE) {
349 unregister_hooks();
350 *error = g_strdup(_("Failed to register avatar image render hook"));
351 return -1;
353 /* cache dir */
354 if (cache_dir_init() == -1) {
355 unregister_hooks();
356 *error = g_strdup(_("Failed to create avatar image cache directory"));
357 return -1;
359 /* preferences page */
360 libravatar_prefs_init();
361 /* curl library */
362 curl_global_init(CURL_GLOBAL_DEFAULT);
363 /* missing cache */
364 if (missing_cache_init() == -1) {
365 unregister_hooks();
366 *error = g_strdup(_("Failed to load missing items cache"));
367 return -1;
369 debug_print("Libravatar plugin loaded\n");
371 return 0;
375 * Destructor for the plugin.
376 * Unregister the callback function and frees matcher.
378 * @return Always TRUE.
380 gboolean plugin_done(void)
382 unregister_hooks();
383 libravatar_prefs_done();
384 missing_cache_done();
385 if (cache_dir != NULL)
386 g_free(cache_dir);
387 debug_print("Libravatar plugin unloaded\n");
389 return TRUE;
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)
428 return "GTK3";
432 * Get the license acronym the plugin is released under.
434 * @return The "GPL3+" constant.
436 const gchar *plugin_licence(void)
438 return "GPL3+";
442 * Get the version of the plugin.
444 * @return The current version string.
446 const gchar *plugin_version(void)
448 return VERSION;
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}};
462 return features;