Make Litehtml image loading non-blocking using threads
[claws.git] / src / plugins / litehtml_viewer / container_linux_images.cpp
1 /*
2  * Claws Mail -- A GTK+ based, lightweight, and fast e-mail client
3  * Copyright(C) 2019 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  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write tothe Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #include "claws-features.h"
21 #endif
22
23 #include "common/utils.h"
24
25 #include "container_linux.h"
26 #include "http.h"
27 #include "lh_prefs.h"
28
29 static GdkPixbuf *lh_get_image(const litehtml::tchar_t* url)
30 {
31         GError *error = NULL;
32         GdkPixbuf *pixbuf = NULL;
33         http* http_loader = NULL;
34
35         if (!lh_prefs_get()->enable_remote_content) {
36                 debug_print("blocking download of image from '%s'\n", url);
37                 return NULL;
38         }
39
40         debug_print("allowing download of image from '%s'\n", url);
41
42         http_loader = new http();
43         GInputStream *image = http_loader->load_url(url, &error);
44
45         if (error || !image) {
46                 if (error) {
47                         g_warning("lh_get_image: Could not create pixbuf %s",
48                                         error->message);
49                         g_clear_error(&error);
50                 }
51                 goto theend;
52         }
53
54         pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
55         if (error) {
56                 g_warning("lh_get_image: Could not create pixbuf %s",
57                                 error->message);
58                 pixbuf = NULL;
59                 g_clear_error(&error);
60         }
61
62 theend:
63         if (http_loader) {
64                 delete http_loader;
65         }
66
67         return pixbuf;
68 }
69
70 struct FetchCtx {
71         container_linux *container;
72         gchar *url;
73 };
74
75 static void get_image_threaded(GTask *task, gpointer source, gpointer task_data, GCancellable *cancellable)
76 {
77         struct FetchCtx *ctx = (struct FetchCtx *)task_data;
78         GdkPixbuf *pixbuf = lh_get_image(ctx->url);
79
80         g_task_return_pointer(task, pixbuf, NULL);
81 }
82
83 static void get_image_callback(GObject *source, GAsyncResult *res, gpointer user_data)
84 {
85         GdkPixbuf *pixbuf;
86         struct FetchCtx *ctx = (struct FetchCtx *)user_data;
87
88         pixbuf = GDK_PIXBUF(g_task_propagate_pointer(G_TASK(res), NULL));
89
90         if (pixbuf != NULL) {
91                 ctx->container->add_image_to_cache(ctx->url, pixbuf);
92                 ctx->container->redraw(true);
93         }
94
95         g_free(ctx->url);
96         g_free(ctx);
97 }
98
99 void container_linux::load_image( const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, bool redraw_on_ready )
100 {
101         litehtml::tstring url;
102         make_url(src, baseurl, url);
103         bool found = false;
104
105         lock_images_cache();
106
107         for (auto ii = m_images.cbegin(); ii != m_images.cend(); ++ii) {
108                 const image *i = &(*ii);
109
110                 if (!strcmp(i->first.c_str(), url.c_str())) {
111                         found = true;
112                         break;
113                 }
114         }
115
116         unlock_images_cache();
117
118         if(!found) {
119                 struct FetchCtx *ctx = g_new(struct FetchCtx, 1);
120                 ctx->url = g_strdup(url.c_str());
121                 ctx->container = this;
122
123                 GTask *task = g_task_new(this, NULL, get_image_callback, ctx);
124                 g_task_set_task_data(task, ctx, NULL);
125                 g_task_run_in_thread(task, get_image_threaded);
126         } else {
127                 debug_print("found image in cache: '%s'\n", url.c_str());
128         }
129 }
130
131 void container_linux::get_image_size( const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, litehtml::size& sz )
132 {
133         litehtml::tstring url;
134         make_url(src, baseurl, url);
135         bool found = false;
136         const image *img = NULL;
137
138         lock_images_cache();
139
140         for (auto ii = m_images.cbegin(); ii != m_images.cend(); ++ii) {
141                 const image *i = &(*ii);
142                 if (i->first == url) {
143                         img = i;
144                         found = true;
145                         break;
146                 }
147         }
148
149         if(img != NULL)
150         {
151                 sz.width        = gdk_pixbuf_get_width(img->second);
152                 sz.height       = gdk_pixbuf_get_height(img->second);
153         } else
154         {
155                 sz.width        = 0;
156                 sz.height       = 0;
157         }
158
159         unlock_images_cache();
160 }
161
162 void container_linux::add_image_to_cache(const gchar *url, GdkPixbuf *image)
163 {
164         g_return_if_fail(url != NULL);
165         g_return_if_fail(image != NULL);
166
167         debug_print("adding image to cache: '%s'\n", url);
168         lock_images_cache();
169         m_images.push_back(std::make_pair(url, image));
170         unlock_images_cache();
171 }
172 void container_linux::lock_images_cache(void)
173 {
174         g_rec_mutex_lock(&m_images_lock);
175 }
176
177 void container_linux::unlock_images_cache(void)
178 {
179         g_rec_mutex_unlock(&m_images_lock);
180 }
181
182 void container_linux::clear_images()
183 {
184         lock_images_cache();
185
186         for(auto i = m_images.begin(); i != m_images.end(); ++i) {
187                 image *img = &(*i);
188
189                 if (img->second) {
190                         g_object_unref(img->second);
191                 }
192         }
193
194         m_images.clear();
195
196         unlock_images_cache();
197 }
198
199 gint container_linux::clear_images(gint desired_size)
200 {
201         gint size = 0;
202         gint num = 0;
203
204         lock_images_cache();
205
206         /* First, tally up size of all the stored GdkPixbufs and
207          * deallocate those which make the total size be above
208          * the desired_size limit. We will remove their list
209          * elements later. */
210         for (auto i = m_images.rbegin(); i != m_images.rend(); ++i) {
211                 image *img = &(*i);
212                 gint cursize;
213
214                 if (img->second == NULL)
215                         continue;
216
217                 cursize = gdk_pixbuf_get_byte_length(img->second);
218
219                 if (size + cursize > desired_size) {
220                         g_object_unref(img->second);
221                         img->second = NULL;
222                         num++;
223                 } else {
224                         size += cursize;
225                 }
226         }
227
228         /* Remove elements whose GdkPixbuf pointers point to NULL. */
229         m_images.remove_if([&](image _img) -> bool {
230                         if (_img.second == NULL)
231                                 return true;
232                         return false;
233                         });
234
235         unlock_images_cache();
236
237         return num;
238 }