6d0fdac76ddbf5ac3e56707cb7a32e61dc1aa109
[claws.git] / src / plugins / libravatar / libravatar_image.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2016 Ricardo Mones and the Claws Mail Team
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <curl/curl.h>
26 #include <pthread.h>
27
28 #include <common/claws.h>
29 #include <prefs_common.h>
30 #include <file-utils.h>
31
32 #include "libravatar.h"
33 #include "libravatar_prefs.h"
34 #include "libravatar_missing.h"
35 #include "libravatar_image.h"
36
37 static size_t write_image_data_cb(void *ptr, size_t size, size_t nmemb, void *stream)
38 {
39         size_t written = claws_fwrite(ptr, size, nmemb, (FILE *)stream);
40         debug_print("received %"G_GSIZE_FORMAT" bytes from avatar server\n", written);
41
42         return written;
43 }
44
45 static GdkPixbuf *image_pixbuf_from_filename(const gchar *filename)
46 {
47         GdkPixbuf *picture = NULL;
48         GError *error = NULL;
49         gint w, h;
50
51         gdk_pixbuf_get_file_info(filename, &w, &h);
52
53         if (w != AVATAR_SIZE || h != AVATAR_SIZE)
54                 /* server can provide a different size from the requested in URL */
55                 picture = gdk_pixbuf_new_from_file_at_scale(
56                                 filename, AVATAR_SIZE, AVATAR_SIZE, TRUE, &error);
57         else    /* exact size */
58                 picture = gdk_pixbuf_new_from_file(filename, &error);
59
60         if (error != NULL) {
61                 g_warning("failed to load image '%s': %s", filename, error->message);
62                 g_error_free(error);
63         } else {
64                 if (!picture)
65                         g_warning("failed to load image '%s': no error returned!", filename);
66         }
67
68         return picture;
69 }
70
71 static GdkPixbuf *pixbuf_from_url(const gchar *url, const gchar *md5, const gchar *filename) {
72         GdkPixbuf *image = NULL;
73         FILE *file;
74         CURL *curl;
75         CURLcode res;
76         long filesize;
77
78         file = claws_fopen(filename, "wb");
79         if (file == NULL) {
80                 g_warning("could not open '%s' for writing", filename);
81                 return NULL;
82         }
83         curl = curl_easy_init();
84         if (curl == NULL) {
85                 g_warning("could not initialize curl to get image from URL");
86                 unlink(filename);
87                 claws_fclose(file);
88                 return NULL;
89         }
90
91         curl_easy_setopt(curl, CURLOPT_URL, url);
92         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_image_data_cb);
93         /* make sure timeout is less than general IO timeout */
94         curl_easy_setopt(curl, CURLOPT_TIMEOUT,
95                         (libravatarprefs.timeout == 0
96                                 || libravatarprefs.timeout
97                                         > prefs_common_get_prefs()->io_timeout_secs)
98                         ? prefs_common_get_prefs()->io_timeout_secs
99                         : libravatarprefs.timeout);
100         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
101
102         if (libravatarprefs.allow_redirects) {
103                 long maxredirs = (libravatarprefs.default_mode == DEF_MODE_URL)
104                         ? libravatarprefs.max_redirects_url
105                         : ((libravatarprefs.default_mode == DEF_MODE_MM)
106                                 ? libravatarprefs.max_redirects_mm
107                                 : libravatarprefs.max_redirects_url);
108                 debug_print("setting max redirects to %ld\n", maxredirs);
109                 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
110                 curl_easy_setopt(curl, CURLOPT_MAXREDIRS, maxredirs);
111         }
112         curl_easy_setopt(curl, CURLOPT_FILE, file);
113         debug_print("retrieving URL to file: %s -> %s\n", url, filename);
114         res = curl_easy_perform(curl);
115         if (res != CURLE_OK) {
116                 debug_print("curl_easy_perfom failed: %s", curl_easy_strerror(res));
117                 unlink(filename);
118                 claws_fclose(file);
119         } else {
120                 filesize = ftell(file);
121                 claws_safe_fclose(file);
122                 if (filesize < MIN_PNG_SIZE)
123                         debug_print("not enough data for an avatar image: %ld bytes\n", filesize);
124                 else
125                         image = image_pixbuf_from_filename(filename);
126
127                 if (!libravatarprefs.cache_icons || filesize == 0) {
128                         if (g_unlink(filename) < 0)
129                                 g_warning("failed to delete cache file '%s'", filename);
130                 }
131
132                 if (filesize == 0)
133                         missing_add_md5(libravatarmisses, md5);
134         }
135
136         curl_easy_cleanup(curl);
137
138         return image;
139 }
140
141 static void *get_image_thread(void *arg) {
142         AvatarImageFetch *ctx = (AvatarImageFetch *)arg;
143
144         /* get image */
145         ctx->pixbuf = pixbuf_from_url(ctx->url, ctx->md5, ctx->filename);
146         /* done here */
147         ctx->ready = TRUE;
148
149         return arg;
150 }
151
152 GdkPixbuf *libravatar_image_fetch(AvatarImageFetch *ctx)
153 {
154 #ifdef USE_PTHREAD
155         pthread_t pt;
156 #endif
157
158         g_return_val_if_fail(ctx != NULL, NULL);
159
160 #ifdef USE_PTHREAD
161         if (pthread_create(&pt, NULL, get_image_thread, (void *)ctx) != 0) {
162                 debug_print("synchronous image fetching (couldn't create thread)\n");
163                 get_image_thread(ctx);
164         } else {
165                 debug_print("waiting for thread completion\n");
166                 /*
167                 while (!ctx->ready ) {
168                         claws_do_idle();
169                 }
170                 */
171                 pthread_join(pt, NULL);
172                 debug_print("thread completed\n");
173         }
174 #else
175         debug_print("synchronous image fetching (pthreads unavailable)\n");
176         get_image_thread(ctx);
177 #endif
178         if (ctx->pixbuf == NULL) {
179                 g_warning("could not get image");
180         }
181         return ctx->pixbuf;
182 }