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