diff --git a/source/blender/blenlib/BLI_fileops.h b/source/blender/blenlib/BLI_fileops.h index 3ee22e4ad0a7..e2bd8fd9b8d5 100644 --- a/source/blender/blenlib/BLI_fileops.h +++ b/source/blender/blenlib/BLI_fileops.h @@ -1,177 +1,179 @@ /* * 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) 2001-2002 by NaN Holding BV. * All rights reserved. */ /** \file * \ingroup bli * \brief File and directory operations. * */ #ifndef __BLI_FILEOPS_H__ #define __BLI_FILEOPS_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif /* for size_t (needed on windows) */ #include #include /* for PATH_MAX */ #include "BLI_compiler_attrs.h" #ifndef PATH_MAX # define PATH_MAX 4096 #endif /* Common */ int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_copy(const char *path, const char *to) ATTR_NONNULL(); int BLI_rename(const char *from, const char *to) ATTR_NONNULL(); int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL(); int BLI_delete_soft(const char *path, const char **error_message) ATTR_NONNULL(); #if 0 /* Unused */ int BLI_move(const char *path, const char *to) ATTR_NONNULL(); int BLI_create_symlink(const char *path, const char *to) ATTR_NONNULL(); #endif /* keep in sync with the definition of struct direntry in BLI_fileops_types.h */ #ifdef WIN32 # if defined(_MSC_VER) typedef struct _stat64 BLI_stat_t; # else typedef struct _stat BLI_stat_t; # endif #else typedef struct stat BLI_stat_t; #endif int BLI_fstat(int fd, BLI_stat_t *buffer) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_stat(const char *path, BLI_stat_t *buffer) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); #ifdef WIN32 int BLI_wstat(const wchar_t *path, BLI_stat_t *buffer); #endif typedef enum eFileAttributes { FILE_ATTR_READONLY = 1 << 0, /* Read-only or Immutable. */ FILE_ATTR_HIDDEN = 1 << 1, /* Hidden or invisible. */ FILE_ATTR_SYSTEM = 1 << 2, /* Used by the Operating System. */ FILE_ATTR_ARCHIVE = 1 << 3, /* Marked as archived. */ FILE_ATTR_COMPRESSED = 1 << 4, /* Compressed. */ FILE_ATTR_ENCRYPTED = 1 << 5, /* Encrypted. */ FILE_ATTR_RESTRICTED = 1 << 6, /* Protected by OS. */ FILE_ATTR_TEMPORARY = 1 << 7, /* Used for temporary storage. */ FILE_ATTR_SPARSE_FILE = 1 << 8, /* Sparse File. */ FILE_ATTR_OFFLINE = 1 << 9, /* Data is not immediately available. */ FILE_ATTR_ALIAS = 1 << 10, /* Mac Alias or Windows Lnk. File-based redirection. */ FILE_ATTR_REPARSE_POINT = 1 << 11, /* File has associated reparse point. */ FILE_ATTR_SYMLINK = 1 << 12, /* Reference to another file. */ FILE_ATTR_JUNCTION_POINT = 1 << 13, /* Folder Symlink. */ FILE_ATTR_MOUNT_POINT = 1 << 14, /* Volume mounted as a folder. */ FILE_ATTR_HARDLINK = 1 << 15, /* Duplicated directory entry. */ } eFileAttributes; #define FILE_ATTR_ANY_LINK \ (FILE_ATTR_ALIAS | FILE_ATTR_REPARSE_POINT | FILE_ATTR_SYMLINK | FILE_ATTR_JUNCTION_POINT | \ FILE_ATTR_MOUNT_POINT | FILE_ATTR_HARDLINK) +void BLI_file_alias_target(char *target, const char *filepath); + /* Directories */ struct direntry; bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BLI_is_file(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BLI_dir_create_recursive(const char *dir) ATTR_NONNULL(); double BLI_dir_free_space(const char *dir) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); char *BLI_current_working_dir(char *dir, const size_t maxlen) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); eFileAttributes BLI_file_attributes(const char *path); /* Filelist */ unsigned int BLI_filelist_dir_contents(const char *dir, struct direntry **r_filelist); void BLI_filelist_entry_duplicate(struct direntry *dst, const struct direntry *src); void BLI_filelist_duplicate(struct direntry **dest_filelist, struct direntry *const src_filelist, const unsigned int nrentries); void BLI_filelist_entry_free(struct direntry *entry); void BLI_filelist_free(struct direntry *filelist, const unsigned int nrentries); void BLI_filelist_entry_size_to_string(const struct stat *st, const uint64_t sz, const bool compact, char r_size[]); void BLI_filelist_entry_mode_to_string( const struct stat *st, const bool compact, char r_mode1[], char r_mode2[], char r_mode3[]); void BLI_filelist_entry_owner_to_string(const struct stat *st, const bool compact, char r_owner[]); void BLI_filelist_entry_datetime_to_string(const struct stat *st, const int64_t ts, const bool compact, char r_time[], char r_date[], bool *r_is_today, bool *r_is_yesterday); /* Files */ FILE *BLI_fopen(const char *filename, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); void *BLI_gzopen(const char *filename, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_open(const char *filename, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_access(const char *filename, int mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BLI_file_is_writable(const char *file) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); bool BLI_file_touch(const char *file) ATTR_NONNULL(); #if 0 /* UNUSED */ int BLI_file_gzip(const char *from, const char *to) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); #endif char *BLI_file_ungzip_to_mem(const char *from_file, int *r_size) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); size_t BLI_file_descriptor_size(int file) ATTR_WARN_UNUSED_RESULT; size_t BLI_file_size(const char *file) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /* compare if one was last modified before the other */ bool BLI_file_older(const char *file1, const char *file2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /* read ascii file as lines, empty list if reading fails */ struct LinkNode *BLI_file_read_as_lines(const char *file) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); void *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size); void *BLI_file_read_binary_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size); void BLI_file_free_lines(struct LinkNode *lines); /* this weirdo pops up in two places ... */ #if !defined(WIN32) # ifndef O_BINARY # define O_BINARY 0 # endif #else void BLI_get_short_name(char short_name[256], const char *filename); #endif #ifdef __cplusplus } #endif #endif /* __BLI_FILEOPS_H__ */ diff --git a/source/blender/blenlib/intern/storage.c b/source/blender/blenlib/intern/storage.c index 04b3e8abca29..2abc4b19acbb 100644 --- a/source/blender/blenlib/intern/storage.c +++ b/source/blender/blenlib/intern/storage.c @@ -146,219 +146,236 @@ double BLI_dir_free_space(const char *dir) } strcpy(name, dir); if (len) { slash = strrchr(name, '/'); if (slash) { slash[1] = 0; } } else { strcpy(name, "/"); } # if defined(USE_STATFS_STATVFS) if (statvfs(name, &disk)) { return -1; } # elif defined(USE_STATFS_4ARGS) if (statfs(name, &disk, sizeof(struct statfs), 0)) { return -1; } # else if (statfs(name, &disk)) { return -1; } # endif return (((double)disk.f_bsize) * ((double)disk.f_bfree)); #endif } /** * Returns the file size of an opened file descriptor. */ size_t BLI_file_descriptor_size(int file) { BLI_stat_t st; if ((file < 0) || (BLI_fstat(file, &st) == -1)) { return -1; } return st.st_size; } /** * Returns the size of a file. */ size_t BLI_file_size(const char *path) { BLI_stat_t stats; if (BLI_stat(path, &stats) == -1) { return -1; } return stats.st_size; } eFileAttributes BLI_file_attributes(const char *path) { int ret = 0; #ifdef WIN32 wchar_t wline[FILE_MAXDIR]; BLI_strncpy_wchar_from_utf8(wline, path, ARRAY_SIZE(wline)); DWORD attr = GetFileAttributesW(wline); if (attr & FILE_ATTRIBUTE_READONLY) { ret |= FILE_ATTR_READONLY; } if (attr & FILE_ATTRIBUTE_HIDDEN) { ret |= FILE_ATTR_HIDDEN; } if (attr & FILE_ATTRIBUTE_SYSTEM) { ret |= FILE_ATTR_SYSTEM; } if (attr & FILE_ATTRIBUTE_ARCHIVE) { ret |= FILE_ATTR_ARCHIVE; } if (attr & FILE_ATTRIBUTE_COMPRESSED) { ret |= FILE_ATTR_COMPRESSED; } if (attr & FILE_ATTRIBUTE_ENCRYPTED) { ret |= FILE_ATTR_ENCRYPTED; } if (attr & FILE_ATTRIBUTE_TEMPORARY) { ret |= FILE_ATTR_TEMPORARY; } if (attr & FILE_ATTRIBUTE_SPARSE_FILE) { ret |= FILE_ATTR_SPARSE_FILE; } if (attr & FILE_ATTRIBUTE_OFFLINE) { ret |= FILE_ATTR_OFFLINE; } if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { ret |= FILE_ATTR_REPARSE_POINT; } #endif #ifdef __APPLE__ /* TODO: - * If Hidden (Invisible) set FILE_ATTR_HIDDEN - * If Locked set FILE_ATTR_READONLY - * If Restricted set FILE_ATTR_RESTRICTED + * If NSURLIsHiddenKey set FILE_ATTR_HIDDEN + * If not NSURLIsReadableKey or NSURLIsUserImmutableKey set FILE_ATTR_READONLY + * If NSURLIsApplicationKey set FILE_ATTR_SYSTEM + * If NSURLIsAliasFileKey set FILE_ATTR_ALIAS + * If NSURLIsSymbolicLinkKey set FILE_ATTR_SYMLINK */ #endif #ifdef __linux__ /* TODO: * If Immutable set FILE_ATTR_READONLY * If Archived set FILE_ATTR_ARCHIVE */ #endif return ret; } +/** + * Returns the target path of a file-based redirection, like Mac Alias or Win32 Shortcut file. + */ +void BLI_file_alias_target(char *target, const char *filepath) +{ +#ifdef __APPLE__ + /* Find target in Mac Alias file, copy to target string. */ +#endif + +#ifdef WIN32 + /* TODO: Find target in Win32 Shortcut - Shell Link (.lnk) file. */ + /* Format: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/ */ +#endif +} + /** * Returns the st_mode from stat-ing the specified path name, or 0 if stat fails * (most likely doesn't exist or no access). */ int BLI_exists(const char *name) { #if defined(WIN32) BLI_stat_t st; wchar_t *tmp_16 = alloc_utf16_from_8(name, 1); int len, res; len = wcslen(tmp_16); /* in Windows #stat doesn't recognize dir ending on a slash * so we remove it here */ if (len > 3 && (tmp_16[len - 1] == L'\\' || tmp_16[len - 1] == L'/')) { tmp_16[len - 1] = '\0'; } /* two special cases where the trailing slash is needed: * 1. after the share part of a UNC path * 2. after the C:\ when the path is the volume only */ if ((len >= 3) && (tmp_16[0] == L'\\') && (tmp_16[1] == L'\\')) { BLI_cleanup_unc_16(tmp_16); } if ((tmp_16[1] == L':') && (tmp_16[2] == L'\0')) { tmp_16[2] = L'\\'; tmp_16[3] = L'\0'; } res = BLI_wstat(tmp_16, &st); free(tmp_16); if (res == -1) { return (0); } #else struct stat st; BLI_assert(!BLI_path_is_rel(name)); if (stat(name, &st)) { return (0); } #endif return (st.st_mode); } #ifdef WIN32 int BLI_fstat(int fd, BLI_stat_t *buffer) { # if defined(_MSC_VER) return _fstat64(fd, buffer); # else return _fstat(fd, buffer); # endif } int BLI_stat(const char *path, BLI_stat_t *buffer) { int r; UTF16_ENCODE(path); r = BLI_wstat(path_16, buffer); UTF16_UN_ENCODE(path); return r; } int BLI_wstat(const wchar_t *path, BLI_stat_t *buffer) { # if defined(_MSC_VER) return _wstat64(path, buffer); # else return _wstat(path, buffer); # endif } #else int BLI_fstat(int fd, struct stat *buffer) { return fstat(fd, buffer); } int BLI_stat(const char *path, struct stat *buffer) { return stat(path, buffer); } #endif /** * Does the specified path point to a directory? * \note Would be better in fileops.c except that it needs stat.h so add here */ bool BLI_is_dir(const char *file) { return S_ISDIR(BLI_exists(file)); } /** * Does the specified path point to a non-directory? */ bool BLI_is_file(const char *path) diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 61eb4db83004..26edd2fe3a77 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -1447,200 +1447,204 @@ void file_operator_to_sfile(bContext *C, SpaceFile *sfile, wmOperator *op) } if ((prop = RNA_struct_find_property(op->ptr, "directory"))) { RNA_property_string_get(op->ptr, prop, sfile->params->dir); } } /* we could check for relative_path property which is used when converting * in the other direction but doesn't hurt to do this every time */ BLI_path_abs(sfile->params->dir, BKE_main_blendfile_path(bmain)); /* XXX, files and dirs updates missing, not really so important though */ } /** * Use to set the file selector path from some arbitrary source. */ void file_sfile_filepath_set(SpaceFile *sfile, const char *filepath) { BLI_assert(BLI_exists(filepath)); if (BLI_is_dir(filepath)) { BLI_strncpy(sfile->params->dir, filepath, sizeof(sfile->params->dir)); } else { if ((sfile->params->flag & FILE_DIRSEL_ONLY) == 0) { BLI_split_dirfile(filepath, sfile->params->dir, sfile->params->file, sizeof(sfile->params->dir), sizeof(sfile->params->file)); } else { BLI_split_dir_part(filepath, sfile->params->dir, sizeof(sfile->params->dir)); } } } void file_draw_check(bContext *C) { SpaceFile *sfile = CTX_wm_space_file(C); wmOperator *op = sfile->op; if (op) { /* fail on reload */ if (op->type->check) { file_sfile_to_operator(C, op, sfile); /* redraw */ if (op->type->check(C, op)) { file_operator_to_sfile(C, sfile, op); /* redraw, else the changed settings wont get updated */ ED_area_tag_redraw(CTX_wm_area(C)); } } } } /* for use with; UI_block_func_set */ void file_draw_check_cb(bContext *C, void *UNUSED(arg1), void *UNUSED(arg2)) { file_draw_check(C); } bool file_draw_check_exists(SpaceFile *sfile) { if (sfile->op) { /* fails on reload */ if (sfile->params && (sfile->params->flag & FILE_CHECK_EXISTING)) { char filepath[FILE_MAX]; BLI_join_dirfile(filepath, sizeof(filepath), sfile->params->dir, sfile->params->file); if (BLI_is_file(filepath)) { return true; } } } return false; } int file_exec(bContext *C, wmOperator *exec_op) { Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); const struct FileDirEntry *file = filelist_file(sfile->files, sfile->params->active_file); char filepath[FILE_MAX]; /* directory change */ if (file && (file->typeflag & FILE_TYPE_DIR)) { if (!file->relpath) { return OPERATOR_CANCELLED; } if (FILENAME_IS_PARENT(file->relpath)) { BLI_parent_dir(sfile->params->dir); } else { BLI_cleanup_path(BKE_main_blendfile_path(bmain), sfile->params->dir); BLI_path_append(sfile->params->dir, sizeof(sfile->params->dir) - 1, file->relpath); BLI_add_slash(sfile->params->dir); } + /* If this is an Alias or Shortcut use redirection path instead. */ + if (file->redirection_path) { + strcpy(sfile->params->dir, file->redirection_path); + } ED_file_change_dir(C); } /* opening file - sends events now, so things get handled on windowqueue level */ else if (sfile->op) { wmOperator *op = sfile->op; /* When used as a macro, for double-click, to prevent closing when double-clicking on item. */ if (RNA_boolean_get(exec_op->ptr, "need_active")) { const int numfiles = filelist_files_ensure(sfile->files); int i, active = 0; for (i = 0; i < numfiles; i++) { if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { active = 1; break; } } if (active == 0) { return OPERATOR_CANCELLED; } } sfile->op = NULL; file_sfile_to_operator_ex(C, op, sfile, filepath); if (BLI_exists(sfile->params->dir)) { fsmenu_insert_entry(ED_fsmenu_get(), FS_CATEGORY_RECENT, sfile->params->dir, NULL, ICON_FILE_FOLDER, FS_INSERT_SAVE | FS_INSERT_FIRST); } BLI_make_file_string(BKE_main_blendfile_path(bmain), filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE); fsmenu_write_file(ED_fsmenu_get(), filepath); WM_event_fileselect_event(wm, op, EVT_FILESELECT_EXEC); } return OPERATOR_FINISHED; } void FILE_OT_execute(struct wmOperatorType *ot) { PropertyRNA *prop; /* identifiers */ ot->name = "Execute File Window"; ot->description = "Execute selected file"; ot->idname = "FILE_OT_execute"; /* api callbacks */ ot->exec = file_exec; ot->poll = file_operator_poll; /* properties */ prop = RNA_def_boolean(ot->srna, "need_active", 0, "Need Active", "Only execute if there's an active selected file in the file list"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } int file_parent_exec(bContext *C, wmOperator *UNUSED(unused)) { Main *bmain = CTX_data_main(C); SpaceFile *sfile = CTX_wm_space_file(C); if (sfile->params) { if (BLI_parent_dir(sfile->params->dir)) { BLI_cleanup_dir(BKE_main_blendfile_path(bmain), sfile->params->dir); ED_file_change_dir(C); if (sfile->params->recursion_level > 1) { /* Disable 'dirtree' recursion when going up in tree. */ sfile->params->recursion_level = 0; filelist_setrecursion(sfile->files, sfile->params->recursion_level); } WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); } } return OPERATOR_FINISHED; } void FILE_OT_parent(struct wmOperatorType *ot) { /* identifiers */ ot->name = "Parent File"; ot->description = "Move to parent directory"; ot->idname = "FILE_OT_parent"; /* api callbacks */ ot->exec = file_parent_exec; ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ } diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 28e6d95beb33..6a749554c6cd 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -113,200 +113,202 @@ void folderlist_popdir(struct ListBase *folderlist, char *dir) } } /* delete the folder next or use setdir directly before PREVIOUS OP */ } void folderlist_pushdir(ListBase *folderlist, const char *dir) { struct FolderList *folder, *previous_folder; previous_folder = folderlist->last; /* check if already exists */ if (previous_folder && previous_folder->foldername) { if (BLI_path_cmp(previous_folder->foldername, dir) == 0) { return; } } /* create next folder element */ folder = MEM_mallocN(sizeof(*folder), __func__); folder->foldername = BLI_strdup(dir); /* add it to the end of the list */ BLI_addtail(folderlist, folder); } const char *folderlist_peeklastdir(ListBase *folderlist) { struct FolderList *folder; if (!folderlist->last) { return NULL; } folder = folderlist->last; return folder->foldername; } int folderlist_clear_next(struct SpaceFile *sfile) { struct FolderList *folder; /* if there is no folder_next there is nothing we can clear */ if (!sfile->folders_next) { return 0; } /* if previous_folder, next_folder or refresh_folder operators are executed * it doesn't clear folder_next */ folder = sfile->folders_prev->last; if ((!folder) || (BLI_path_cmp(folder->foldername, sfile->params->dir) == 0)) { return 0; } /* eventually clear flist->folders_next */ return 1; } /* not listbase itself */ void folderlist_free(ListBase *folderlist) { if (folderlist) { FolderList *folder; for (folder = folderlist->first; folder; folder = folder->next) { MEM_freeN(folder->foldername); } BLI_freelistN(folderlist); } } ListBase *folderlist_duplicate(ListBase *folderlist) { if (folderlist) { ListBase *folderlistn = MEM_callocN(sizeof(*folderlistn), __func__); FolderList *folder; BLI_duplicatelist(folderlistn, folderlist); for (folder = folderlistn->first; folder; folder = folder->next) { folder->foldername = MEM_dupallocN(folder->foldername); } return folderlistn; } return NULL; } /* ------------------FILELIST------------------------ */ typedef struct FileListInternEntry { struct FileListInternEntry *next, *prev; /** ASSET_UUID_LENGTH */ char uuid[16]; /** eFileSel_File_Types */ int typeflag; /** ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */ int blentype; char *relpath; + /** Optional absolute path, only allocated when needed. */ + char *redirection_path; /** not strictly needed, but used during sorting, avoids to have to recompute it there... */ char *name; /** Defined in BLI_fileops.h */ eFileAttributes attributes; BLI_stat_t st; } FileListInternEntry; typedef struct FileListIntern { /** FileListInternEntry items. */ ListBase entries; FileListInternEntry **filtered; char curr_uuid[16]; /* Used to generate uuid during internal listing. */ } FileListIntern; #define FILELIST_ENTRYCACHESIZE_DEFAULT 1024 /* Keep it a power of two! */ typedef struct FileListEntryCache { size_t size; /* The size of the cache... */ int flags; /* This one gathers all entries from both block and misc caches. Used for easy bulk-freing. */ ListBase cached_entries; /* Block cache: all entries between start and end index. * used for part of the list on display. */ FileDirEntry **block_entries; int block_start_index, block_end_index, block_center_index, block_cursor; /* Misc cache: random indices, FIFO behavior. * Note: Not 100% sure we actually need that, time will say. */ int misc_cursor; int *misc_entries_indices; GHash *misc_entries; /* Allows to quickly get a cached entry from its UUID. */ GHash *uuids; /* Previews handling. */ TaskPool *previews_pool; ThreadQueue *previews_done; } FileListEntryCache; /* FileListCache.flags */ enum { FLC_IS_INIT = 1 << 0, FLC_PREVIEWS_ACTIVE = 1 << 1, }; typedef struct FileListEntryPreview { char path[FILE_MAX]; unsigned int flags; int index; ImBuf *img; } FileListEntryPreview; typedef struct FileListFilter { unsigned int filter; unsigned int filter_id; char filter_glob[FILE_MAXFILE]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ short flags; } FileListFilter; /* FileListFilter.flags */ enum { FLF_DO_FILTER = 1 << 0, FLF_HIDE_DOT = 1 << 1, FLF_HIDE_PARENT = 1 << 2, FLF_HIDE_LIB_DIR = 1 << 3, }; typedef struct FileList { FileDirEntryArr filelist; short prv_w; short prv_h; short flags; short sort; FileListFilter filter_data; struct FileListIntern filelist_intern; struct FileListEntryCache filelist_cache; /* We need to keep those info outside of actual filelist items, * because those are no more persistent * (only generated on demand, and freed as soon as possible). * Persistent part (mere list of paths + stat info) * is kept as small as possible, and filebrowser-agnostic. */ GHash *selection_state; short max_recursion; short recursion_level; @@ -1047,269 +1049,275 @@ static int filelist_geticon_ex(FileDirEntry *file, } else if (typeflag & FILE_TYPE_BTX) { return ICON_FILE_BLANK; } else if (typeflag & FILE_TYPE_COLLADA) { return ICON_FILE_3D; } else if (typeflag & FILE_TYPE_ALEMBIC) { return ICON_FILE_3D; } else if (typeflag & FILE_TYPE_USD) { return ICON_FILE_3D; } else if (typeflag & FILE_TYPE_OBJECT_IO) { return ICON_FILE_3D; } else if (typeflag & FILE_TYPE_TEXT) { return ICON_FILE_TEXT; } else if (typeflag & FILE_TYPE_ARCHIVE) { return ICON_FILE_ARCHIVE; } else if (typeflag & FILE_TYPE_BLENDERLIB) { const int ret = UI_idcode_icon_get(file->blentype); if (ret != ICON_NONE) { return ret; } } return is_main ? ICON_FILE_BLANK : ICON_NONE; } int filelist_geticon(struct FileList *filelist, const int index, const bool is_main) { FileDirEntry *file = filelist_geticon_get_file(filelist, index); return filelist_geticon_ex(file, filelist->filelist.root, is_main, false); } /* ********** Main ********** */ static void parent_dir_until_exists_or_default_root(char *dir) { if (!BLI_parent_dir_until_exists(dir)) { #ifdef WIN32 get_default_root(dir); #else strcpy(dir, "/"); #endif } } static bool filelist_checkdir_dir(struct FileList *UNUSED(filelist), char *r_dir, const bool do_change) { if (do_change) { parent_dir_until_exists_or_default_root(r_dir); return true; } else { return BLI_is_dir(r_dir); } } static bool filelist_checkdir_lib(struct FileList *UNUSED(filelist), char *r_dir, const bool do_change) { char tdir[FILE_MAX_LIBEXTRA]; char *name; const bool is_valid = (BLI_is_dir(r_dir) || (BLO_library_path_explode(r_dir, tdir, NULL, &name) && BLI_is_file(tdir) && !name)); if (do_change && !is_valid) { /* if not a valid library, we need it to be a valid directory! */ parent_dir_until_exists_or_default_root(r_dir); return true; } return is_valid; } static bool filelist_checkdir_main(struct FileList *filelist, char *r_dir, const bool do_change) { /* TODO */ return filelist_checkdir_lib(filelist, r_dir, do_change); } static void filelist_entry_clear(FileDirEntry *entry) { if (entry->name) { MEM_freeN(entry->name); } if (entry->description) { MEM_freeN(entry->description); } if (entry->relpath) { MEM_freeN(entry->relpath); } + if (entry->redirection_path) { + MEM_freeN(entry->redirection_path); + } if (entry->image) { IMB_freeImBuf(entry->image); } /* For now, consider FileDirEntryRevision::poin as not owned here, * so no need to do anything about it */ if (!BLI_listbase_is_empty(&entry->variants)) { FileDirEntryVariant *var; for (var = entry->variants.first; var; var = var->next) { if (var->name) { MEM_freeN(var->name); } if (var->description) { MEM_freeN(var->description); } if (!BLI_listbase_is_empty(&var->revisions)) { FileDirEntryRevision *rev; for (rev = var->revisions.first; rev; rev = rev->next) { if (rev->comment) { MEM_freeN(rev->comment); } } BLI_freelistN(&var->revisions); } } /* TODO: tags! */ BLI_freelistN(&entry->variants); } else if (entry->entry) { MEM_freeN(entry->entry); } } static void filelist_entry_free(FileDirEntry *entry) { filelist_entry_clear(entry); MEM_freeN(entry); } static void filelist_direntryarr_free(FileDirEntryArr *array) { #if 0 FileDirEntry *entry, *entry_next; for (entry = array->entries.first; entry; entry = entry_next) { entry_next = entry->next; filelist_entry_free(entry); } BLI_listbase_clear(&array->entries); #else BLI_assert(BLI_listbase_is_empty(&array->entries)); #endif array->nbr_entries = 0; array->nbr_entries_filtered = -1; array->entry_idx_start = -1; array->entry_idx_end = -1; } static void filelist_intern_entry_free(FileListInternEntry *entry) { if (entry->relpath) { MEM_freeN(entry->relpath); } + if (entry->redirection_path) { + MEM_freeN(entry->redirection_path); + } if (entry->name) { MEM_freeN(entry->name); } MEM_freeN(entry); } static void filelist_intern_free(FileListIntern *filelist_intern) { FileListInternEntry *entry, *entry_next; for (entry = filelist_intern->entries.first; entry; entry = entry_next) { entry_next = entry->next; filelist_intern_entry_free(entry); } BLI_listbase_clear(&filelist_intern->entries); MEM_SAFE_FREE(filelist_intern->filtered); } static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata, int UNUSED(threadid)) { FileListEntryCache *cache = BLI_task_pool_userdata(pool); FileListEntryPreview *preview = taskdata; ThumbSource source = 0; // printf("%s: Start (%d)...\n", __func__, threadid); // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); BLI_assert(preview->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)); if (preview->flags & FILE_TYPE_IMAGE) { source = THB_SOURCE_IMAGE; } else if (preview->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)) { source = THB_SOURCE_BLEND; } else if (preview->flags & FILE_TYPE_MOVIE) { source = THB_SOURCE_MOVIE; } else if (preview->flags & FILE_TYPE_FTFONT) { source = THB_SOURCE_FONT; } IMB_thumb_path_lock(preview->path); preview->img = IMB_thumb_manage(preview->path, THB_LARGE, source); IMB_thumb_path_unlock(preview->path); /* Used to tell free func to not free anything. * Note that we do not care about cas result here, * we only want value attribution itself to be atomic (and memory barier).*/ atomic_cas_uint32(&preview->flags, preview->flags, 0); BLI_thread_queue_push(cache->previews_done, preview); // printf("%s: End (%d)...\n", __func__, threadid); } static void filelist_cache_preview_freef(TaskPool *__restrict UNUSED(pool), void *taskdata, int UNUSED(threadid)) { FileListEntryPreview *preview = taskdata; /* If preview->flag is empty, it means that preview has already been generated and * added to done queue, we do not own it anymore. */ if (preview->flags) { if (preview->img) { IMB_freeImBuf(preview->img); } MEM_freeN(preview); } } static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) { if (!cache->previews_pool) { TaskScheduler *scheduler = BLI_task_scheduler_get(); cache->previews_pool = BLI_task_pool_create_background(scheduler, cache); cache->previews_done = BLI_thread_queue_init(); IMB_thumb_locks_acquire(); } } static void filelist_cache_previews_clear(FileListEntryCache *cache) { FileListEntryPreview *preview; if (cache->previews_pool) { BLI_task_pool_cancel(cache->previews_pool); while ((preview = BLI_thread_queue_pop_timeout(cache->previews_done, 0))) { // printf("%s: DONE %d - %s - %p\n", __func__, preview->index, preview->path, // preview->img); @@ -1563,200 +1571,203 @@ static const char *fileentry_uiname(const char *root, name = (char *)relpath; } else { name = (char *)BLI_path_basename(relpath); } } BLI_assert(name); return name; } const char *filelist_dir(struct FileList *filelist) { return filelist->filelist.root; } bool filelist_is_dir(struct FileList *filelist, const char *path) { return filelist->checkdirf(filelist, (char *)path, false); } /** * May modify in place given r_dir, which is expected to be FILE_MAX_LIBEXTRA length. */ void filelist_setdir(struct FileList *filelist, char *r_dir) { BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA); BLI_cleanup_dir(BKE_main_blendfile_path_from_global(), r_dir); const bool is_valid_path = filelist->checkdirf(filelist, r_dir, true); BLI_assert(is_valid_path); UNUSED_VARS_NDEBUG(is_valid_path); if (!STREQ(filelist->filelist.root, r_dir)) { BLI_strncpy(filelist->filelist.root, r_dir, sizeof(filelist->filelist.root)); filelist->flags |= FL_FORCE_RESET; } } void filelist_setrecursion(struct FileList *filelist, const int recursion_level) { if (filelist->max_recursion != recursion_level) { filelist->max_recursion = recursion_level; filelist->flags |= FL_FORCE_RESET; } } bool filelist_force_reset(struct FileList *filelist) { return (filelist->flags & FL_FORCE_RESET) != 0; } bool filelist_is_ready(struct FileList *filelist) { return (filelist->flags & FL_IS_READY) != 0; } bool filelist_pending(struct FileList *filelist) { return (filelist->flags & FL_IS_PENDING) != 0; } /** * Limited version of full update done by space_file's file_refresh(), * to be used by operators and such. * Ensures given filelist is ready to be used (i.e. it is filtered and sorted), * unless it is tagged for a full refresh. */ int filelist_files_ensure(FileList *filelist) { if (!filelist_force_reset(filelist) || !filelist_empty(filelist)) { filelist_sort(filelist); filelist_filter(filelist); } return filelist->filelist.nbr_entries_filtered; } static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index) { FileListInternEntry *entry = filelist->filelist_intern.filtered[index]; FileListEntryCache *cache = &filelist->filelist_cache; FileDirEntry *ret; FileDirEntryRevision *rev; ret = MEM_callocN(sizeof(*ret), __func__); rev = MEM_callocN(sizeof(*rev), __func__); rev->size = (uint64_t)entry->st.st_size; rev->time = (int64_t)entry->st.st_mtime; ret->entry = rev; ret->relpath = BLI_strdup(entry->relpath); ret->name = BLI_strdup(entry->name); ret->description = BLI_strdupcat(filelist->filelist.root, entry->relpath); memcpy(ret->uuid, entry->uuid, sizeof(ret->uuid)); ret->blentype = entry->blentype; ret->typeflag = entry->typeflag; ret->attributes = entry->attributes; + if (entry->redirection_path) { + ret->redirection_path = BLI_strdup(entry->redirection_path); + } BLI_addtail(&cache->cached_entries, ret); return ret; } static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry) { BLI_remlink(&filelist->filelist_cache.cached_entries, entry); filelist_entry_free(entry); } static FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request) { FileDirEntry *ret = NULL, *old; FileListEntryCache *cache = &filelist->filelist_cache; const size_t cache_size = cache->size; int old_index; if ((index < 0) || (index >= filelist->filelist.nbr_entries_filtered)) { return ret; } if (index >= cache->block_start_index && index < cache->block_end_index) { const int idx = (index - cache->block_start_index + cache->block_cursor) % cache_size; return cache->block_entries[idx]; } if ((ret = BLI_ghash_lookup(cache->misc_entries, POINTER_FROM_INT(index)))) { return ret; } if (!use_request) { return NULL; } // printf("requesting file %d (not yet cached)\n", index); /* Else, we have to add new entry to 'misc' cache - and possibly make room for it first! */ ret = filelist_file_create_entry(filelist, index); old_index = cache->misc_entries_indices[cache->misc_cursor]; if ((old = BLI_ghash_popkey(cache->misc_entries, POINTER_FROM_INT(old_index), NULL))) { BLI_ghash_remove(cache->uuids, old->uuid, NULL, NULL); filelist_file_release_entry(filelist, old); } BLI_ghash_insert(cache->misc_entries, POINTER_FROM_INT(index), ret); BLI_ghash_insert(cache->uuids, ret->uuid, ret); cache->misc_entries_indices[cache->misc_cursor] = index; cache->misc_cursor = (cache->misc_cursor + 1) % cache_size; #if 0 /* Actually no, only block cached entries should have preview imho. */ if (cache->previews_pool) { filelist_cache_previews_push(filelist, ret, index); } #endif return ret; } FileDirEntry *filelist_file(struct FileList *filelist, int index) { return filelist_file_ex(filelist, index, true); } int filelist_file_findpath(struct FileList *filelist, const char *filename) { int fidx = -1; if (filelist->filelist.nbr_entries_filtered < 0) { return fidx; } /* XXX TODO Cache could probably use a ghash on paths too? Not really urgent though. * This is only used to find again renamed entry, * annoying but looks hairy to get rid of it currently. */ for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; if (STREQ(entry->relpath, filename)) { return fidx; } } return -1; } FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]) { if (filelist->filelist.nbr_entries_filtered < 0) { return NULL; } if (filelist->filelist_cache.uuids) { FileDirEntry *entry = BLI_ghash_lookup(filelist->filelist_cache.uuids, uuid); if (entry) { return entry; } } @@ -2390,200 +2401,209 @@ void filelist_entry_parent_select_set(FileList *filelist, filelist_entry_select_index_set(filelist, 0, select, flag, check); } } /* WARNING! dir must be FILE_MAX_LIBEXTRA long! */ bool filelist_islibrary(struct FileList *filelist, char *dir, char **group) { return BLO_library_path_explode(filelist->filelist.root, dir, group, NULL); } static int groupname_to_code(const char *group) { char buf[BLO_GROUP_MAX]; char *lslash; BLI_assert(group); BLI_strncpy(buf, group, sizeof(buf)); lslash = (char *)BLI_last_slash(buf); if (lslash) { lslash[0] = '\0'; } return buf[0] ? BKE_idcode_from_name(buf) : 0; } static unsigned int groupname_to_filter_id(const char *group) { int id_code = groupname_to_code(group); return BKE_idcode_to_idfilter(id_code); } /** * From here, we are in 'Job Context', * i.e. have to be careful about sharing stuff between background working thread. * and main one (used by UI among other things). */ typedef struct TodoDir { int level; char *dir; } TodoDir; static int filelist_readjob_list_dir(const char *root, ListBase *entries, const char *filter_glob, const bool do_lib, const char *main_name, const bool skip_currpar) { struct direntry *files; int nbr_files, nbr_entries = 0; char path[FILE_MAX]; nbr_files = BLI_filelist_dir_contents(root, &files); if (files) { int i = nbr_files; while (i--) { FileListInternEntry *entry; if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) { continue; } entry = MEM_callocN(sizeof(*entry), __func__); entry->relpath = MEM_dupallocN(files[i].relname); entry->st = files[i].s; BLI_join_dirfile(path, sizeof(path), root, entry->relpath); /* Set file type. */ if (S_ISDIR(files[i].s.st_mode)) { entry->typeflag = FILE_TYPE_DIR; } else if (do_lib && BLO_has_bfile_extension(entry->relpath)) { /* If we are considering .blend files as libs, promote them to directory status. */ entry->typeflag = FILE_TYPE_BLENDER; /* prevent current file being used as acceptable dir */ if (BLI_path_cmp(main_name, path) != 0) { entry->typeflag |= FILE_TYPE_DIR; } } /* Otherwise, do not check extensions for directories! */ else if (!(entry->typeflag & FILE_TYPE_DIR)) { entry->typeflag = file_extension_type(root, entry->relpath); if (filter_glob[0] && BLI_path_extension_check_glob(entry->relpath, filter_glob)) { entry->typeflag |= FILE_TYPE_OPERATOR; } } /* Set file attributes. */ entry->attributes = BLI_file_attributes(path); #ifndef WIN32 /* Set linux-style dot files hidden too. */ if (is_hidden_dot_filename(entry->relpath, entry)) { entry->attributes |= FILE_ATTR_HIDDEN; } #endif + if (entry->attributes & FILE_ATTR_ALIAS) { + /* This is a file-type redirection. */ + entry->redirection_path = MEM_callocN(FILE_MAXDIR, __func__); + BLI_file_alias_target(entry->redirection_path, path); + if (BLI_is_dir(entry->redirection_path)) { + entry->typeflag = FILE_TYPE_DIR; + } + } + BLI_addtail(entries, entry); nbr_entries++; } BLI_filelist_free(files, nbr_files); } return nbr_entries; } static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar) { FileListInternEntry *entry; LinkNode *ln, *names; int i, nnames, idcode = 0, nbr_entries = 0; char dir[FILE_MAX_LIBEXTRA], *group; bool ok; struct BlendHandle *libfiledata = NULL; /* name test */ ok = BLO_library_path_explode(root, dir, &group, NULL); if (!ok) { return nbr_entries; } /* there we go */ libfiledata = BLO_blendhandle_from_file(dir, NULL); if (libfiledata == NULL) { return nbr_entries; } /* memory for strings is passed into filelist[i].entry->relpath * and freed in filelist_entry_free. */ if (group) { idcode = groupname_to_code(group); names = BLO_blendhandle_get_datablock_names(libfiledata, idcode, &nnames); } else { names = BLO_blendhandle_get_linkable_groups(libfiledata); nnames = BLI_linklist_count(names); } BLO_blendhandle_close(libfiledata); if (!skip_currpar) { entry = MEM_callocN(sizeof(*entry), __func__); entry->relpath = BLI_strdup(FILENAME_PARENT); entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); BLI_addtail(entries, entry); nbr_entries++; } for (i = 0, ln = names; i < nnames; i++, ln = ln->next) { const char *blockname = ln->link; entry = MEM_callocN(sizeof(*entry), __func__); entry->relpath = BLI_strdup(blockname); entry->typeflag |= FILE_TYPE_BLENDERLIB; if (!(group && idcode)) { entry->typeflag |= FILE_TYPE_DIR; entry->blentype = groupname_to_code(blockname); } else { entry->blentype = idcode; } BLI_addtail(entries, entry); nbr_entries++; } BLI_linklist_free(names, free); return nbr_entries; } #if 0 /* Kept for reference here, in case we want to add back that feature later. * We do not need it currently. */ /* Code ***NOT*** updated for job stuff! */ static void filelist_readjob_main_rec(Main *bmain, FileList *filelist) { ID *id; FileDirEntry *files, *firstlib = NULL; ListBase *lb; int a, fake, idcode, ok, totlib, totbl; // filelist->type = FILE_MAIN; // XXX TODO: add modes to filebrowser BLI_assert(filelist->filelist.entries == NULL); if (filelist->filelist.root[0] == '/') { filelist->filelist.root[0] = '\0'; } if (filelist->filelist.root[0]) { idcode = groupname_to_code(filelist->filelist.root); if (idcode == 0) { filelist->filelist.root[0] = '\0'; } } if (filelist->dir[0] == 0) { diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index bdfe5040794e..a5ffc117aca6 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -856,201 +856,204 @@ typedef enum eFileSel_File_Types { FILE_TYPE_ALEMBIC = (1 << 16), /** For all kinds of recognized import/export formats. No need for specialized types. */ FILE_TYPE_OBJECT_IO = (1 << 17), FILE_TYPE_USD = (1 << 18), /** An FS directory (i.e. S_ISDIR on its path is true). */ FILE_TYPE_DIR = (1 << 30), FILE_TYPE_BLENDERLIB = (1u << 31), } eFileSel_File_Types; /* Selection Flags in filesel: struct direntry, unsigned char selflag */ typedef enum eDirEntry_SelectFlag { /* FILE_SEL_ACTIVE = (1 << 1), */ /* UNUSED */ FILE_SEL_HIGHLIGHTED = (1 << 2), FILE_SEL_SELECTED = (1 << 3), FILE_SEL_EDITING = (1 << 4), } eDirEntry_SelectFlag; /* ***** Related to file browser, but never saved in DNA, only here to help with RNA. ***** */ /** * About Unique identifier. * * Stored in a CustomProps once imported. * Each engine is free to use it as it likes - it will be the only thing passed to it by blender to * identify asset/variant/version (concatenating the three into a single 48 bytes one). * Assumed to be 128bits, handled as four integers due to lack of real bytes proptype in RNA :|. */ #define ASSET_UUID_LENGTH 16 /* Used to communicate with asset engines outside of 'import' context. */ # # typedef struct AssetUUID { int uuid_asset[4]; int uuid_variant[4]; int uuid_revision[4]; } AssetUUID; # # typedef struct AssetUUIDList { AssetUUID *uuids; int nbr_uuids; char _pad[4]; } AssetUUIDList; /* Container for a revision, only relevant in asset context. */ # # typedef struct FileDirEntryRevision { struct FileDirEntryRevision *next, *prev; char *comment; void *_pad; int uuid[4]; uint64_t size; int64_t time; /* Temp caching of UI-generated strings... */ char size_str[16]; char datetime_str[16 + 8]; } FileDirEntryRevision; /* Container for a variant, only relevant in asset context. * In case there are no variants, a single one shall exist, with NULL name/description. */ # # typedef struct FileDirEntryVariant { struct FileDirEntryVariant *next, *prev; int uuid[4]; char *name; char *description; ListBase revisions; int nbr_revisions; int act_revision; } FileDirEntryVariant; /* Container for mere direntry, with additional asset-related data. */ # # typedef struct FileDirEntry { struct FileDirEntry *next, *prev; int uuid[4]; char *name; char *description; /* Either point to active variant/revision if available, or own entry * (in mere filebrowser case). */ FileDirEntryRevision *entry; /** #eFileSel_File_Types. */ int typeflag; /** ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */ int blentype; + /* Path to item that is relative to current folder root. */ char *relpath; + /* Optional absolute path when not relative to current folder root. */ + char *redirection_path; /** TODO: make this a real ID pointer? */ void *poin; struct ImBuf *image; /* Tags are for info only, most of filtering is done in asset engine. */ char **tags; int nbr_tags; short status; short flags; /* eFileAttributes defined in BLI_fileops.h */ int attributes; ListBase variants; int nbr_variants; int act_variant; } FileDirEntry; /** * Array of direntries. * * This struct is used in various, different contexts. * * In Filebrowser UI, it stores the total number of available entries, the number of visible * (filtered) entries, and a subset of those in 'entries' ListBase, from idx_start (included) * to idx_end (excluded). * * In AssetEngine context (i.e. outside of 'browsing' context), entries contain all needed data, * there is no filtering, so nbr_entries_filtered, entry_idx_start and entry_idx_end * should all be set to -1. */ # # typedef struct FileDirEntryArr { ListBase entries; int nbr_entries; int nbr_entries_filtered; int entry_idx_start, entry_idx_end; /** FILE_MAX. */ char root[1024]; } FileDirEntryArr; #if 0 /* UNUSED */ /* FileDirEntry.status */ enum { ASSET_STATUS_LOCAL = 1 << 0, /* If active uuid is available locally/immediately. */ ASSET_STATUS_LATEST = 1 << 1, /* If active uuid is latest available version. */ }; #endif /* FileDirEntry.flags */ enum { FILE_ENTRY_INVALID_PREVIEW = 1 << 0, /* The preview for this entry could not be generated. */ }; /** \} */ /* -------------------------------------------------------------------- */ /** \name Image/UV Editor * \{ */ /* Image/UV Editor */ typedef struct SpaceImage { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ ListBase regionbase; char spacetype; char link_flag; char _pad0[6]; /* End 'SpaceLink' header. */ struct Image *image; struct ImageUser iuser; /** Histogram waveform and vectorscope. */ struct Scopes scopes; /** Sample line histogram. */ struct Histogram sample_line_hist; /** Grease pencil data. */ struct bGPdata *gpd; /** UV editor 2d cursor. */ float cursor[2]; /** User defined offset, image is centered. */ float xof, yof; /** User defined zoom level. */ float zoom; /** Storage for offset while render drawing. */ float centx, centy; /** View/paint/mask. */ char mode; /* Storage for sub-space types. */ char mode_prev; char pin; char _pad1;