1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2007 Blender Foundation
17 * All rights reserved.
18 */
19
20 /** \file
21 * \ingroup imbuf
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26
27 #include "MEM_guardedalloc.h"
28
29 #include "BLI_fileops.h"
30 #include "BLI_ghash.h"
31 #include "BLI_hash_md5.h"
32 #include "BLI_path_util.h"
33 #include "BLI_string.h"
34 #include "BLI_system.h"
35 #include "BLI_threads.h"
36 #include "BLI_utildefines.h"
37 #include BLI_SYSTEM_PID_H
38
39 #include "DNA_space_types.h" /* For FILE_MAX_LIBEXTRA */
40
41 #include "BLO_readfile.h"
42
43 #include "IMB_imbuf.h"
44 #include "IMB_imbuf_types.h"
45 #include "IMB_metadata.h"
46 #include "IMB_thumbs.h"
47
48 #include <ctype.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <sys/stat.h>
52 #include <sys/types.h>
53 #include <time.h>
54
55 #ifdef WIN32
56 /* Need to include windows.h so _WIN32_IE is defined. */
57 # include <windows.h>
58 # ifndef _WIN32_IE
59 /* Minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already. */
60 # define _WIN32_IE 0x0400
61 # endif
62 /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
63 * because 'near' is disabled through BLI_windstuff */
64 # include "BLI_winstuff.h"
65 # include "utfconv.h"
66 # include <direct.h> /* chdir */
67 # include <shlobj.h>
68 #endif
69
70 #if defined(WIN32) || defined(__APPLE__)
71 /* pass */
72 #else
73 # define USE_FREEDESKTOP
74 #endif
75
76 /* '$HOME/.cache/thumbnails' or '$HOME/.thumbnails' */
77 #ifdef USE_FREEDESKTOP
78 # define THUMBNAILS "thumbnails"
79 #else
80 # define THUMBNAILS ".thumbnails"
81 #endif
82
83 #define URI_MAX (FILE_MAX * 3 + 8)
84
get_thumb_dir(char * dir,ThumbSize size)85 static bool get_thumb_dir(char *dir, ThumbSize size)
86 {
87 char *s = dir;
88 const char *subdir;
89 #ifdef WIN32
90 wchar_t dir_16[MAX_PATH];
91 /* yes, applications shouldn't store data there, but so does GIMP :)*/
92 SHGetSpecialFolderPathW(0, dir_16, CSIDL_PROFILE, 0);
93 conv_utf_16_to_8(dir_16, dir, FILE_MAX);
94 s += strlen(dir);
95 #else
96 # if defined(USE_FREEDESKTOP)
97 const char *home_cache = BLI_getenv("XDG_CACHE_HOME");
98 const char *home = home_cache ? home_cache : BLI_getenv("HOME");
99 # else
100 const char *home = BLI_getenv("HOME");
101 # endif
102 if (!home) {
103 return 0;
104 }
105 s += BLI_strncpy_rlen(s, home, FILE_MAX);
106
107 # ifdef USE_FREEDESKTOP
108 if (!home_cache) {
109 s += BLI_strncpy_rlen(s, "/.cache", FILE_MAX - (s - dir));
110 }
111 # endif
112 #endif
113 switch (size) {
114 case THB_NORMAL:
115 subdir = "/" THUMBNAILS "/normal/";
116 break;
117 case THB_LARGE:
118 subdir = "/" THUMBNAILS "/large/";
119 break;
120 case THB_FAIL:
121 subdir = "/" THUMBNAILS "/fail/blender/";
122 break;
123 default:
124 return 0; /* unknown size */
125 }
126
127 s += BLI_strncpy_rlen(s, subdir, FILE_MAX - (s - dir));
128 (void)s;
129
130 return 1;
131 }
132
133 #undef THUMBNAILS
134
135 /* --- Begin of adapted code from glib. --- */
136
137 /* -------------------------------------------------------------------- */
138 /** \name Escape URI String
139 *
140 * The following code is adapted from function g_escape_uri_string from the gnome glib
141 * Source: http://svn.gnome.org/viewcvs/glib/trunk/glib/gconvert.c?view=markup
142 * released under the Gnu General Public License.
143 *
144 * \{ */
145
146 typedef enum {
147 UNSAFE_ALL = 0x1, /* Escape all unsafe characters */
148 UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */
149 UNSAFE_PATH = 0x8, /* Allows '/', '&', '=', ':', '@', '+', '$' and ',' */
150 UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */
151 UNSAFE_SLASHES = 0x20, /* Allows all characters except for '/' and '%' */
152 } UnsafeCharacterSet;
153
154 /* Don't lose comment alignment. */
155 /* clang-format off */
156 static const unsigned char acceptable[96] = {
157 /* A table of the ASCII chars from space (32) to DEL (127) */
158 /* ! " # $ % & ' ( ) * + , - . / */
159 0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
160 /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
161 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
162 /* @ A B C D E F G H I J K L M N O */
163 0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
164 /* P Q R S T U V W X Y Z [ \ ] ^ _ */
165 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
166 /* ` a b c d e f g h i j k l m n o */
167 0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
168 /* p q r s t u v w x y z { | } ~ DEL */
169 0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20,
170 };
171 /* clang-format on */
172
173 static const char hex[17] = "0123456789abcdef";
174
175 /* Note: This escape function works on file: URIs, but if you want to
176 * escape something else, please read RFC-2396 */
escape_uri_string(const char * string,char * escaped_string,int escaped_string_size,UnsafeCharacterSet mask)177 static void escape_uri_string(const char *string,
178 char *escaped_string,
179 int escaped_string_size,
180 UnsafeCharacterSet mask)
181 {
182 #define ACCEPTABLE(a) ((a) >= 32 && (a) < 128 && (acceptable[(a)-32] & use_mask))
183
184 const char *p;
185 char *q;
186 int c;
187 UnsafeCharacterSet use_mask;
188 use_mask = mask;
189
190 BLI_assert(escaped_string_size > 0);
191
192 /* space for \0 */
193 escaped_string_size -= 1;
194
195 for (q = escaped_string, p = string; (*p != '\0') && escaped_string_size; p++) {
196 c = (unsigned char)*p;
197
198 if (!ACCEPTABLE(c)) {
199 if (escaped_string_size < 3) {
200 break;
201 }
202
203 *q++ = '%'; /* means hex coming */
204 *q++ = hex[c >> 4];
205 *q++ = hex[c & 15];
206 escaped_string_size -= 3;
207 }
208 else {
209 *q++ = *p;
210 escaped_string_size -= 1;
211 }
212 }
213
214 *q = '\0';
215 }
216
217 /** \} */
218
219 /* --- End of adapted code from glib. --- */
220
thumbhash_from_path(const char * UNUSED (path),ThumbSource source,char * r_hash)221 static bool thumbhash_from_path(const char *UNUSED(path), ThumbSource source, char *r_hash)
222 {
223 switch (source) {
224 case THB_SOURCE_FONT:
225 return IMB_thumb_load_font_get_hash(r_hash);
226 default:
227 r_hash[0] = '\0';
228 return false;
229 }
230 }
231
uri_from_filename(const char * path,char * uri)232 static bool uri_from_filename(const char *path, char *uri)
233 {
234 char orig_uri[URI_MAX];
235 const char *dirstart = path;
236
237 #ifdef WIN32
238 {
239 char vol[3];
240
241 BLI_strncpy(orig_uri, "file:///", FILE_MAX);
242 if (strlen(path) < 2 && path[1] != ':') {
243 /* not a correct absolute path */
244 return 0;
245 }
246 /* on windows, using always uppercase drive/volume letter in uri */
247 vol[0] = (unsigned char)toupper(path[0]);
248 vol[1] = ':';
249 vol[2] = '\0';
250 strcat(orig_uri, vol);
251 dirstart += 2;
252 }
253 strcat(orig_uri, dirstart);
254 BLI_str_replace_char(orig_uri, '\\', '/');
255 #else
256 BLI_snprintf(orig_uri, URI_MAX, "file://%s", dirstart);
257 #endif
258
259 escape_uri_string(orig_uri, uri, URI_MAX, UNSAFE_PATH);
260
261 return 1;
262 }
263
thumbpathname_from_uri(const char * uri,char * r_path,const int path_len,char * r_name,int name_len,ThumbSize size)264 static bool thumbpathname_from_uri(
265 const char *uri, char *r_path, const int path_len, char *r_name, int name_len, ThumbSize size)
266 {
267 char name_buff[40];
268
269 if (r_path && !r_name) {
270 r_name = name_buff;
271 name_len = sizeof(name_buff);
272 }
273
274 if (r_name) {
275 char hexdigest[33];
276 unsigned char digest[16];
277 BLI_hash_md5_buffer(uri, strlen(uri), digest);
278 hexdigest[0] = '\0';
279 BLI_snprintf(r_name, name_len, "%s.png", BLI_hash_md5_to_hexdigest(digest, hexdigest));
280 // printf("%s: '%s' --> '%s'\n", __func__, uri, r_name);
281 }
282
283 if (r_path) {
284 char tmppath[FILE_MAX];
285
286 if (get_thumb_dir(tmppath, size)) {
287 BLI_snprintf(r_path, path_len, "%s%s", tmppath, r_name);
288 // printf("%s: '%s' --> '%s'\n", __func__, uri, r_path);
289 return true;
290 }
291 }
292 return false;
293 }
294
thumbname_from_uri(const char * uri,char * thumb,const int thumb_len)295 static void thumbname_from_uri(const char *uri, char *thumb, const int thumb_len)
296 {
297 thumbpathname_from_uri(uri, NULL, 0, thumb, thumb_len, THB_FAIL);
298 }
299
thumbpath_from_uri(const char * uri,char * path,const int path_len,ThumbSize size)300 static bool thumbpath_from_uri(const char *uri, char *path, const int path_len, ThumbSize size)
301 {
302 return thumbpathname_from_uri(uri, path, path_len, NULL, 0, size);
303 }
304
IMB_thumb_makedirs(void)305 void IMB_thumb_makedirs(void)
306 {
307 char tpath[FILE_MAX];
308 #if 0 /* UNUSED */
309 if (get_thumb_dir(tpath, THB_NORMAL)) {
310 BLI_dir_create_recursive(tpath);
311 }
312 #endif
313 if (get_thumb_dir(tpath, THB_LARGE)) {
314 BLI_dir_create_recursive(tpath);
315 }
316 if (get_thumb_dir(tpath, THB_FAIL)) {
317 BLI_dir_create_recursive(tpath);
318 }
319 }
320
321 /* create thumbnail for file and returns new imbuf for thumbnail */
thumb_create_ex(const char * file_path,const char * uri,const char * thumb,const bool use_hash,const char * hash,const char * blen_group,const char * blen_id,ThumbSize size,ThumbSource source,ImBuf * img)322 static ImBuf *thumb_create_ex(const char *file_path,
323 const char *uri,
324 const char *thumb,
325 const bool use_hash,
326 const char *hash,
327 const char *blen_group,
328 const char *blen_id,
329 ThumbSize size,
330 ThumbSource source,
331 ImBuf *img)
332 {
333 char desc[URI_MAX + 22];
334 char tpath[FILE_MAX];
335 char tdir[FILE_MAX];
336 char temp[FILE_MAX];
337 char mtime[40] = "0"; /* in case we can't stat the file */
338 char cwidth[40] = "0"; /* in case images have no data */
339 char cheight[40] = "0";
340 short tsize = 128;
341 short ex, ey;
342 float scaledx, scaledy;
343 BLI_stat_t info;
344
345 switch (size) {
346 case THB_NORMAL:
347 tsize = PREVIEW_RENDER_DEFAULT_HEIGHT;
348 break;
349 case THB_LARGE:
350 tsize = PREVIEW_RENDER_DEFAULT_HEIGHT * 2;
351 break;
352 case THB_FAIL:
353 tsize = 1;
354 break;
355 default:
356 return NULL; /* unknown size */
357 }
358
359 /* exception, skip images over 100mb */
360 if (source == THB_SOURCE_IMAGE) {
361 const size_t file_size = BLI_file_size(file_path);
362 if (file_size != -1 && file_size > THUMB_SIZE_MAX) {
363 // printf("file too big: %d, skipping %s\n", (int)size, file_path);
364 return NULL;
365 }
366 }
367
368 if (get_thumb_dir(tdir, size)) {
369 BLI_snprintf(tpath, FILE_MAX, "%s%s", tdir, thumb);
370 // thumb[8] = '\0'; /* shorten for tempname, not needed anymore */
371 BLI_snprintf(temp, FILE_MAX, "%sblender_%d_%s.png", tdir, abs(getpid()), thumb);
372 if (BLI_path_ncmp(file_path, tdir, sizeof(tdir)) == 0) {
373 return NULL;
374 }
375 if (size == THB_FAIL) {
376 img = IMB_allocImBuf(1, 1, 32, IB_rect | IB_metadata);
377 if (!img) {
378 return NULL;
379 }
380 }
381 else {
382 if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT)) {
383 /* only load if we didn't give an image */
384 if (img == NULL) {
385 switch (source) {
386 case THB_SOURCE_IMAGE:
387 img = IMB_loadiffname(file_path, IB_rect | IB_metadata, NULL);
388 break;
389 case THB_SOURCE_BLEND:
390 img = IMB_thumb_load_blend(file_path, blen_group, blen_id);
391 break;
392 case THB_SOURCE_FONT:
393 img = IMB_thumb_load_font(file_path, tsize, tsize);
394 break;
395 default:
396 BLI_assert(0); /* This should never happen */
397 }
398 }
399
400 if (img != NULL) {
401 if (BLI_stat(file_path, &info) != -1) {
402 BLI_snprintf(mtime, sizeof(mtime), "%ld", (long int)info.st_mtime);
403 }
404 BLI_snprintf(cwidth, sizeof(cwidth), "%d", img->x);
405 BLI_snprintf(cheight, sizeof(cheight), "%d", img->y);
406 }
407 }
408 else if (THB_SOURCE_MOVIE == source) {
409 struct anim *anim = NULL;
410 anim = IMB_open_anim(file_path, IB_rect | IB_metadata, 0, NULL);
411 if (anim != NULL) {
412 img = IMB_anim_absolute(anim, 0, IMB_TC_NONE, IMB_PROXY_NONE);
413 if (img == NULL) {
414 printf("not an anim; %s\n", file_path);
415 }
416 else {
417 IMB_freeImBuf(img);
418 img = IMB_anim_previewframe(anim);
419 }
420 IMB_free_anim(anim);
421 }
422 if (BLI_stat(file_path, &info) != -1) {
423 BLI_snprintf(mtime, sizeof(mtime), "%ld", (long int)info.st_mtime);
424 }
425 }
426 if (!img) {
427 return NULL;
428 }
429
430 if (img->x > img->y) {
431 scaledx = (float)tsize;
432 scaledy = ((float)img->y / (float)img->x) * tsize;
433 }
434 else {
435 scaledy = (float)tsize;
436 scaledx = ((float)img->x / (float)img->y) * tsize;
437 }
438 ex = (short)scaledx;
439 ey = (short)scaledy;
440
441 /* save some time by only scaling byte buf */
442 if (img->rect_float) {
443 if (img->rect == NULL) {
444 IMB_rect_from_float(img);
445 }
446
447 imb_freerectfloatImBuf(img);
448 }
449
450 IMB_scaleImBuf(img, ex, ey);
451 }
452 BLI_snprintf(desc, sizeof(desc), "Thumbnail for %s", uri);
453 IMB_metadata_ensure(&img->metadata);
454 IMB_metadata_set_field(img->metadata, "Software", "Blender");
455 IMB_metadata_set_field(img->metadata, "Thumb::URI", uri);
456 IMB_metadata_set_field(img->metadata, "Description", desc);
457 IMB_metadata_set_field(img->metadata, "Thumb::MTime", mtime);
458 if (use_hash) {
459 IMB_metadata_set_field(img->metadata, "X-Blender::Hash", hash);
460 }
461 if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT)) {
462 IMB_metadata_set_field(img->metadata, "Thumb::Image::Width", cwidth);
463 IMB_metadata_set_field(img->metadata, "Thumb::Image::Height", cheight);
464 }
465 img->ftype = IMB_FTYPE_PNG;
466 img->planes = 32;
467
468 /* If we generated from a 16bit PNG e.g., we have a float rect, not a byte one - fix this. */
469 IMB_rect_from_float(img);
470 imb_freerectfloatImBuf(img);
471
472 if (IMB_saveiff(img, temp, IB_rect | IB_metadata)) {
473 #ifndef WIN32
474 chmod(temp, S_IRUSR | S_IWUSR);
475 #endif
476 // printf("%s saving thumb: '%s'\n", __func__, tpath);
477
478 BLI_rename(temp, tpath);
479 }
480 }
481 return img;
482 }
483
thumb_create_or_fail(const char * file_path,const char * uri,const char * thumb,const bool use_hash,const char * hash,const char * blen_group,const char * blen_id,ThumbSize size,ThumbSource source)484 static ImBuf *thumb_create_or_fail(const char *file_path,
485 const char *uri,
486 const char *thumb,
487 const bool use_hash,
488 const char *hash,
489 const char *blen_group,
490 const char *blen_id,
491 ThumbSize size,
492 ThumbSource source)
493 {
494 ImBuf *img = thumb_create_ex(
495 file_path, uri, thumb, use_hash, hash, blen_group, blen_id, size, source, NULL);
496
497 if (!img) {
498 /* thumb creation failed, write fail thumb */
499 img = thumb_create_ex(
500 file_path, uri, thumb, use_hash, hash, blen_group, blen_id, THB_FAIL, source, NULL);
501 if (img) {
502 /* we don't need failed thumb anymore */
503 IMB_freeImBuf(img);
504 img = NULL;
505 }
506 }
507
508 return img;
509 }
510
IMB_thumb_create(const char * path,ThumbSize size,ThumbSource source,ImBuf * img)511 ImBuf *IMB_thumb_create(const char *path, ThumbSize size, ThumbSource source, ImBuf *img)
512 {
513 char uri[URI_MAX] = "";
514 char thumb_name[40];
515
516 if (!uri_from_filename(path, uri)) {
517 return NULL;
518 }
519 thumbname_from_uri(uri, thumb_name, sizeof(thumb_name));
520
521 return thumb_create_ex(
522 path, uri, thumb_name, false, THUMB_DEFAULT_HASH, NULL, NULL, size, source, img);
523 }
524
525 /* read thumbnail for file and returns new imbuf for thumbnail */
IMB_thumb_read(const char * path,ThumbSize size)526 ImBuf *IMB_thumb_read(const char *path, ThumbSize size)
527 {
528 char thumb[FILE_MAX];
529 char uri[URI_MAX];
530 ImBuf *img = NULL;
531
532 if (!uri_from_filename(path, uri)) {
533 return NULL;
534 }
535 if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
536 img = IMB_loadiffname(thumb, IB_rect | IB_metadata, NULL);
537 }
538
539 return img;
540 }
541
542 /* delete all thumbs for the file */
IMB_thumb_delete(const char * path,ThumbSize size)543 void IMB_thumb_delete(const char *path, ThumbSize size)
544 {
545 char thumb[FILE_MAX];
546 char uri[URI_MAX];
547
548 if (!uri_from_filename(path, uri)) {
549 return;
550 }
551 if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
552 if (BLI_path_ncmp(path, thumb, sizeof(thumb)) == 0) {
553 return;
554 }
555 if (BLI_exists(thumb)) {
556 BLI_delete(thumb, false, false);
557 }
558 }
559 }
560
561 /* create the thumb if necessary and manage failed and old thumbs */
IMB_thumb_manage(const char * org_path,ThumbSize size,ThumbSource source)562 ImBuf *IMB_thumb_manage(const char *org_path, ThumbSize size, ThumbSource source)
563 {
564 char thumb_path[FILE_MAX];
565 char thumb_name[40];
566 char uri[URI_MAX];
567 char path_buff[FILE_MAX_LIBEXTRA];
568 const char *file_path;
569 const char *path;
570 BLI_stat_t st;
571 ImBuf *img = NULL;
572 char *blen_group = NULL, *blen_id = NULL;
573
574 path = file_path = org_path;
575 if (source == THB_SOURCE_BLEND) {
576 if (BLO_library_path_explode(path, path_buff, &blen_group, &blen_id)) {
577 if (blen_group) {
578 if (!blen_id) {
579 /* No preview for blen groups */
580 return NULL;
581 }
582 file_path = path_buff; /* path needs to be a valid file! */
583 }
584 }
585 }
586
587 if (BLI_stat(file_path, &st) == -1) {
588 return NULL;
589 }
590 if (!uri_from_filename(path, uri)) {
591 return NULL;
592 }
593 if (thumbpath_from_uri(uri, thumb_path, sizeof(thumb_path), THB_FAIL)) {
594 /* failure thumb exists, don't try recreating */
595 if (BLI_exists(thumb_path)) {
596 /* clear out of date fail case (note for blen IDs we use blender file itself here) */
597 if (BLI_file_older(thumb_path, file_path)) {
598 BLI_delete(thumb_path, false, false);
599 }
600 else {
601 return NULL;
602 }
603 }
604 }
605
606 if (thumbpathname_from_uri(
607 uri, thumb_path, sizeof(thumb_path), thumb_name, sizeof(thumb_name), size)) {
608 if (BLI_path_ncmp(path, thumb_path, sizeof(thumb_path)) == 0) {
609 img = IMB_loadiffname(path, IB_rect, NULL);
610 }
611 else {
612 img = IMB_loadiffname(thumb_path, IB_rect | IB_metadata, NULL);
613 if (img) {
614 bool regenerate = false;
615
616 char mtime[40];
617 char thumb_hash[33];
618 char thumb_hash_curr[33];
619
620 const bool use_hash = thumbhash_from_path(file_path, source, thumb_hash);
621
622 if (IMB_metadata_get_field(img->metadata, "Thumb::MTime", mtime, sizeof(mtime))) {
623 regenerate = (st.st_mtime != atol(mtime));
624 }
625 else {
626 /* illegal thumb, regenerate it! */
627 regenerate = true;
628 }
629
630 if (use_hash && !regenerate) {
631 if (IMB_metadata_get_field(
632 img->metadata, "X-Blender::Hash", thumb_hash_curr, sizeof(thumb_hash_curr))) {
633 regenerate = !STREQ(thumb_hash, thumb_hash_curr);
634 }
635 else {
636 regenerate = true;
637 }
638 }
639
640 if (regenerate) {
641 /* recreate all thumbs */
642 IMB_freeImBuf(img);
643 img = NULL;
644 IMB_thumb_delete(path, THB_NORMAL);
645 IMB_thumb_delete(path, THB_LARGE);
646 IMB_thumb_delete(path, THB_FAIL);
647 img = thumb_create_or_fail(
648 file_path, uri, thumb_name, use_hash, thumb_hash, blen_group, blen_id, size, source);
649 }
650 }
651 else {
652 char thumb_hash[33];
653 const bool use_hash = thumbhash_from_path(file_path, source, thumb_hash);
654
655 img = thumb_create_or_fail(
656 file_path, uri, thumb_name, use_hash, thumb_hash, blen_group, blen_id, size, source);
657 }
658 }
659 }
660
661 /* Our imbuf **must** have a valid rect (i.e. 8-bits/channels)
662 * data, we rely on this in draw code.
663 * However, in some cases we may end loading 16bits PNGs, which generated float buffers.
664 * This should be taken care of in generation step, but add also a safeguard here! */
665 if (img) {
666 IMB_rect_from_float(img);
667 imb_freerectfloatImBuf(img);
668 }
669
670 return img;
671 }
672
673 /* ***** Threading ***** */
674 /* Thumbnail handling is not really threadsafe in itself.
675 * However, as long as we do not operate on the same file, we shall have no collision.
676 * So idea is to 'lock' a given source file path.
677 */
678
679 static struct IMBThumbLocks {
680 GSet *locked_paths;
681 int lock_counter;
682 ThreadCondition cond;
683 } thumb_locks = {0};
684
IMB_thumb_locks_acquire(void)685 void IMB_thumb_locks_acquire(void)
686 {
687 BLI_thread_lock(LOCK_IMAGE);
688
689 if (thumb_locks.lock_counter == 0) {
690 BLI_assert(thumb_locks.locked_paths == NULL);
691 thumb_locks.locked_paths = BLI_gset_str_new(__func__);
692 BLI_condition_init(&thumb_locks.cond);
693 }
694 thumb_locks.lock_counter++;
695
696 BLI_assert(thumb_locks.locked_paths != NULL);
697 BLI_assert(thumb_locks.lock_counter > 0);
698 BLI_thread_unlock(LOCK_IMAGE);
699 }
700
IMB_thumb_locks_release(void)701 void IMB_thumb_locks_release(void)
702 {
703 BLI_thread_lock(LOCK_IMAGE);
704 BLI_assert((thumb_locks.locked_paths != NULL) && (thumb_locks.lock_counter > 0));
705
706 thumb_locks.lock_counter--;
707 if (thumb_locks.lock_counter == 0) {
708 BLI_gset_free(thumb_locks.locked_paths, MEM_freeN);
709 thumb_locks.locked_paths = NULL;
710 BLI_condition_end(&thumb_locks.cond);
711 }
712
713 BLI_thread_unlock(LOCK_IMAGE);
714 }
715
IMB_thumb_path_lock(const char * path)716 void IMB_thumb_path_lock(const char *path)
717 {
718 void *key = BLI_strdup(path);
719
720 BLI_thread_lock(LOCK_IMAGE);
721 BLI_assert((thumb_locks.locked_paths != NULL) && (thumb_locks.lock_counter > 0));
722
723 if (thumb_locks.locked_paths) {
724 while (!BLI_gset_add(thumb_locks.locked_paths, key)) {
725 BLI_condition_wait_global_mutex(&thumb_locks.cond, LOCK_IMAGE);
726 }
727 }
728
729 BLI_thread_unlock(LOCK_IMAGE);
730 }
731
IMB_thumb_path_unlock(const char * path)732 void IMB_thumb_path_unlock(const char *path)
733 {
734 const void *key = path;
735
736 BLI_thread_lock(LOCK_IMAGE);
737 BLI_assert((thumb_locks.locked_paths != NULL) && (thumb_locks.lock_counter > 0));
738
739 if (thumb_locks.locked_paths) {
740 if (!BLI_gset_remove(thumb_locks.locked_paths, key, MEM_freeN)) {
741 BLI_assert(0);
742 }
743 BLI_condition_notify_all(&thumb_locks.cond);
744 }
745
746 BLI_thread_unlock(LOCK_IMAGE);
747 }
748