On the Windows platform there will be some errors printed to the console if the user's thumbnail cache folder doesn't already exist. While creating those folders there is an attempt to do so multiple times and so we get errors when trying to create when exists. This is caused by paths that have hard-coded forward slashes, which causes our path processing routines to not work correctly. This patch defines those paths using platform-varying separator characters. Differential Revision: https://developer.blender.org/D11505 Reviewed by Brecht Van Lommel
748 lines
21 KiB
C
748 lines
21 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2007 Blender Foundation
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup imbuf
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_fileops.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_hash_md5.h"
|
|
#include "BLI_path_util.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_system.h"
|
|
#include "BLI_threads.h"
|
|
#include "BLI_utildefines.h"
|
|
#include BLI_SYSTEM_PID_H
|
|
|
|
#include "DNA_space_types.h" /* For FILE_MAX_LIBEXTRA */
|
|
|
|
#include "BLO_readfile.h"
|
|
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
#include "IMB_metadata.h"
|
|
#include "IMB_thumbs.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
|
|
#ifdef WIN32
|
|
/* Need to include windows.h so _WIN32_IE is defined. */
|
|
# include <windows.h>
|
|
# ifndef _WIN32_IE
|
|
/* Minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already. */
|
|
# define _WIN32_IE 0x0400
|
|
# endif
|
|
/* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
|
|
* because 'near' is disabled through BLI_windstuff */
|
|
# include "BLI_winstuff.h"
|
|
# include "utfconv.h"
|
|
# include <direct.h> /* #chdir */
|
|
# include <shlobj.h>
|
|
#endif
|
|
|
|
#if defined(WIN32) || defined(__APPLE__)
|
|
/* pass */
|
|
#else
|
|
# define USE_FREEDESKTOP
|
|
#endif
|
|
|
|
/* '$HOME/.cache/thumbnails' or '$HOME/.thumbnails' */
|
|
#ifdef USE_FREEDESKTOP
|
|
# define THUMBNAILS "thumbnails"
|
|
#else
|
|
# define THUMBNAILS ".thumbnails"
|
|
#endif
|
|
|
|
#define URI_MAX (FILE_MAX * 3 + 8)
|
|
|
|
static bool get_thumb_dir(char *dir, ThumbSize size)
|
|
{
|
|
char *s = dir;
|
|
const char *subdir;
|
|
#ifdef WIN32
|
|
wchar_t dir_16[MAX_PATH];
|
|
/* yes, applications shouldn't store data there, but so does GIMP :)*/
|
|
SHGetSpecialFolderPathW(0, dir_16, CSIDL_PROFILE, 0);
|
|
conv_utf_16_to_8(dir_16, dir, FILE_MAX);
|
|
s += strlen(dir);
|
|
#else
|
|
# if defined(USE_FREEDESKTOP)
|
|
const char *home_cache = BLI_getenv("XDG_CACHE_HOME");
|
|
const char *home = home_cache ? home_cache : BLI_getenv("HOME");
|
|
# else
|
|
const char *home = BLI_getenv("HOME");
|
|
# endif
|
|
if (!home) {
|
|
return 0;
|
|
}
|
|
s += BLI_strncpy_rlen(s, home, FILE_MAX);
|
|
|
|
# ifdef USE_FREEDESKTOP
|
|
if (!home_cache) {
|
|
s += BLI_strncpy_rlen(s, "/.cache", FILE_MAX - (s - dir));
|
|
}
|
|
# endif
|
|
#endif
|
|
switch (size) {
|
|
case THB_NORMAL:
|
|
subdir = SEP_STR THUMBNAILS SEP_STR "normal" SEP_STR;
|
|
break;
|
|
case THB_LARGE:
|
|
subdir = SEP_STR THUMBNAILS SEP_STR "large" SEP_STR;
|
|
break;
|
|
case THB_FAIL:
|
|
subdir = SEP_STR THUMBNAILS SEP_STR "fail" SEP_STR "blender" SEP_STR;
|
|
break;
|
|
default:
|
|
return 0; /* unknown size */
|
|
}
|
|
|
|
s += BLI_strncpy_rlen(s, subdir, FILE_MAX - (s - dir));
|
|
(void)s;
|
|
|
|
return 1;
|
|
}
|
|
|
|
#undef THUMBNAILS
|
|
|
|
/* --- Begin of adapted code from glib. --- */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Escape URI String
|
|
*
|
|
* The following code is adapted from function g_escape_uri_string from the gnome glib
|
|
* Source: http://svn.gnome.org/viewcvs/glib/trunk/glib/gconvert.c?view=markup
|
|
* released under the Gnu General Public License.
|
|
*
|
|
* \{ */
|
|
|
|
typedef enum {
|
|
UNSAFE_ALL = 0x1, /* Escape all unsafe characters */
|
|
UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */
|
|
UNSAFE_PATH = 0x8, /* Allows '/', '&', '=', ':', '@', '+', '$' and ',' */
|
|
UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */
|
|
UNSAFE_SLASHES = 0x20, /* Allows all characters except for '/' and '%' */
|
|
} UnsafeCharacterSet;
|
|
|
|
/* Don't lose comment alignment. */
|
|
/* clang-format off */
|
|
static const unsigned char acceptable[96] = {
|
|
/* A table of the ASCII chars from space (32) to DEL (127) */
|
|
/* ! " # $ % & ' ( ) * + , - . / */
|
|
0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
|
|
/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
|
|
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
|
|
/* @ A B C D E F G H I J K L M N O */
|
|
0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
|
|
/* P Q R S T U V W X Y Z [ \ ] ^ _ */
|
|
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
|
|
/* ` a b c d e f g h i j k l m n o */
|
|
0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
|
|
/* p q r s t u v w x y z { | } ~ DEL */
|
|
0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20,
|
|
};
|
|
/* clang-format on */
|
|
|
|
static const char hex[17] = "0123456789abcdef";
|
|
|
|
/* Note: This escape function works on file: URIs, but if you want to
|
|
* escape something else, please read RFC-2396 */
|
|
static void escape_uri_string(const char *string,
|
|
char *escaped_string,
|
|
int escaped_string_size,
|
|
UnsafeCharacterSet mask)
|
|
{
|
|
#define ACCEPTABLE(a) ((a) >= 32 && (a) < 128 && (acceptable[(a)-32] & use_mask))
|
|
|
|
const char *p;
|
|
char *q;
|
|
int c;
|
|
UnsafeCharacterSet use_mask;
|
|
use_mask = mask;
|
|
|
|
BLI_assert(escaped_string_size > 0);
|
|
|
|
/* space for \0 */
|
|
escaped_string_size -= 1;
|
|
|
|
for (q = escaped_string, p = string; (*p != '\0') && escaped_string_size; p++) {
|
|
c = (unsigned char)*p;
|
|
|
|
if (!ACCEPTABLE(c)) {
|
|
if (escaped_string_size < 3) {
|
|
break;
|
|
}
|
|
|
|
*q++ = '%'; /* means hex coming */
|
|
*q++ = hex[c >> 4];
|
|
*q++ = hex[c & 15];
|
|
escaped_string_size -= 3;
|
|
}
|
|
else {
|
|
*q++ = *p;
|
|
escaped_string_size -= 1;
|
|
}
|
|
}
|
|
|
|
*q = '\0';
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* --- End of adapted code from glib. --- */
|
|
|
|
static bool thumbhash_from_path(const char *UNUSED(path), ThumbSource source, char *r_hash)
|
|
{
|
|
switch (source) {
|
|
case THB_SOURCE_FONT:
|
|
return IMB_thumb_load_font_get_hash(r_hash);
|
|
default:
|
|
r_hash[0] = '\0';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool uri_from_filename(const char *path, char *uri)
|
|
{
|
|
char orig_uri[URI_MAX];
|
|
const char *dirstart = path;
|
|
|
|
#ifdef WIN32
|
|
{
|
|
char vol[3];
|
|
|
|
BLI_strncpy(orig_uri, "file:///", FILE_MAX);
|
|
if (strlen(path) < 2 && path[1] != ':') {
|
|
/* not a correct absolute path */
|
|
return 0;
|
|
}
|
|
/* on windows, using always uppercase drive/volume letter in uri */
|
|
vol[0] = (unsigned char)toupper(path[0]);
|
|
vol[1] = ':';
|
|
vol[2] = '\0';
|
|
strcat(orig_uri, vol);
|
|
dirstart += 2;
|
|
}
|
|
strcat(orig_uri, dirstart);
|
|
BLI_str_replace_char(orig_uri, '\\', '/');
|
|
#else
|
|
BLI_snprintf(orig_uri, URI_MAX, "file://%s", dirstart);
|
|
#endif
|
|
|
|
escape_uri_string(orig_uri, uri, URI_MAX, UNSAFE_PATH);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool thumbpathname_from_uri(
|
|
const char *uri, char *r_path, const int path_len, char *r_name, int name_len, ThumbSize size)
|
|
{
|
|
char name_buff[40];
|
|
|
|
if (r_path && !r_name) {
|
|
r_name = name_buff;
|
|
name_len = sizeof(name_buff);
|
|
}
|
|
|
|
if (r_name) {
|
|
char hexdigest[33];
|
|
unsigned char digest[16];
|
|
BLI_hash_md5_buffer(uri, strlen(uri), digest);
|
|
hexdigest[0] = '\0';
|
|
BLI_snprintf(r_name, name_len, "%s.png", BLI_hash_md5_to_hexdigest(digest, hexdigest));
|
|
// printf("%s: '%s' --> '%s'\n", __func__, uri, r_name);
|
|
}
|
|
|
|
if (r_path) {
|
|
char tmppath[FILE_MAX];
|
|
|
|
if (get_thumb_dir(tmppath, size)) {
|
|
BLI_snprintf(r_path, path_len, "%s%s", tmppath, r_name);
|
|
// printf("%s: '%s' --> '%s'\n", __func__, uri, r_path);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void thumbname_from_uri(const char *uri, char *thumb, const int thumb_len)
|
|
{
|
|
thumbpathname_from_uri(uri, NULL, 0, thumb, thumb_len, THB_FAIL);
|
|
}
|
|
|
|
static bool thumbpath_from_uri(const char *uri, char *path, const int path_len, ThumbSize size)
|
|
{
|
|
return thumbpathname_from_uri(uri, path, path_len, NULL, 0, size);
|
|
}
|
|
|
|
void IMB_thumb_makedirs(void)
|
|
{
|
|
char tpath[FILE_MAX];
|
|
#if 0 /* UNUSED */
|
|
if (get_thumb_dir(tpath, THB_NORMAL)) {
|
|
BLI_dir_create_recursive(tpath);
|
|
}
|
|
#endif
|
|
if (get_thumb_dir(tpath, THB_LARGE)) {
|
|
BLI_dir_create_recursive(tpath);
|
|
}
|
|
if (get_thumb_dir(tpath, THB_FAIL)) {
|
|
BLI_dir_create_recursive(tpath);
|
|
}
|
|
}
|
|
|
|
/* create thumbnail for file and returns new imbuf for thumbnail */
|
|
static ImBuf *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)
|
|
{
|
|
char desc[URI_MAX + 22];
|
|
char tpath[FILE_MAX];
|
|
char tdir[FILE_MAX];
|
|
char temp[FILE_MAX];
|
|
char mtime[40] = "0"; /* in case we can't stat the file */
|
|
char cwidth[40] = "0"; /* in case images have no data */
|
|
char cheight[40] = "0";
|
|
short tsize = 128;
|
|
short ex, ey;
|
|
float scaledx, scaledy;
|
|
BLI_stat_t info;
|
|
|
|
switch (size) {
|
|
case THB_NORMAL:
|
|
tsize = PREVIEW_RENDER_DEFAULT_HEIGHT;
|
|
break;
|
|
case THB_LARGE:
|
|
tsize = PREVIEW_RENDER_DEFAULT_HEIGHT * 2;
|
|
break;
|
|
case THB_FAIL:
|
|
tsize = 1;
|
|
break;
|
|
default:
|
|
return NULL; /* unknown size */
|
|
}
|
|
|
|
/* exception, skip images over 100mb */
|
|
if (source == THB_SOURCE_IMAGE) {
|
|
const size_t file_size = BLI_file_size(file_path);
|
|
if (file_size != -1 && file_size > THUMB_SIZE_MAX) {
|
|
// printf("file too big: %d, skipping %s\n", (int)size, file_path);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (get_thumb_dir(tdir, size)) {
|
|
BLI_snprintf(tpath, FILE_MAX, "%s%s", tdir, thumb);
|
|
// thumb[8] = '\0'; /* shorten for tempname, not needed anymore */
|
|
BLI_snprintf(temp, FILE_MAX, "%sblender_%d_%s.png", tdir, abs(getpid()), thumb);
|
|
if (BLI_path_ncmp(file_path, tdir, sizeof(tdir)) == 0) {
|
|
return NULL;
|
|
}
|
|
if (size == THB_FAIL) {
|
|
img = IMB_allocImBuf(1, 1, 32, IB_rect | IB_metadata);
|
|
if (!img) {
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT)) {
|
|
/* only load if we didn't give an image */
|
|
if (img == NULL) {
|
|
switch (source) {
|
|
case THB_SOURCE_IMAGE:
|
|
img = IMB_loadiffname(file_path, IB_rect | IB_metadata, NULL);
|
|
break;
|
|
case THB_SOURCE_BLEND:
|
|
img = IMB_thumb_load_blend(file_path, blen_group, blen_id);
|
|
break;
|
|
case THB_SOURCE_FONT:
|
|
img = IMB_thumb_load_font(file_path, tsize, tsize);
|
|
break;
|
|
default:
|
|
BLI_assert_unreachable(); /* This should never happen */
|
|
}
|
|
}
|
|
|
|
if (img != NULL) {
|
|
if (BLI_stat(file_path, &info) != -1) {
|
|
BLI_snprintf(mtime, sizeof(mtime), "%ld", (long int)info.st_mtime);
|
|
}
|
|
BLI_snprintf(cwidth, sizeof(cwidth), "%d", img->x);
|
|
BLI_snprintf(cheight, sizeof(cheight), "%d", img->y);
|
|
}
|
|
}
|
|
else if (THB_SOURCE_MOVIE == source) {
|
|
struct anim *anim = NULL;
|
|
anim = IMB_open_anim(file_path, IB_rect | IB_metadata, 0, NULL);
|
|
if (anim != NULL) {
|
|
img = IMB_anim_absolute(anim, 0, IMB_TC_NONE, IMB_PROXY_NONE);
|
|
if (img == NULL) {
|
|
printf("not an anim; %s\n", file_path);
|
|
}
|
|
else {
|
|
IMB_freeImBuf(img);
|
|
img = IMB_anim_previewframe(anim);
|
|
}
|
|
IMB_free_anim(anim);
|
|
}
|
|
if (BLI_stat(file_path, &info) != -1) {
|
|
BLI_snprintf(mtime, sizeof(mtime), "%ld", (long int)info.st_mtime);
|
|
}
|
|
}
|
|
if (!img) {
|
|
return NULL;
|
|
}
|
|
|
|
if (img->x > img->y) {
|
|
scaledx = (float)tsize;
|
|
scaledy = ((float)img->y / (float)img->x) * tsize;
|
|
}
|
|
else {
|
|
scaledy = (float)tsize;
|
|
scaledx = ((float)img->x / (float)img->y) * tsize;
|
|
}
|
|
ex = (short)scaledx;
|
|
ey = (short)scaledy;
|
|
|
|
/* save some time by only scaling byte buf */
|
|
if (img->rect_float) {
|
|
if (img->rect == NULL) {
|
|
IMB_rect_from_float(img);
|
|
}
|
|
|
|
imb_freerectfloatImBuf(img);
|
|
}
|
|
|
|
IMB_scaleImBuf(img, ex, ey);
|
|
}
|
|
BLI_snprintf(desc, sizeof(desc), "Thumbnail for %s", uri);
|
|
IMB_metadata_ensure(&img->metadata);
|
|
IMB_metadata_set_field(img->metadata, "Software", "Blender");
|
|
IMB_metadata_set_field(img->metadata, "Thumb::URI", uri);
|
|
IMB_metadata_set_field(img->metadata, "Description", desc);
|
|
IMB_metadata_set_field(img->metadata, "Thumb::MTime", mtime);
|
|
if (use_hash) {
|
|
IMB_metadata_set_field(img->metadata, "X-Blender::Hash", hash);
|
|
}
|
|
if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT)) {
|
|
IMB_metadata_set_field(img->metadata, "Thumb::Image::Width", cwidth);
|
|
IMB_metadata_set_field(img->metadata, "Thumb::Image::Height", cheight);
|
|
}
|
|
img->ftype = IMB_FTYPE_PNG;
|
|
img->planes = 32;
|
|
|
|
/* If we generated from a 16bit PNG e.g., we have a float rect, not a byte one - fix this. */
|
|
IMB_rect_from_float(img);
|
|
imb_freerectfloatImBuf(img);
|
|
|
|
if (IMB_saveiff(img, temp, IB_rect | IB_metadata)) {
|
|
#ifndef WIN32
|
|
chmod(temp, S_IRUSR | S_IWUSR);
|
|
#endif
|
|
// printf("%s saving thumb: '%s'\n", __func__, tpath);
|
|
|
|
BLI_rename(temp, tpath);
|
|
}
|
|
}
|
|
return img;
|
|
}
|
|
|
|
static ImBuf *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)
|
|
{
|
|
ImBuf *img = thumb_create_ex(
|
|
file_path, uri, thumb, use_hash, hash, blen_group, blen_id, size, source, NULL);
|
|
|
|
if (!img) {
|
|
/* thumb creation failed, write fail thumb */
|
|
img = thumb_create_ex(
|
|
file_path, uri, thumb, use_hash, hash, blen_group, blen_id, THB_FAIL, source, NULL);
|
|
if (img) {
|
|
/* we don't need failed thumb anymore */
|
|
IMB_freeImBuf(img);
|
|
img = NULL;
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
ImBuf *IMB_thumb_create(const char *path, ThumbSize size, ThumbSource source, ImBuf *img)
|
|
{
|
|
char uri[URI_MAX] = "";
|
|
char thumb_name[40];
|
|
|
|
if (!uri_from_filename(path, uri)) {
|
|
return NULL;
|
|
}
|
|
thumbname_from_uri(uri, thumb_name, sizeof(thumb_name));
|
|
|
|
return thumb_create_ex(
|
|
path, uri, thumb_name, false, THUMB_DEFAULT_HASH, NULL, NULL, size, source, img);
|
|
}
|
|
|
|
/* read thumbnail for file and returns new imbuf for thumbnail */
|
|
ImBuf *IMB_thumb_read(const char *path, ThumbSize size)
|
|
{
|
|
char thumb[FILE_MAX];
|
|
char uri[URI_MAX];
|
|
ImBuf *img = NULL;
|
|
|
|
if (!uri_from_filename(path, uri)) {
|
|
return NULL;
|
|
}
|
|
if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
|
|
img = IMB_loadiffname(thumb, IB_rect | IB_metadata, NULL);
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
/* delete all thumbs for the file */
|
|
void IMB_thumb_delete(const char *path, ThumbSize size)
|
|
{
|
|
char thumb[FILE_MAX];
|
|
char uri[URI_MAX];
|
|
|
|
if (!uri_from_filename(path, uri)) {
|
|
return;
|
|
}
|
|
if (thumbpath_from_uri(uri, thumb, sizeof(thumb), size)) {
|
|
if (BLI_path_ncmp(path, thumb, sizeof(thumb)) == 0) {
|
|
return;
|
|
}
|
|
if (BLI_exists(thumb)) {
|
|
BLI_delete(thumb, false, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* create the thumb if necessary and manage failed and old thumbs */
|
|
ImBuf *IMB_thumb_manage(const char *org_path, ThumbSize size, ThumbSource source)
|
|
{
|
|
char thumb_path[FILE_MAX];
|
|
char thumb_name[40];
|
|
char uri[URI_MAX];
|
|
char path_buff[FILE_MAX_LIBEXTRA];
|
|
const char *file_path;
|
|
const char *path;
|
|
BLI_stat_t st;
|
|
ImBuf *img = NULL;
|
|
char *blen_group = NULL, *blen_id = NULL;
|
|
|
|
path = file_path = org_path;
|
|
if (source == THB_SOURCE_BLEND) {
|
|
if (BLO_library_path_explode(path, path_buff, &blen_group, &blen_id)) {
|
|
if (blen_group) {
|
|
if (!blen_id) {
|
|
/* No preview for blen groups */
|
|
return NULL;
|
|
}
|
|
file_path = path_buff; /* path needs to be a valid file! */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BLI_stat(file_path, &st) == -1) {
|
|
return NULL;
|
|
}
|
|
if (!uri_from_filename(path, uri)) {
|
|
return NULL;
|
|
}
|
|
if (thumbpath_from_uri(uri, thumb_path, sizeof(thumb_path), THB_FAIL)) {
|
|
/* failure thumb exists, don't try recreating */
|
|
if (BLI_exists(thumb_path)) {
|
|
/* clear out of date fail case (note for blen IDs we use blender file itself here) */
|
|
if (BLI_file_older(thumb_path, file_path)) {
|
|
BLI_delete(thumb_path, false, false);
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (thumbpathname_from_uri(
|
|
uri, thumb_path, sizeof(thumb_path), thumb_name, sizeof(thumb_name), size)) {
|
|
if (BLI_path_ncmp(path, thumb_path, sizeof(thumb_path)) == 0) {
|
|
img = IMB_loadiffname(path, IB_rect, NULL);
|
|
}
|
|
else {
|
|
img = IMB_loadiffname(thumb_path, IB_rect | IB_metadata, NULL);
|
|
if (img) {
|
|
bool regenerate = false;
|
|
|
|
char mtime[40];
|
|
char thumb_hash[33];
|
|
char thumb_hash_curr[33];
|
|
|
|
const bool use_hash = thumbhash_from_path(file_path, source, thumb_hash);
|
|
|
|
if (IMB_metadata_get_field(img->metadata, "Thumb::MTime", mtime, sizeof(mtime))) {
|
|
regenerate = (st.st_mtime != atol(mtime));
|
|
}
|
|
else {
|
|
/* illegal thumb, regenerate it! */
|
|
regenerate = true;
|
|
}
|
|
|
|
if (use_hash && !regenerate) {
|
|
if (IMB_metadata_get_field(
|
|
img->metadata, "X-Blender::Hash", thumb_hash_curr, sizeof(thumb_hash_curr))) {
|
|
regenerate = !STREQ(thumb_hash, thumb_hash_curr);
|
|
}
|
|
else {
|
|
regenerate = true;
|
|
}
|
|
}
|
|
|
|
if (regenerate) {
|
|
/* recreate all thumbs */
|
|
IMB_freeImBuf(img);
|
|
img = NULL;
|
|
IMB_thumb_delete(path, THB_NORMAL);
|
|
IMB_thumb_delete(path, THB_LARGE);
|
|
IMB_thumb_delete(path, THB_FAIL);
|
|
img = thumb_create_or_fail(
|
|
file_path, uri, thumb_name, use_hash, thumb_hash, blen_group, blen_id, size, source);
|
|
}
|
|
}
|
|
else {
|
|
char thumb_hash[33];
|
|
const bool use_hash = thumbhash_from_path(file_path, source, thumb_hash);
|
|
|
|
img = thumb_create_or_fail(
|
|
file_path, uri, thumb_name, use_hash, thumb_hash, blen_group, blen_id, size, source);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Our imbuf **must** have a valid rect (i.e. 8-bits/channels)
|
|
* data, we rely on this in draw code.
|
|
* However, in some cases we may end loading 16bits PNGs, which generated float buffers.
|
|
* This should be taken care of in generation step, but add also a safeguard here! */
|
|
if (img) {
|
|
IMB_rect_from_float(img);
|
|
imb_freerectfloatImBuf(img);
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
/* ***** Threading ***** */
|
|
/* Thumbnail handling is not really threadsafe in itself.
|
|
* However, as long as we do not operate on the same file, we shall have no collision.
|
|
* So idea is to 'lock' a given source file path.
|
|
*/
|
|
|
|
static struct IMBThumbLocks {
|
|
GSet *locked_paths;
|
|
int lock_counter;
|
|
ThreadCondition cond;
|
|
} thumb_locks = {0};
|
|
|
|
void IMB_thumb_locks_acquire(void)
|
|
{
|
|
BLI_thread_lock(LOCK_IMAGE);
|
|
|
|
if (thumb_locks.lock_counter == 0) {
|
|
BLI_assert(thumb_locks.locked_paths == NULL);
|
|
thumb_locks.locked_paths = BLI_gset_str_new(__func__);
|
|
BLI_condition_init(&thumb_locks.cond);
|
|
}
|
|
thumb_locks.lock_counter++;
|
|
|
|
BLI_assert(thumb_locks.locked_paths != NULL);
|
|
BLI_assert(thumb_locks.lock_counter > 0);
|
|
BLI_thread_unlock(LOCK_IMAGE);
|
|
}
|
|
|
|
void IMB_thumb_locks_release(void)
|
|
{
|
|
BLI_thread_lock(LOCK_IMAGE);
|
|
BLI_assert((thumb_locks.locked_paths != NULL) && (thumb_locks.lock_counter > 0));
|
|
|
|
thumb_locks.lock_counter--;
|
|
if (thumb_locks.lock_counter == 0) {
|
|
BLI_gset_free(thumb_locks.locked_paths, MEM_freeN);
|
|
thumb_locks.locked_paths = NULL;
|
|
BLI_condition_end(&thumb_locks.cond);
|
|
}
|
|
|
|
BLI_thread_unlock(LOCK_IMAGE);
|
|
}
|
|
|
|
void IMB_thumb_path_lock(const char *path)
|
|
{
|
|
void *key = BLI_strdup(path);
|
|
|
|
BLI_thread_lock(LOCK_IMAGE);
|
|
BLI_assert((thumb_locks.locked_paths != NULL) && (thumb_locks.lock_counter > 0));
|
|
|
|
if (thumb_locks.locked_paths) {
|
|
while (!BLI_gset_add(thumb_locks.locked_paths, key)) {
|
|
BLI_condition_wait_global_mutex(&thumb_locks.cond, LOCK_IMAGE);
|
|
}
|
|
}
|
|
|
|
BLI_thread_unlock(LOCK_IMAGE);
|
|
}
|
|
|
|
void IMB_thumb_path_unlock(const char *path)
|
|
{
|
|
const void *key = path;
|
|
|
|
BLI_thread_lock(LOCK_IMAGE);
|
|
BLI_assert((thumb_locks.locked_paths != NULL) && (thumb_locks.lock_counter > 0));
|
|
|
|
if (thumb_locks.locked_paths) {
|
|
if (!BLI_gset_remove(thumb_locks.locked_paths, key, MEM_freeN)) {
|
|
BLI_assert_unreachable();
|
|
}
|
|
BLI_condition_notify_all(&thumb_locks.cond);
|
|
}
|
|
|
|
BLI_thread_unlock(LOCK_IMAGE);
|
|
}
|