patch [#34103] use booleans for extensions testing.
[blender.git] / source / blender / imbuf / intern / thumbs.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
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  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2007 Blender Foundation
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): Andrea Weikert.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/imbuf/intern/thumbs.c
29  *  \ingroup imbuf
30  */
31
32
33 #include <stdio.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "BLI_utildefines.h"
38 #include "BLI_string.h"
39 #include "BLI_path_util.h"
40 #include "BLI_fileops.h"
41 #include "BLI_md5.h"
42
43 #include "IMB_imbuf_types.h"
44 #include "IMB_imbuf.h"
45 #include "IMB_thumbs.h"
46 #include "IMB_metadata.h"
47
48 #include <ctype.h>
49 #include <string.h>
50 #include <time.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <stdio.h>
54
55 #ifdef WIN32
56 #  include <windows.h> /* need to include windows.h so _WIN32_IE is defined  */
57 #  ifndef _WIN32_IE
58 #    define _WIN32_IE 0x0400 /* minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already */
59 #  endif
60 #  include <shlobj.h>  /* for SHGetSpecialFolderPath, has to be done before BLI_winstuff
61                         * because 'near' is disabled through BLI_windstuff */
62 #  include <process.h> /* getpid */
63 #  include <direct.h> /* chdir */
64 #  include "BLI_winstuff.h"
65 #  include "utfconv.h"
66 #else
67 #  include <unistd.h>
68 #endif
69
70 #define URI_MAX FILE_MAX * 3 + 8
71
72 static int get_thumb_dir(char *dir, ThumbSize size)
73 {
74 #ifdef WIN32
75         wchar_t dir_16[MAX_PATH];
76         /* yes, applications shouldn't store data there, but so does GIMP :)*/
77         SHGetSpecialFolderPathW(0, dir_16, CSIDL_PROFILE, 0);
78         conv_utf_16_to_8(dir_16, dir, FILE_MAX);
79
80
81 #else
82         const char *home = getenv("HOME");
83         if (!home) return 0;
84         BLI_strncpy(dir, home, FILE_MAX);
85 #endif
86         switch (size) {
87                 case THB_NORMAL:
88                         strcat(dir, "/.thumbnails/normal/");
89                         break;
90                 case THB_LARGE:
91                         strcat(dir, "/.thumbnails/large/");
92                         break;
93                 case THB_FAIL:
94                         strcat(dir, "/.thumbnails/fail/blender/");
95                         break;
96                 default:
97                         return 0; /* unknown size */
98         }
99         return 1;
100 }
101
102 /** ----- begin of adapted code from glib ---
103  * The following code is adapted from function g_escape_uri_string from the gnome glib
104  * Source: http://svn.gnome.org/viewcvs/glib/trunk/glib/gconvert.c?view=markup
105  * released under the Gnu General Public License.
106  */
107 typedef enum {
108         UNSAFE_ALL        = 0x1, /* Escape all unsafe characters   */
109         UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+'  */
110         UNSAFE_PATH       = 0x8, /* Allows '/', '&', '=', ':', '@', '+', '$' and ',' */
111         UNSAFE_HOST       = 0x10, /* Allows '/' and ':' and '@' */
112         UNSAFE_SLASHES    = 0x20 /* Allows all characters except for '/' and '%' */
113 } UnsafeCharacterSet;
114
115 static const unsigned char acceptable[96] = {
116         /* A table of the ASCII chars from space (32) to DEL (127) */
117         /*      !    "    #    $    %    &    '    (    )    *    +    ,    -    .    / */
118         0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
119         /* 0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ? */
120         0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
121         /* @    A    B    C    D    E    F    G    H    I    J    K    L    M    N    O */
122         0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
123         /* P    Q    R    S    T    U    V    W    X    Y    Z    [    \    ]    ^    _ */
124         0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
125         /* `    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o */
126         0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
127         /* p    q    r    s    t    u    v    w    x    y    z    {    |    }    ~  DEL */
128         0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20
129 };
130
131 static const char hex[17] = "0123456789abcdef";
132
133 /* Note: This escape function works on file: URIs, but if you want to
134  * escape something else, please read RFC-2396 */
135 static void escape_uri_string(const char *string, char *escaped_string, int len, UnsafeCharacterSet mask)
136 {
137 #define ACCEPTABLE(a) ((a) >= 32 && (a) < 128 && (acceptable[(a) - 32] & use_mask))
138
139         const char *p;
140         char *q;
141         int c;
142         UnsafeCharacterSet use_mask;
143         use_mask = mask;
144
145         for (q = escaped_string, p = string; (*p != '\0') && len; p++) {
146                 c = (unsigned char) *p;
147                 len--;
148
149                 if (!ACCEPTABLE(c)) {
150                         *q++ = '%'; /* means hex coming */
151                         *q++ = hex[c >> 4];
152                         *q++ = hex[c & 15];
153                 }
154                 else {
155                         *q++ = *p;
156                 }
157         }
158
159         *q = '\0';
160 }
161
162 static void to_hex_char(char *hexbytes, const unsigned char *bytes, int len)
163 {
164         const unsigned char *p;
165         char *q;
166
167         for (q = hexbytes, p = bytes; len; p++) {
168                 const unsigned char c = (unsigned char) *p;
169                 len--;
170                 *q++ = hex[c >> 4];
171                 *q++ = hex[c & 15];
172         }
173 }
174
175 /** ----- end of adapted code from glib --- */
176
177 static int uri_from_filename(const char *path, char *uri)
178 {
179         char orig_uri[URI_MAX];
180         const char *dirstart = path;
181         
182 #ifdef WIN32
183         {
184                 char vol[3];
185
186                 BLI_strncpy(orig_uri, "file:///", FILE_MAX);
187                 if (strlen(path) < 2 && path[1] != ':') {
188                         /* not a correct absolute path */
189                         return 0;
190                 }
191                 /* on windows, using always uppercase drive/volume letter in uri */
192                 vol[0] = (unsigned char)toupper(path[0]);
193                 vol[1] = ':';
194                 vol[2] = '\0';
195                 strcat(orig_uri, vol);
196                 dirstart += 2;
197         }
198 #else
199         BLI_strncpy(orig_uri, "file://", FILE_MAX);
200 #endif
201         strcat(orig_uri, dirstart);
202         BLI_char_switch(orig_uri, '\\', '/');
203         
204 #ifdef WITH_ICONV
205         {
206                 char uri_utf8[FILE_MAX * 3 + 8];
207                 escape_uri_string(orig_uri, uri_utf8, FILE_MAX * 3 + 8, UNSAFE_PATH);
208                 BLI_string_to_utf8(uri_utf8, uri, NULL);
209         }
210 #else 
211         escape_uri_string(orig_uri, uri, FILE_MAX * 3 + 8, UNSAFE_PATH);
212 #endif
213         return 1;
214 }
215
216 static void thumbname_from_uri(const char *uri, char *thumb, const int thumb_len)
217 {
218         char hexdigest[33];
219         unsigned char digest[16];
220
221         md5_buffer(uri, strlen(uri), digest);
222         hexdigest[0] = '\0';
223         to_hex_char(hexdigest, digest, 16);
224         hexdigest[32] = '\0';
225         BLI_snprintf(thumb, thumb_len, "%s.png", hexdigest);
226
227         // printf("%s: '%s' --> '%s'\n", __func__, uri, thumb);
228 }
229
230 static int thumbpath_from_uri(const char *uri, char *path, const int path_len, ThumbSize size)
231 {
232         char tmppath[FILE_MAX];
233         int rv = 0;
234
235         if (get_thumb_dir(tmppath, size)) {
236                 char thumb[40];
237                 thumbname_from_uri(uri, thumb, sizeof(thumb));
238                 BLI_snprintf(path, path_len, "%s%s", tmppath, thumb);
239                 rv = 1;
240         }
241         return rv;
242 }
243
244 void IMB_thumb_makedirs(void)
245 {
246         char tpath[FILE_MAX];
247         if (get_thumb_dir(tpath, THB_NORMAL)) {
248                 BLI_dir_create_recursive(tpath);
249         }
250         if (get_thumb_dir(tpath, THB_FAIL)) {
251                 BLI_dir_create_recursive(tpath);
252         }
253 }
254
255 /* create thumbnail for file and returns new imbuf for thumbnail */
256 ImBuf *IMB_thumb_create(const char *path, ThumbSize size, ThumbSource source, ImBuf *img)
257 {
258         char uri[URI_MAX] = "";
259         char desc[URI_MAX + 22];
260         char tpath[FILE_MAX];
261         char tdir[FILE_MAX];
262         char temp[FILE_MAX];
263         char mtime[40] = "0"; /* in case we can't stat the file */
264         char cwidth[40] = "0"; /* in case images have no data */
265         char cheight[40] = "0";
266         char thumb[40];
267         short tsize = 128;
268         short ex, ey;
269         float scaledx, scaledy;
270         struct stat info;
271
272         switch (size) {
273                 case THB_NORMAL:
274                         tsize = 128;
275                         break;
276                 case THB_LARGE:
277                         tsize = 256;
278                         break;
279                 case THB_FAIL:
280                         tsize = 1;
281                         break;
282                 default:
283                         return NULL; /* unknown size */
284         }
285
286         /* exception, skip images over 100mb */
287         if (source == THB_SOURCE_IMAGE) {
288                 const size_t size = BLI_file_size(path);
289                 if (size != -1 && size > THUMB_SIZE_MAX) {
290                         // printf("file too big: %d, skipping %s\n", (int)size, path);
291                         return NULL;
292                 }
293         }
294
295         uri_from_filename(path, uri);
296         thumbname_from_uri(uri, thumb, sizeof(thumb));
297         if (get_thumb_dir(tdir, size)) {
298                 BLI_snprintf(tpath, FILE_MAX, "%s%s", tdir, thumb);
299                 thumb[8] = '\0'; /* shorten for tempname, not needed anymore */
300                 BLI_snprintf(temp, FILE_MAX, "%sblender_%d_%s.png", tdir, abs(getpid()), thumb);
301                 if (BLI_path_ncmp(path, tdir, sizeof(tdir)) == 0) {
302                         return NULL;
303                 }
304                 if (size == THB_FAIL) {
305                         img = IMB_allocImBuf(1, 1, 32, IB_rect | IB_metadata);
306                         if (!img) return NULL;
307                 }
308                 else {
309                         if (THB_SOURCE_IMAGE == source || THB_SOURCE_BLEND == source) {
310                                 
311                                 /* only load if we didnt give an image */
312                                 if (img == NULL) {
313                                         if (THB_SOURCE_BLEND == source) {
314                                                 img = IMB_loadblend_thumb(path);
315                                         }
316                                         else {
317                                                 img = IMB_loadiffname(path, IB_rect | IB_metadata, NULL);
318                                         }
319                                 }
320
321                                 if (img != NULL) {
322                                         stat(path, &info);
323                                         BLI_snprintf(mtime, sizeof(mtime), "%ld", (long int)info.st_mtime);
324                                         BLI_snprintf(cwidth, sizeof(cwidth), "%d", img->x);
325                                         BLI_snprintf(cheight, sizeof(cheight), "%d", img->y);
326                                 }
327                         }
328                         else if (THB_SOURCE_MOVIE == source) {
329                                 struct anim *anim = NULL;
330                                 anim = IMB_open_anim(path, IB_rect | IB_metadata, 0, NULL);
331                                 if (anim != NULL) {
332                                         img = IMB_anim_absolute(anim, 0, IMB_TC_NONE, IMB_PROXY_NONE);
333                                         if (img == NULL) {
334                                                 printf("not an anim; %s\n", path);
335                                         }
336                                         else {
337                                                 IMB_freeImBuf(img);
338                                                 img = IMB_anim_previewframe(anim);
339                                         }
340                                         IMB_free_anim(anim);
341                                 }
342                                 stat(path, &info);
343                                 BLI_snprintf(mtime, sizeof(mtime), "%ld", (long int)info.st_mtime);
344                         }
345                         if (!img) return NULL;
346
347                         if (img->x > img->y) {
348                                 scaledx = (float)tsize;
349                                 scaledy =  ( (float)img->y / (float)img->x) * tsize;
350                         }
351                         else {
352                                 scaledy = (float)tsize;
353                                 scaledx =  ( (float)img->x / (float)img->y) * tsize;
354                         }
355                         ex = (short)scaledx;
356                         ey = (short)scaledy;
357                         
358                         /* save some time by only scaling byte buf */
359                         if (img->rect_float) {
360                                 if (img->rect == NULL) {
361                                         IMB_rect_from_float(img);
362                                 }
363
364                                 imb_freerectfloatImBuf(img);
365                         }
366
367                         IMB_scaleImBuf(img, ex, ey);
368                 }
369                 BLI_snprintf(desc, sizeof(desc), "Thumbnail for %s", uri);
370                 IMB_metadata_change_field(img, "Description", desc);
371                 IMB_metadata_change_field(img, "Software", "Blender");
372                 IMB_metadata_change_field(img, "Thumb::URI", uri);
373                 IMB_metadata_change_field(img, "Thumb::MTime", mtime);
374                 if (THB_SOURCE_IMAGE == source) {
375                         IMB_metadata_change_field(img, "Thumb::Image::Width", cwidth);
376                         IMB_metadata_change_field(img, "Thumb::Image::Height", cheight);
377                 }
378                 img->ftype = PNG;
379                 img->planes = 32;
380
381                 if (IMB_saveiff(img, temp, IB_rect | IB_metadata)) {
382 #ifndef WIN32
383                         chmod(temp, S_IRUSR | S_IWUSR);
384 #endif
385                         // printf("%s saving thumb: '%s'\n", __func__, tpath);
386
387                         BLI_rename(temp, tpath);
388                 }
389
390                 return img;
391         }
392         return img;
393 }
394
395 /* read thumbnail for file and returns new imbuf for thumbnail */
396 ImBuf *IMB_thumb_read(const char *path, ThumbSize size)
397 {
398         char thumb[FILE_MAX];
399         char uri[FILE_MAX * 3 + 8];
400         ImBuf *img = NULL;
401
402         if (!uri_from_filename(path, uri)) {
403                 return NULL;
404         }
405         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
406                 img = IMB_loadiffname(thumb, IB_rect | IB_metadata, NULL);
407         }
408
409         return img;
410 }
411
412 /* delete all thumbs for the file */
413 void IMB_thumb_delete(const char *path, ThumbSize size)
414 {
415         char thumb[FILE_MAX];
416         char uri[FILE_MAX * 3 + 8];
417
418         if (!uri_from_filename(path, uri)) {
419                 return;
420         }
421         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
422                 if (BLI_path_ncmp(path, thumb, sizeof(thumb)) == 0) {
423                         return;
424                 }
425                 if (BLI_exists(thumb)) {
426                         BLI_delete(thumb, 0, 0);
427                 }
428         }
429 }
430
431
432 /* create the thumb if necessary and manage failed and old thumbs */
433 ImBuf *IMB_thumb_manage(const char *path, ThumbSize size, ThumbSource source)
434 {
435         char thumb[FILE_MAX];
436         char uri[FILE_MAX * 3 + 8];
437         struct stat st;
438         ImBuf *img = NULL;
439         
440         if (BLI_stat(path, &st)) {
441                 return NULL;
442         }
443         if (!uri_from_filename(path, uri)) {
444                 return NULL;
445         }
446         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), THB_FAIL)) {
447                 /* failure thumb exists, don't try recreating */
448                 if (BLI_exists(thumb)) {
449                         /* clear out of date fail case */
450                         if (BLI_file_older(thumb, path)) {
451                                 BLI_delete(thumb, 0, 0);
452                         }
453                         else {
454                                 return NULL;
455                         }
456                 }
457         }
458
459         if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
460                 if (BLI_path_ncmp(path, thumb, sizeof(thumb)) == 0) {
461                         img = IMB_loadiffname(path, IB_rect, NULL);
462                 }
463                 else {
464                         img = IMB_loadiffname(thumb, IB_rect | IB_metadata, NULL);
465                         if (img) {
466                                 char mtime[40];
467                                 if (!IMB_metadata_get_field(img, "Thumb::MTime", mtime, 40)) {
468                                         /* illegal thumb, forget it! */
469                                         IMB_freeImBuf(img);
470                                         img = NULL;
471                                 }
472                                 else {
473                                         time_t t = atol(mtime);
474                                         if (st.st_mtime != t) {
475                                                 /* recreate all thumbs */
476                                                 IMB_freeImBuf(img);
477                                                 img = NULL;
478                                                 IMB_thumb_delete(path, THB_NORMAL);
479                                                 IMB_thumb_delete(path, THB_LARGE);
480                                                 IMB_thumb_delete(path, THB_FAIL);
481                                                 img = IMB_thumb_create(path, size, source, NULL);
482                                                 if (!img) {
483                                                         /* thumb creation failed, write fail thumb */
484                                                         img = IMB_thumb_create(path, THB_FAIL, source, NULL);
485                                                         if (img) {
486                                                                 /* we don't need failed thumb anymore */
487                                                                 IMB_freeImBuf(img);
488                                                                 img = NULL;
489                                                         }
490                                                 }
491                                         }
492                                 }
493                         }
494                         else {
495                                 img = IMB_thumb_create(path, size, source, NULL);
496                                 if (!img) {
497                                         /* thumb creation failed, write fail thumb */
498                                         img = IMB_thumb_create(path, THB_FAIL, source, NULL);
499                                         if (img) {
500                                                 /* we don't need failed thumb anymore */
501                                                 IMB_freeImBuf(img);
502                                                 img = NULL;
503                                         }
504                                 }
505                         }
506                 }
507         }
508
509         return img;
510 }