Libravatar: refactor image retrieval
authorRicardo Mones <ricardo@mones.org>
Mon, 17 Oct 2016 12:09:25 +0000 (14:09 +0200)
committerRicardo Mones <ricardo@mones.org>
Mon, 17 Oct 2016 12:09:25 +0000 (14:09 +0200)
And use a synchronous thread for fetching, in preparation
of a future asynchronous fetching. Thanks Andrej for the
ideas.

src/plugins/libravatar/Makefile.am
src/plugins/libravatar/libravatar.c
src/plugins/libravatar/libravatar_image.c [new file with mode: 0644]
src/plugins/libravatar/libravatar_image.h [new file with mode: 0644]

index 7edf1f5..4fcc57c 100644 (file)
@@ -79,6 +79,7 @@ libravatar_la_SOURCES = \
        libravatar.c libravatar.h \
        libravatar_prefs.c libravatar_prefs.h \
        libravatar_cache.c libravatar_cache.h \
+       libravatar_image.c libravatar_image.h \
        libravatar_missing.c libravatar_missing.h \
        libravatar_federation.c libravatar_federation.h
 
index e36829e..7ce4341 100644 (file)
@@ -30,6 +30,7 @@
 #include "libravatar.h"
 #include "libravatar_prefs.h"
 #include "libravatar_cache.h"
+#include "libravatar_image.h"
 #include "libravatar_missing.h"
 #include "libravatar_federation.h"
 #include "prefs_common.h"
@@ -96,9 +97,21 @@ default_url:
        return g_strdup(libravatarprefs.base_url);
 }
 
-static GtkWidget *image_widget_from_filename(const gchar *filename)
+static GtkWidget *image_widget_from_pixbuf(GdkPixbuf *picture)
 {
        GtkWidget *image = NULL;
+
+       if (picture) {
+               image = gtk_image_new_from_pixbuf(picture);
+               g_object_unref(picture);
+       } else
+               g_warning("null picture returns null widget");
+
+       return image;
+}
+
+static GtkWidget *image_widget_from_filename(const gchar *filename)
+{
        GdkPixbuf *picture = NULL;
        GError *error = NULL;
        gint w, h;
@@ -115,15 +128,10 @@ static GtkWidget *image_widget_from_filename(const gchar *filename)
        if (error != NULL) {
                g_warning("failed to load image '%s': %s", 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!", filename);
+               return NULL;
        }
 
-       return image;
+       return image_widget_from_pixbuf(picture);
 }
 
 static gchar *cache_name_for_md5(const gchar *md5)
@@ -149,61 +157,17 @@ static size_t write_image_data_cb(void *ptr, size_t size, size_t nmemb, void *st
 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");
-               return NULL;
+       AvatarImageFetch aif;
+
+       aif.url = url;
+       aif.md5 = md5;
+       aif.filename = cache_name_for_md5(md5);
+       libravatar_image_fetch(&aif);
+       if (aif.pixbuf) {
+               image = gtk_image_new_from_pixbuf(aif.pixbuf);
+               g_object_unref(aif.pixbuf);
        }
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_image_data_cb);
-       /* make sure timeout is less than general IO timeout */
-       curl_easy_setopt(curl, CURLOPT_TIMEOUT,
-                       (libravatarprefs.timeout == 0
-                               || libravatarprefs.timeout
-                                       > prefs_common_get_prefs()->io_timeout_secs)
-                       ? prefs_common_get_prefs()->io_timeout_secs
-                       : libravatarprefs.timeout);
-       curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
-
-       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_URL)? 3L
-                               : ((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 || filesize == 0) {
-                       if (g_unlink(filename) < 0)
-                               g_warning("failed to delete cache file '%s'", filename);
-               }
-
-               if (filesize == 0)
-                       missing_add_md5(libravatarmisses, md5);
-       } else {
-               g_warning("could not open '%s' for writing", filename);
-       }
-       curl_easy_cleanup(curl);
-       g_free(filename);
+       g_free(aif.filename);
 
        return image;
 }
