Always remove local images from Litehtml image cache
[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         http_loader = new http();
36         GInputStream *image = http_loader->load_url(url, &error);
37
38         if (error || !image) {
39                 if (error) {
40                         g_warning("lh_get_image: Could not create pixbuf %s",
41                                         error->message);
42                         g_clear_error(&error);
43                 }
44                 goto theend;
45         }
46
47         pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
48         if (error) {
49                 g_warning("lh_get_image: Could not create pixbuf %s",
50                                 error->message);
51                 pixbuf = NULL;
52                 g_clear_error(&error);
53         }
54
55 theend:
56         if (http_loader) {
57                 delete http_loader;
58         }
59
60         return pixbuf;
61 }
62
63 struct FetchCtx {
64         container_linux *container;
65         gchar *url;
66 };
67
68 static void get_image_threaded(GTask *task, gpointer source, gpointer task_data, GCancellable *cancellable)
69 {
70         struct FetchCtx *ctx = (struct FetchCtx *)task_data;
71         GdkPixbuf *pixbuf = lh_get_image(ctx->url);
72
73         g_task_return_pointer(task, pixbuf, NULL);
74 }
75
76 static void get_image_callback(GObject *source, GAsyncResult *res, gpointer user_data)
77 {
78         GdkPixbuf *pixbuf;
79         struct FetchCtx *ctx = (struct FetchCtx *)user_data;
80
81         pixbuf = GDK_PIXBUF(g_task_propagate_pointer(G_TASK(res), NULL));
82
83         if (pixbuf != NULL) {
84                 ctx->container->add_image_to_cache(ctx->url, pixbuf);
85                 ctx->container->redraw(true);
86         }
87
88         g_free(ctx->url);
89         g_free(ctx);
90 }
91
92 void container_linux::load_image( const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, bool redraw_on_ready )
93 {
94         litehtml::tstring url;
95         make_url(src, baseurl, url);
96         bool found = false;
97
98         lock_images_cache();
99
100         for (auto ii = m_images.cbegin(); ii != m_images.cend(); ++ii) {
101                 const image *i = &(*ii);
102
103                 if (!strcmp(i->first.c_str(), url.c_str())) {
104                         found = true;
105                         break;
106                 }
107         }
108
109         unlock_images_cache();
110
111         if (!found) {
112                 struct FetchCtx *ctx = g_new(struct FetchCtx, 1);
113
114                 /* Attached images can be loaded into cache right here. */
115                 if (!strncmp(src, "cid:", 4)) {
116                         GdkPixbuf *pixbuf = get_local_image(src);
117
118                         if (pixbuf != NULL)
119                                 add_image_to_cache(src, pixbuf);
120
121                         return;
122                 }
123
124                 if (!lh_prefs_get()->enable_remote_content) {
125                         debug_print("blocking download of image from '%s'\n", src);
126                         return;
127                 }
128
129                 debug_print("allowing download of image from '%s'\n", src);
130
131                 ctx->url = g_strdup(url.c_str());
132                 ctx->container = this;
133
134                 GTask *task = g_task_new(this, NULL, get_image_callback, ctx);
135                 g_task_set_task_data(task, ctx, NULL);
136                 g_task_run_in_thread(task, get_image_threaded);
137         } else {
138                 debug_print("found image in cache: '%s'\n", url.c_str());
139         }
140 }
141
142 void container_linux::get_image_size( const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, litehtml::size& sz )
143 {
144         litehtml::tstring url;
145         make_url(src, baseurl, url);
146         bool found = false;
147         const image *img = NULL;
148
149         lock_images_cache();
150
151         for (auto ii = m_images.cbegin(); ii != m_images.cend(); ++ii) {
152                 const image *i = &(*ii);
153                 if (i->first == url) {
154                         img = i;
155                         found = true;
156                         break;
157                 }
158         }
159
160         if(img != NULL)
161         {
162                 sz.width        = gdk_pixbuf_get_width(img->second);
163                 sz.height       = gdk_pixbuf_get_height(img->second);
164         } else
165         {
166                 sz.width        = 0;
167                 sz.height       = 0;
168         }
169
170         unlock_images_cache();
171 }
172
173 void container_linux::add_image_to_cache(const gchar *url, GdkPixbuf *image)
174 {
175         g_return_if_fail(url != NULL);
176         g_return_if_fail(image != NULL);
177
178         debug_print("adding image to cache: '%s'\n", url);
179         lock_images_cache();
180         m_images.push_back(std::make_pair(url, image));
181         unlock_images_cache();
182 }
183 void container_linux::lock_images_cache(void)
184 {
185         g_rec_mutex_lock(&m_images_lock);
186 }
187
188 void container_linux::unlock_images_cache(void)
189 {
190         g_rec_mutex_unlock(&m_images_lock);
191 }
192
193 void container_linux::clear_images()
194 {
195         lock_images_cache();
196
197         for(auto i = m_images.begin(); i != m_images.end(); ++i) {
198                 image *img = &(*i);
199
200                 if (img->second) {
201                         g_object_unref(img->second);
202                 }
203         }
204
205         m_images.clear();
206
207         unlock_images_cache();
208 }
209
210 gint container_linux::clear_images(gint desired_size)
211 {
212         gint size = 0;
213         gint num = 0;
214
215         lock_images_cache();
216
217         /* First, remove all local images - the ones with "cid:"
218          * URL. We will remove their list elements later. */
219         for (auto i = m_images.rbegin(); i != m_images.rend(); ++i) {
220                 image *img = &(*i);
221
222                 if (!strncmp(img->first.c_str(), "cid:", 4)) {
223                         g_object_unref(img->second);
224                         img->second = NULL;
225                         num++;
226                 }
227         }
228
229         /* Now tally up size of all the stored GdkPixbufs and
230          * deallocate those which make the total size be above
231          * the desired_size limit. We will remove their list
232          * elements later. */
233         for (auto i = m_images.rbegin(); i != m_images.rend(); ++i) {
234                 image *img = &(*i);
235                 gint cursize;
236
237                 if (img->second == NULL)
238                         continue;
239
240                 cursize = gdk_pixbuf_get_byte_length(img->second);
241
242                 if (size + cursize > desired_size) {
243                         g_object_unref(img->second);
244                         img->second = NULL;
245                         num++;
246                 } else {
247                         size += cursize;
248                 }
249         }
250
251         /* Remove elements whose GdkPixbuf pointers point to NULL. */
252         m_images.remove_if([&](image _img) -> bool {
253                         if (_img.second == NULL)
254                                 return true;
255                         return false;
256                         });
257
258         unlock_images_cache();
259
260         return num;
261 }