diff --git a/src/plugins/libravatar/libravatar_image.c b/src/plugins/libravatar/libravatar_image.c
new file mode 100644 (file)
index 0000000..fa1f78a
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2016 Ricardo Mones and the Claws Mail Team
+ *
+ * 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 <curl/curl.h>
+#include <pthread.h>
+
+#include <common/claws.h>
+#include <prefs_common.h>
+
+#include "libravatar.h"
+#include "libravatar_prefs.h"
+#include "libravatar_missing.h"
+#include "libravatar_image.h"
+
+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 GdkPixbuf *image_pixbuf_from_filename(const gchar *filename)
+{
+       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", filename, error->message);
+               g_error_free(error);
+       } else {
+               if (!picture)
+                       g_warning("failed to load image '%s': no error returned!", filename);
+       }
+
+       return picture;
+}
+
+static GdkPixbuf *pixbuf_from_url(const gchar *url, const gchar *md5, const gchar *filename) {
+       GdkPixbuf *image = NULL;
+       FILE *file;
+       CURL *curl;
+       long filesize;
+
+       file = fopen(filename, "wb");
+       if (file == NULL) {
+               g_warning("could not open '%s' for writing", filename);
+               return NULL;
+       }
+       curl = curl_easy_init();
+       if (curl == NULL) {
+               g_warning("could not initialize curl to get image from URL");
+               return NULL;
+       }
+
+       curl_easy_setopt(curl, CURLOPT_URL, url);
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_image_data_cb);
+       /* make sure timeout is less than general IO timeout */
+       curl_easy_setopt(curl, CURLOPT_TIMEOUT,
+                       (libravatarprefs.timeout == 0
+                               || libravatarprefs.timeout
+                                       > prefs_common_get_prefs()->io_timeout_secs)
+                       ? prefs_common_get_prefs()->io_timeout_secs
+                       : libravatarprefs.timeout);
+       curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+
+       if (libravatarprefs.allow_redirects) {
+               long maxredirs = (libravatarprefs.default_mode == DEF_MODE_URL)? 3L
+                       : ((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_pixbuf_from_filename(filename);
+
+       if (!libravatarprefs.cache_icons || filesize == 0) {
+               if (g_unlink(filename) < 0)
+                       g_warning("failed to delete cache file '%s'", filename);
+       }
+
+       if (filesize == 0)
+               missing_add_md5(libravatarmisses, md5);
+
+       curl_easy_cleanup(curl);
+
+       return image;
+}
+
+static void *get_image_thread(void *arg) {
+       AvatarImageFetch *ctx = (AvatarImageFetch *)arg;
+
+       /* get image */
+       ctx->pixbuf = pixbuf_from_url(ctx->url, ctx->md5, ctx->filename);
+       /* done here */
+       ctx->ready = TRUE;
+
+       return arg;
+}
+
+GdkPixbuf *libravatar_image_fetch(AvatarImageFetch *ctx)
+{
+#ifdef USE_PTHREAD
+       pthread_t pt;
+#endif
+
+       g_return_if_fail(ctx != NULL);
+
+#ifdef USE_PTHREAD
+       if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, get_image_thread, (void *)ctx) != 0) {
+               debug_print("synchronous image fetching (couldn't create thread)\n");
+               get_image_thread(ctx);
+       } else {
+               debug_print("waiting for thread completion\n");
+               /*
+               while (!ctx->ready ) {
+                       claws_do_idle();
+               }
+               */
+               pthread_join(pt, NULL);
+               debug_print("thread completed\n");
+       }
+#else
+       debug_print("synchronous image fetching (pthreads unavailable)\n");
+       get_image_thread(ctx);
+#endif
+       if (ctx->pixbuf == NULL) {
+               g_warning("could not get image");
+       }
+       return ctx->pixbuf;
+}
diff --git a/src/plugins/libravatar/libravatar_image.h b/src/plugins/libravatar/libravatar_image.h
new file mode 100644 (file)
index 0000000..dc298b7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2016 Ricardo Mones and the Claws Mail Team
+ *
+ * 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_IMAGE_H
+#define __LIBRAVATAR_IMAGE_H
+
+#include <glib.h>
+
+typedef struct _AvatarImageFetch       AvatarImageFetch;
+
+GdkPixbuf *libravatar_image_fetch(AvatarImageFetch *ctx);
+
+struct _AvatarImageFetch
+{
+       const gchar     *url;
+       const gchar     *md5;
+       gchar           *filename;
+       GdkPixbuf       *pixbuf;
+       gboolean        ready;
+};
+
+#endif