From f69e9681fa32f6f9baafd2aa4a70427864ce6bb5 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 19 Aug 2015 22:41:39 +0200 Subject: Final 'FileBrowser First Stage' merge. It basically rewrites most of filelist.c, with some more limited changes in other areas of filebrowser. From user perspective, it: * Removes some info in 'long' drawing mode (owner, permissions) - OS-specific data that do not really matter in Blender! * Makes short/long display 'fixed' size (among four choices, like thumbnails mode). * Allows to list several layers of dirtree at once, in a flat way (inside .blend files and/or real directories). * Consequently, adds datablocks types filtering. * Uses way less RAM when listing big directories, especially in thumbnail mode (we are talking of several hundred of MiB spared). * Generates thumbnails way faster. From code perspective, it: * Is ready for asset engine needs (on data structure level in filebrowser's listing). * Simplifies and makes 'generic' file listing much lighter. * Separates file listing in three different aspects: ** 'generic' filelisting (in BLI), which becomes a shallow wrapper around stat struct. ** 'filebrowser drawing' filelisting, which only contains current visible subset of the whole list (sliding window), with extra drawing data (strings for size, date/time, preview, etc.). ** 'asset-ready' filelisting, which is used for operations common to 'basic' filehandling and future asset-related one. * Uses uuid's to handle file selection/state in the browser, instead of using flags in filelisting items. * Uses much lighter BLI_task handling for previews, instead of heavy 'job' system (using the new 'notifier' timer to handle UI refresh, in similar way to jobs). * Moves .blend datablocks preview handling to IMB_thumbnail (necessary to avoid storing all datablock previews at once, and gives better consistency and performances too). Revision: https://developer.blender.org/D1316 Thanks to Campbell & Sergey for the reviews. :) --- source/blender/editors/space_file/filelist.c | 2528 +++++++++++++++++++------- 1 file changed, 1883 insertions(+), 645 deletions(-) (limited to 'source/blender/editors/space_file/filelist.c') diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 21a072d0adf..02b31b494c2 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -35,6 +35,8 @@ #include #include #include +#include +#include #ifndef WIN32 # include @@ -45,9 +47,16 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_fileops.h" #include "BLI_fileops_types.h" #include "BLI_fnmatch.h" +#include "BLI_ghash.h" +#include "BLI_hash_md5.h" #include "BLI_linklist.h" +#include "BLI_math.h" +#include "BLI_stack.h" +#include "BLI_task.h" +#include "BLI_threads.h" #include "BLI_utildefines.h" #ifdef WIN32 @@ -60,13 +69,13 @@ #include "BKE_icons.h" #include "BKE_idcode.h" #include "BKE_main.h" -#include "BKE_report.h" #include "BLO_readfile.h" #include "DNA_space_types.h" #include "ED_datafiles.h" #include "ED_fileselect.h" +#include "ED_screen.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -78,6 +87,9 @@ #include "WM_types.h" #include "UI_resources.h" +#include "UI_interface_icons.h" + +#include "atomic_ops.h" #include "filelist.h" @@ -194,49 +206,128 @@ ListBase *folderlist_duplicate(ListBase *folderlist) /* ------------------FILELIST------------------------ */ -struct FileList; +typedef struct FileListInternEntry { + struct FileListInternEntry *next, *prev; + + char uuid[16]; /* ASSET_UUID_LENGTH */ + + int typeflag; /* eFileSel_File_Types */ + int blentype; /* ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */ + + char *relpath; + char *name; /* not striclty needed, but used during sorting, avoids to have to recompute it there... */ + + BLI_stat_t st; +} FileListInternEntry; + +typedef struct FileListIntern { + ListBase entries; /* FileListInternEntry items. */ + 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; + + /* Block cache: all entries between start and end index. used for part of the list on diplay. */ + 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_todo; + ThreadQueue *previews_done; + double previews_timestamp; + int previews_pending; +} FileListEntryCache; + +/* FileListCache.flags */ +enum { + FLC_IS_INIT = 1 << 0, + FLC_PREVIEWS_ACTIVE = 1 << 1, +}; -typedef struct FileImage { - struct FileImage *next, *prev; +typedef struct FileListEntryPreview { char path[FILE_MAX]; unsigned int flags; int index; - short done; ImBuf *img; -} FileImage; +} FileListEntryPreview; typedef struct FileListFilter { - bool hide_dot; - bool hide_parent; unsigned int filter; + unsigned int filter_id; char filter_glob[64]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ + short flags; } FileListFilter; +/* FileListFilter.flags */ +enum { + FLF_HIDE_DOT = 1 << 0, + FLF_HIDE_PARENT = 1 << 1, + FLF_HIDE_LIB_DIR = 1 << 2, +}; + typedef struct FileList { - struct direntry *filelist; - int numfiles; - char dir[FILE_MAX]; + FileDirEntryArr filelist; + short prv_w; short prv_h; - bool changed; + short flags; short sort; - bool need_sorting; FileListFilter filter_data; - int *fidx; /* Also used to detect when we need to filter! */ - int numfiltered; - bool need_thumbnails; + 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; struct BlendHandle *libfiledata; - void (*readf)(struct FileList *); - bool (*filterf)(struct direntry *, const char *, FileListFilter *); + /* Set given path as root directory, may change given string in place to a valid value. */ + void (*checkdirf)(struct FileList *, char *); + + /* Fill filelist (to be called by read job). */ + void (*read_jobf)(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); + + /* Filter an entry of current filelist. */ + bool (*filterf)(struct FileListInternEntry *, const char *, FileListFilter *); } FileList; +/* FileList.flags */ +enum { + FL_FORCE_RESET = 1 << 0, + FL_IS_READY = 1 << 1, + FL_IS_PENDING = 1 << 2, + FL_NEED_SORTING = 1 << 3, + FL_NEED_FILTERING = 1 << 4, +}; + #define SPECIAL_IMG_SIZE 48 #define SPECIAL_IMG_ROWS 4 #define SPECIAL_IMG_COLS 4 @@ -260,153 +351,187 @@ enum { static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX]; -static void filelist_from_main(struct FileList *filelist); -static void filelist_from_library(struct FileList *filelist); +static void filelist_readjob_main(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); +static void filelist_readjob_lib(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); +static void filelist_readjob_dir(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); -static void filelist_read_main(struct FileList *filelist); -static void filelist_read_library(struct FileList *filelist); -static void filelist_read_dir(struct FileList *filelist); +/* helper, could probably go in BKE actually? */ +static int groupname_to_code(const char *group); +static unsigned int groupname_to_filter_id(const char *group); static void filelist_filter_clear(FileList *filelist); +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size); /* ********** Sort helpers ********** */ -static bool compare_is_directory(const struct direntry *entry) -{ - /* for library browse .blend files may be treated as directories, but - * for sorting purposes they should be considered regular files */ - if (S_ISDIR(entry->type)) - return !(entry->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)); - - return false; -} - -static int compare_direntry_generic(const struct direntry *entry1, const struct direntry *entry2) +static int compare_direntry_generic(const FileListInternEntry *entry1, const FileListInternEntry *entry2) { /* type is equal to stat.st_mode */ - if (compare_is_directory(entry1)) { - if (compare_is_directory(entry2) == 0) { - return -1; + if (entry1->typeflag & FILE_TYPE_DIR) { + if (entry2->typeflag & FILE_TYPE_DIR) { + /* If both entries are tagged as dirs, we make a 'sub filter' that shows first the real dirs, + * then libs (.blend files), then categories in libs. */ + if (entry1->typeflag & FILE_TYPE_BLENDERLIB) { + if (!(entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + return 1; + } + } + else if (entry2->typeflag & FILE_TYPE_BLENDERLIB) { + return -1; + } + else if (entry1->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + return 1; + } + } + else if (entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + return -1; + } } - } - else if (compare_is_directory(entry2)) { - return 1; - } - - if (S_ISREG(entry1->type)) { - if (!S_ISREG(entry2->type)) { + else { return -1; } } - else if (S_ISREG(entry2->type)) { - return 1; + else if (entry2->typeflag & FILE_TYPE_DIR) { + return 1; } - if ((entry1->type & S_IFMT) < (entry2->type & S_IFMT)) return -1; - if ((entry1->type & S_IFMT) > (entry2->type & S_IFMT)) return 1; - + /* make sure "." and ".." are always first */ - if (FILENAME_IS_CURRENT(entry1->relname)) return -1; - if (FILENAME_IS_CURRENT(entry2->relname)) return 1; - if (FILENAME_IS_PARENT(entry1->relname)) return -1; - if (FILENAME_IS_PARENT(entry2->relname)) return 1; + if (FILENAME_IS_CURRENT(entry1->relpath)) return -1; + if (FILENAME_IS_CURRENT(entry2->relpath)) return 1; + if (FILENAME_IS_PARENT(entry1->relpath)) return -1; + if (FILENAME_IS_PARENT(entry2->relpath)) return 1; return 0; } -static int compare_name(const void *a1, const void *a2) +static int compare_name(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - return (BLI_natstrcmp(entry1->relname, entry2->relname)); + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } -static int compare_date(const void *a1, const void *a2) +static int compare_date(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + int64_t time1, time2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - if (entry1->s.st_mtime < entry2->s.st_mtime) return 1; - if (entry1->s.st_mtime > entry2->s.st_mtime) return -1; + time1 = (int64_t)entry1->st.st_mtime; + time2 = (int64_t)entry2->st.st_mtime; + if (time1 < time2) return 1; + if (time1 > time2) return -1; + + name1 = entry1->name; + name2 = entry2->name; - return BLI_natstrcmp(entry1->relname, entry2->relname); + return BLI_natstrcmp(name1, name2); } -static int compare_size(const void *a1, const void *a2) +static int compare_size(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + uint64_t size1, size2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - if (entry1->s.st_size < entry2->s.st_size) return 1; - if (entry1->s.st_size > entry2->s.st_size) return -1; + size1 = entry1->st.st_size; + size2 = entry2->st.st_size; + if (size1 < size2) return 1; + if (size1 > size2) return -1; - return BLI_natstrcmp(entry1->relname, entry2->relname); + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } -static int compare_extension(const void *a1, const void *a2) +static int compare_extension(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; - const char *sufix1, *sufix2; - const char *nil = ""; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - if (!(sufix1 = strstr(entry1->relname, ".blend.gz"))) - sufix1 = strrchr(entry1->relname, '.'); - if (!(sufix2 = strstr(entry2->relname, ".blend.gz"))) - sufix2 = strrchr(entry2->relname, '.'); - if (!sufix1) sufix1 = nil; - if (!sufix2) sufix2 = nil; + if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && !(entry2->typeflag & FILE_TYPE_BLENDERLIB)) return -1; + if (!(entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) return 1; + if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + if ((entry1->typeflag & FILE_TYPE_DIR) && !(entry2->typeflag & FILE_TYPE_DIR)) return 1; + if (!(entry1->typeflag & FILE_TYPE_DIR) && (entry2->typeflag & FILE_TYPE_DIR)) return -1; + if (entry1->blentype < entry2->blentype) return -1; + if (entry1->blentype > entry2->blentype) return 1; + } + else { + const char *sufix1, *sufix2; - return BLI_strcasecmp(sufix1, sufix2); -} + if (!(sufix1 = strstr(entry1->relpath, ".blend.gz"))) + sufix1 = strrchr(entry1->relpath, '.'); + if (!(sufix2 = strstr(entry2->relpath, ".blend.gz"))) + sufix2 = strrchr(entry2->relpath, '.'); + if (!sufix1) sufix1 = ""; + if (!sufix2) sufix2 = ""; -bool filelist_need_sorting(struct FileList *filelist) -{ - return filelist->need_sorting && (filelist->sort != FILE_SORT_NONE); + if ((ret = BLI_strcasecmp(sufix1, sufix2))) { + return ret; + } + } + + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } void filelist_sort(struct FileList *filelist) { - if (filelist_need_sorting(filelist)) { - filelist->need_sorting = false; - + if ((filelist->flags & FL_NEED_SORTING) && (filelist->sort != FILE_SORT_NONE)) { switch (filelist->sort) { case FILE_SORT_ALPHA: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_name); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_name, NULL); break; case FILE_SORT_TIME: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_date); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_date, NULL); break; case FILE_SORT_SIZE: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_size); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_size, NULL); break; case FILE_SORT_EXTENSION: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_extension); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_extension, NULL); break; case FILE_SORT_NONE: /* Should never reach this point! */ default: BLI_assert(0); - return; } filelist_filter_clear(filelist); + filelist->flags &= ~FL_NEED_SORTING; } } @@ -414,7 +539,7 @@ void filelist_setsorting(struct FileList *filelist, const short sort) { if (filelist->sort != sort) { filelist->sort = sort; - filelist->need_sorting = true; + filelist->flags |= FL_NEED_SORTING; } } @@ -422,9 +547,10 @@ void filelist_setsorting(struct FileList *filelist, const short sort) static bool is_hidden_file(const char *filename, FileListFilter *filter) { + char *sep = (char *)BLI_last_slash(filename); bool is_hidden = false; - if (filter->hide_dot) { + if (filter->flags & FLF_HIDE_DOT) { if (filename[0] == '.' && filename[1] != '.' && filename[1] != '\0') { is_hidden = true; /* ignore .file */ } @@ -435,7 +561,7 @@ static bool is_hidden_file(const char *filename, FileListFilter *filter) } } } - if (!is_hidden && filter->hide_parent) { + if (!is_hidden && (filter->flags & FLF_HIDE_PARENT)) { if (filename[0] == '.' && filename[1] == '.' && filename[2] == '\0') { is_hidden = true; /* ignore .. */ } @@ -443,22 +569,49 @@ static bool is_hidden_file(const char *filename, FileListFilter *filter) if (!is_hidden && ((filename[0] == '.') && (filename[1] == '\0'))) { is_hidden = true; /* ignore . */ } + /* filename might actually be a piece of path, in which case we have to check all its parts. */ + if (!is_hidden && sep) { + char tmp_filename[FILE_MAX_LIBEXTRA]; + + BLI_strncpy(tmp_filename, filename, sizeof(tmp_filename)); + sep = tmp_filename + (sep - filename); + while (sep) { + BLI_assert(sep[1] != '\0'); + if (is_hidden_file(sep + 1, filter)) { + is_hidden = true; + break; + } + *sep = '\0'; + sep = (char *)BLI_last_slash(tmp_filename); + } + } return is_hidden; } -static bool is_filtered_file(struct direntry *file, const char *UNUSED(root), FileListFilter *filter) +static bool is_filtered_file(FileListInternEntry *file, const char *UNUSED(root), FileListFilter *filter) { - bool is_filtered = !is_hidden_file(file->relname, filter); + bool is_filtered = !is_hidden_file(file->relpath, filter); - if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relname)) { - if ((file->type & S_IFDIR) && !(filter->filter & FILE_TYPE_FOLDER)) { - is_filtered = false; + if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relpath)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + is_filtered = false; + } + } + else { + if (!(filter->filter & FILE_TYPE_FOLDER)) { + is_filtered = false; + } + } } - if (!(file->type & S_IFDIR) && !(file->flags & filter->filter)) { - is_filtered = false; + else { + if (!(file->typeflag & filter->filter)) { + is_filtered = false; + } } if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relname, FNM_CASEFOLD) != 0) { + if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { is_filtered = false; } } @@ -467,16 +620,41 @@ static bool is_filtered_file(struct direntry *file, const char *UNUSED(root), Fi return is_filtered; } -static bool is_filtered_lib(struct direntry *file, const char *root, FileListFilter *filter) +static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) { - bool is_filtered = !is_hidden_file(file->relname, filter); - char dir[FILE_MAXDIR]; + bool is_filtered; + char path[FILE_MAX_LIBEXTRA], dir[FILE_MAXDIR], *group, *name; + + BLI_join_dirfile(path, sizeof(path), root, file->relpath); - if (BLO_library_path_explode(root, dir, NULL, NULL)) { - is_filtered = !is_hidden_file(file->relname, filter); - if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relname)) { + if (BLO_library_path_explode(path, dir, &group, &name)) { + is_filtered = !is_hidden_file(file->relpath, filter); + if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relpath)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + is_filtered = false; + } + } + else { + if (!(filter->filter & FILE_TYPE_FOLDER)) { + is_filtered = false; + } + } + } + if (is_filtered && group) { + if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { + is_filtered = false; + } + else { + unsigned int filter_id = groupname_to_filter_id(group); + if (!(filter_id & filter->filter_id)) { + is_filtered = false; + } + } + } if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relname, FNM_CASEFOLD) != 0) { + if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { is_filtered = false; } } @@ -489,70 +667,97 @@ static bool is_filtered_lib(struct direntry *file, const char *root, FileListFil return is_filtered; } -static bool is_filtered_main(struct direntry *file, const char *UNUSED(dir), FileListFilter *filter) +static bool is_filtered_main(FileListInternEntry *file, const char *UNUSED(dir), FileListFilter *filter) { - return !is_hidden_file(file->relname, filter); + return !is_hidden_file(file->relpath, filter); } static void filelist_filter_clear(FileList *filelist) { - MEM_SAFE_FREE(filelist->fidx); - filelist->numfiltered = 0; + filelist->flags |= FL_NEED_FILTERING; } void filelist_filter(FileList *filelist) { int num_filtered = 0; - int *fidx_tmp; - int i; + const int num_files = filelist->filelist.nbr_entries; + FileListInternEntry **filtered_tmp, *file; - if (!filelist->filelist) { + if (filelist->filelist.nbr_entries == 0) { return; } - if (filelist->fidx) { + if (!(filelist->flags & FL_NEED_FILTERING)) { /* Assume it has already been filtered, nothing else to do! */ return; } - fidx_tmp = MEM_mallocN(sizeof(*fidx_tmp) * (size_t)filelist->numfiles, __func__); + filelist->filter_data.flags &= ~FLF_HIDE_LIB_DIR; + if (filelist->max_recursion) { + /* Never show lib ID 'categories' directories when we are in 'flat' mode, unless + * root path is a blend file. */ + char dir[FILE_MAXDIR]; + if (!filelist_islibrary(filelist, dir, NULL)) { + filelist->filter_data.flags |= FLF_HIDE_LIB_DIR; + } + } - /* Filter remap & count how many files are left after filter in a single loop. */ - for (i = 0; i < filelist->numfiles; ++i) { - struct direntry *file = &filelist->filelist[i]; + filtered_tmp = MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__); - if (filelist->filterf(file, filelist->dir, &filelist->filter_data)) { - fidx_tmp[num_filtered++] = i; + /* Filter remap & count how many files are left after filter in a single loop. */ + for (file = filelist->filelist_intern.entries.first; file; file = file->next) { + if (filelist->filterf(file, filelist->filelist.root, &filelist->filter_data)) { + filtered_tmp[num_filtered++] = file; } } - /* Note: maybe we could even accept filelist->fidx to be filelist->numfiles -len allocated? */ - filelist->fidx = MEM_mallocN(sizeof(*filelist->fidx) * (size_t)num_filtered, __func__); - memcpy(filelist->fidx, fidx_tmp, sizeof(*filelist->fidx) * (size_t)num_filtered); - filelist->numfiltered = num_filtered; + if (filelist->filelist_intern.filtered) { + MEM_freeN(filelist->filelist_intern.filtered); + } + filelist->filelist_intern.filtered = MEM_mallocN(sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered, + __func__); + memcpy(filelist->filelist_intern.filtered, filtered_tmp, + sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered); + filelist->filelist.nbr_entries_filtered = num_filtered; +// printf("Filetered: %d over %d entries\n", num_filtered, filelist->filelist.nbr_entries); + + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + filelist->flags &= ~FL_NEED_FILTERING; - MEM_freeN(fidx_tmp); + MEM_freeN(filtered_tmp); } void filelist_setfilter_options(FileList *filelist, const bool hide_dot, const bool hide_parent, - const unsigned int filter, + const unsigned int filter, const unsigned int filter_id, const char *filter_glob, const char *filter_search) { - if ((filelist->filter_data.hide_dot != hide_dot) || - (filelist->filter_data.hide_parent != hide_parent) || - (filelist->filter_data.filter != filter) || - !STREQ(filelist->filter_data.filter_glob, filter_glob) || - (BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) - { - filelist->filter_data.hide_dot = hide_dot; - filelist->filter_data.hide_parent = hide_parent; + bool update = false; + if (((filelist->filter_data.flags & FLF_HIDE_DOT) != 0) != (hide_dot != 0)) { + filelist->filter_data.flags ^= FLF_HIDE_DOT; + update = true; + } + if (((filelist->filter_data.flags & FLF_HIDE_PARENT) != 0) != (hide_parent != 0)) { + filelist->filter_data.flags ^= FLF_HIDE_PARENT; + update = true; + } + if ((filelist->filter_data.filter != filter) || (filelist->filter_data.filter_id != filter_id)) { filelist->filter_data.filter = filter; + filelist->filter_data.filter_id = filter_id; + update = true; + } + if (!STREQ(filelist->filter_data.filter_glob, filter_glob)) { BLI_strncpy(filelist->filter_data.filter_glob, filter_glob, sizeof(filelist->filter_data.filter_glob)); + update = true; + } + if ((BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) { BLI_strncpy_ensure_pad(filelist->filter_data.filter_search, filter_search, '*', sizeof(filelist->filter_data.filter_search)); + update = true; + } - /* And now, free filtered data so that we now we have to filter again. */ + if (update) { + /* And now, free filtered data so that we know we have to filter again. */ filelist_filter_clear(filelist); } } @@ -607,218 +812,1122 @@ void filelist_imgsize(struct FileList *filelist, short w, short h) filelist->prv_h = h; } -ImBuf *filelist_getimage(struct FileList *filelist, const int index) +static FileDirEntry *filelist_geticon_get_file(struct FileList *filelist, const int index) { - ImBuf *ibuf = NULL; - int fidx = 0; - BLI_assert(G.background == false); - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; - } - fidx = filelist->fidx[index]; - ibuf = filelist->filelist[fidx].image; + return filelist_file(filelist, index); +} - return ibuf; +ImBuf *filelist_getimage(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return file->image; } -ImBuf *filelist_geticon(struct FileList *filelist, const int index) +static ImBuf *filelist_geticon_image_ex(const unsigned int typeflag, const char *relpath) { ImBuf *ibuf = NULL; - struct direntry *file = NULL; - int fidx = 0; - - BLI_assert(G.background == false); - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; - } - fidx = filelist->fidx[index]; - file = &filelist->filelist[fidx]; - if (file->type & S_IFDIR) { - if (FILENAME_IS_PARENT(filelist->filelist[fidx].relname)) { + if (typeflag & FILE_TYPE_DIR) { + if (FILENAME_IS_PARENT(relpath)) { ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT]; } - else if (FILENAME_IS_CURRENT(filelist->filelist[fidx].relname)) { + else if (FILENAME_IS_CURRENT(relpath)) { ibuf = gSpecialFileImages[SPECIAL_IMG_REFRESH]; } else { ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER]; } } - else { - ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; - } - - if (file->flags & FILE_TYPE_BLENDER) { + else if (typeflag & FILE_TYPE_BLENDER) { ibuf = gSpecialFileImages[SPECIAL_IMG_BLENDFILE]; } - else if (file->flags & FILE_TYPE_MOVIE) { + else if (typeflag & FILE_TYPE_BLENDERLIB) { + ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + } + else if (typeflag & (FILE_TYPE_MOVIE)) { ibuf = gSpecialFileImages[SPECIAL_IMG_MOVIEFILE]; } - else if (file->flags & FILE_TYPE_SOUND) { + else if (typeflag & FILE_TYPE_SOUND) { ibuf = gSpecialFileImages[SPECIAL_IMG_SOUNDFILE]; } - else if (file->flags & FILE_TYPE_PYSCRIPT) { + else if (typeflag & FILE_TYPE_PYSCRIPT) { ibuf = gSpecialFileImages[SPECIAL_IMG_PYTHONFILE]; } - else if (file->flags & FILE_TYPE_FTFONT) { + else if (typeflag & FILE_TYPE_FTFONT) { ibuf = gSpecialFileImages[SPECIAL_IMG_FONTFILE]; } - else if (file->flags & FILE_TYPE_TEXT) { + else if (typeflag & FILE_TYPE_TEXT) { ibuf = gSpecialFileImages[SPECIAL_IMG_TEXTFILE]; } - else if (file->flags & FILE_TYPE_IMAGE) { + else if (typeflag & FILE_TYPE_IMAGE) { ibuf = gSpecialFileImages[SPECIAL_IMG_LOADING]; } - else if (file->flags & FILE_TYPE_BLENDER_BACKUP) { + else if (typeflag & FILE_TYPE_BLENDER_BACKUP) { ibuf = gSpecialFileImages[SPECIAL_IMG_BACKUP]; } + else { + ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + } return ibuf; } -/* ********** Main ********** */ +ImBuf *filelist_geticon_image(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); -FileList *filelist_new(short type) + return filelist_geticon_image_ex(file->typeflag, file->relpath); +} + +static int filelist_geticon_ex( + const int typeflag, const int blentype, const char *relpath, const bool is_main, const bool ignore_libdir) { - FileList *p = MEM_callocN(sizeof(*p), __func__); + if ((typeflag & FILE_TYPE_DIR) && !(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER)))) { + if (FILENAME_IS_PARENT(relpath)) { + return is_main ? ICON_FILE_PARENT : ICON_NONE; + } + else if (typeflag & FILE_TYPE_APPLICATIONBUNDLE) { + return ICON_UGLYPACKAGE; + } + else if (typeflag & FILE_TYPE_BLENDER) { + return ICON_FILE_BLEND; + } + else if (is_main) { + /* Do not return icon for folders if icons are not 'main' draw type (e.g. when used over previews). */ + return ICON_FILE_FOLDER; + } + } - switch (type) { - case FILE_MAIN: - p->readf = filelist_read_main; - p->filterf = is_filtered_main; - break; - case FILE_LOADLIB: - p->readf = filelist_read_library; - p->filterf = is_filtered_lib; - break; - default: - p->readf = filelist_read_dir; - p->filterf = is_filtered_file; - break; + if (typeflag & FILE_TYPE_BLENDER) + return ICON_FILE_BLEND; + else if (typeflag & FILE_TYPE_BLENDER_BACKUP) + return ICON_FILE_BACKUP; + else if (typeflag & FILE_TYPE_IMAGE) + return ICON_FILE_IMAGE; + else if (typeflag & FILE_TYPE_MOVIE) + return ICON_FILE_MOVIE; + else if (typeflag & FILE_TYPE_PYSCRIPT) + return ICON_FILE_SCRIPT; + else if (typeflag & FILE_TYPE_SOUND) + return ICON_FILE_SOUND; + else if (typeflag & FILE_TYPE_FTFONT) + return ICON_FILE_FONT; + else if (typeflag & FILE_TYPE_BTX) + return ICON_FILE_BLANK; + else if (typeflag & FILE_TYPE_COLLADA) + return ICON_FILE_BLANK; + else if (typeflag & FILE_TYPE_TEXT) + return ICON_FILE_TEXT; + else if (typeflag & FILE_TYPE_BLENDERLIB) { + const int ret = UI_idcode_icon_get(blentype); + if (ret != ICON_NONE) { + return ret; + } } - return p; + return is_main ? ICON_FILE_BLANK : ICON_NONE; } -void filelist_free(struct FileList *filelist) +int filelist_geticon(struct FileList *filelist, const int index, const bool is_main) { - if (!filelist) { - printf("Attempting to delete empty filelist.\n"); - return; - } - - MEM_SAFE_FREE(filelist->fidx); - filelist->numfiltered = 0; - memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); + FileDirEntry *file = filelist_geticon_get_file(filelist, index); - filelist->need_sorting = false; - filelist->sort = FILE_SORT_NONE; + return filelist_geticon_ex(file->typeflag, file->blentype, file->relpath, is_main, false); +} + +/* ********** Main ********** */ - BLI_filelist_free(filelist->filelist, filelist->numfiles, NULL); - filelist->numfiles = 0; - filelist->filelist = NULL; +static void filelist_checkdir_dir(struct FileList *UNUSED(filelist), char *r_dir) +{ + BLI_make_exist(r_dir); } -void filelist_freelib(struct FileList *filelist) +static void filelist_checkdir_lib(struct FileList *UNUSED(filelist), char *r_dir) { - if (filelist->libfiledata) - BLO_blendhandle_close(filelist->libfiledata); - filelist->libfiledata = NULL; + char dir[FILE_MAXDIR]; + if (!BLO_library_path_explode(r_dir, dir, NULL, NULL)) { + /* if not a valid library, we need it to be a valid directory! */ + BLI_make_exist(r_dir); + } } -BlendHandle *filelist_lib(struct FileList *filelist) +static void filelist_checkdir_main(struct FileList *filelist, char *r_dir) { - return filelist->libfiledata; + /* TODO */ + filelist_checkdir_lib(filelist, r_dir); } -int filelist_numfiles(struct FileList *filelist) +static void filelist_entry_clear(FileDirEntry *entry) { - return filelist->numfiltered; + if (entry->name) { + MEM_freeN(entry->name); + } + if (entry->description) { + MEM_freeN(entry->description); + } + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + 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); + } } -const char *filelist_dir(struct FileList *filelist) +static void filelist_entry_free(FileDirEntry *entry) { - return filelist->dir; + filelist_entry_clear(entry); + MEM_freeN(entry); } -void filelist_setdir(struct FileList *filelist, const char *dir) +static void filelist_direntryarr_free(FileDirEntryArr *array) { - BLI_strncpy(filelist->dir, dir, sizeof(filelist->dir)); + 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); + array->nbr_entries = 0; + array->nbr_entries_filtered = -1; + array->entry_idx_start = -1; + array->entry_idx_end = -1; } -short filelist_changed(struct FileList *filelist) +static void filelist_intern_entry_free(FileListInternEntry *entry) { - return filelist->changed; + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + if (entry->name) { + MEM_freeN(entry->name); + } + MEM_freeN(entry); } -struct direntry *filelist_file(struct FileList *filelist, int index) +static void filelist_intern_free(FileListIntern *filelist_intern) { - int fidx = 0; - - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; + FileListInternEntry *entry, *entry_next; + + for (entry = filelist_intern->entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_intern_entry_free(entry); } - fidx = filelist->fidx[index]; + BLI_listbase_clear(&filelist_intern->entries); - return &filelist->filelist[fidx]; + MEM_SAFE_FREE(filelist_intern->filtered); } -int filelist_find(struct FileList *filelist, const char *filename) +static void filelist_cache_previewf(TaskPool *pool, void *taskdata, int UNUSED(threadid)) { - int fidx = -1; - - if (!filelist->fidx) - return fidx; + FileListEntryCache *cache = taskdata; + FileListEntryPreview *preview; - for (fidx = 0; fidx < filelist->numfiltered; fidx++) { - int index = filelist->fidx[fidx]; +// printf("%s: Start (%d)...\n", __func__, threadid); - if (STREQ(filelist->filelist[index].relname, filename)) { - return fidx; + /* Note we wait on queue here. */ + while (!BLI_task_pool_canceled(pool) && (preview = BLI_thread_queue_pop(cache->previews_todo))) { + ThumbSource source = 0; + +// 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); + + BLI_thread_queue_push(cache->previews_done, preview); } - return -1; +// printf("%s: End (%d)...\n", __func__, threadid); } -/* would recognize .blend as well */ -static bool file_is_blend_backup(const char *str) +static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) { - const size_t a = strlen(str); - size_t b = 7; - bool retval = 0; + if (!cache->previews_pool) { + TaskScheduler *scheduler = BLI_task_scheduler_get(); + TaskPool *pool; + int num_tasks = max_ii(2, BLI_system_thread_count() / 2); - if (a == 0 || b >= a) { - /* pass */ - } - else { - const char *loc; - - if (a > b + 1) - b++; - - /* allow .blend1 .blend2 .blend32 */ - loc = BLI_strcasestr(str + a - b, ".blend"); - - if (loc) - retval = 1; + pool = cache->previews_pool = BLI_task_pool_create(scheduler, NULL); + cache->previews_todo = BLI_thread_queue_init(); + cache->previews_done = BLI_thread_queue_init(); + + while (num_tasks--) { + BLI_task_pool_push(pool, filelist_cache_previewf, cache, false, TASK_PRIORITY_HIGH); + } + IMB_thumb_locks_acquire(); } - - return (retval); + cache->previews_timestamp = 0.0; } -static int path_extension_type(const char *path) +static void filelist_cache_previews_clear(FileListEntryCache *cache) { - if (BLO_has_bfile_extension(path)) { - return FILE_TYPE_BLENDER; + FileListEntryPreview *preview; + + if (cache->previews_pool) { + while ((preview = BLI_thread_queue_pop_timeout(cache->previews_todo, 0))) { +// printf("%s: TODO %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + MEM_freeN(preview); + cache->previews_pending--; + } + 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); + if (preview->img) { + IMB_freeImBuf(preview->img); + } + MEM_freeN(preview); + cache->previews_pending--; + } } - else if (file_is_blend_backup(path)) { - return FILE_TYPE_BLENDER_BACKUP; +// printf("%s: remaining pending previews: %d\n", __func__, cache->previews_pending); +} + +static void filelist_cache_previews_free(FileListEntryCache *cache, const bool set_inactive) +{ + if (cache->previews_pool) { + BLI_thread_queue_nowait(cache->previews_todo); + BLI_thread_queue_nowait(cache->previews_done); + BLI_task_pool_cancel(cache->previews_pool); + + filelist_cache_previews_clear(cache); + + BLI_thread_queue_free(cache->previews_done); + BLI_thread_queue_free(cache->previews_todo); + BLI_task_pool_free(cache->previews_pool); + cache->previews_pool = NULL; + cache->previews_todo = NULL; + cache->previews_done = NULL; + + IMB_thumb_locks_release(); } - else if (BLI_testextensie(path, ".app")) { - return FILE_TYPE_APPLICATIONBUNDLE; + if (set_inactive) { + cache->flags &= ~FLC_PREVIEWS_ACTIVE; + } + BLI_assert(cache->previews_pending == 0); + cache->previews_timestamp = 0.0; +} + +static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry, const int index) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE); + + if (!entry->image && + !(entry->flags & FILE_ENTRY_INVALID_PREVIEW) && + (entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) + { + FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); + BLI_join_dirfile(preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath); + preview->index = index; + preview->flags = entry->typeflag; + preview->img = NULL; +// printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + + filelist_cache_preview_ensure_running(cache); + BLI_thread_queue_push(cache->previews_todo, preview); + cache->previews_pending++; + } +} + +static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) +{ + cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0; + cache->block_entries = MEM_mallocN(sizeof(*cache->block_entries) * cache_size, __func__); + + cache->misc_entries = BLI_ghash_ptr_new_ex(__func__, cache_size); + cache->misc_entries_indices = MEM_mallocN(sizeof(*cache->misc_entries_indices) * cache_size, __func__); + copy_vn_i(cache->misc_entries_indices, cache_size, -1); + cache->misc_cursor = 0; + + /* XXX This assumes uint is 32 bits and uuid is 128 bits (char[16]), be careful! */ + cache->uuids = BLI_ghash_new_ex( + BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__, cache_size * 2); + + cache->size = cache_size; + cache->flags = FLC_IS_INIT; +} + +static void filelist_cache_free(FileListEntryCache *cache) +{ + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_free(cache, true); + + /* Note we nearly have nothing to do here, entries are just 'borrowed', not owned by cache... */ + MEM_freeN(cache->block_entries); + + BLI_ghash_free(cache->misc_entries, NULL, NULL); + MEM_freeN(cache->misc_entries_indices); + + BLI_ghash_free(cache->uuids, NULL, NULL); +} + +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size) +{ + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_clear(cache); + + /* Note we nearly have nothing to do here, entries are just 'borrowed', not owned by cache... */ + cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0; + if (new_size != cache->size) { + cache->block_entries = MEM_reallocN(cache->block_entries, sizeof(*cache->block_entries) * new_size); + } + + BLI_ghash_clear_ex(cache->misc_entries, NULL, NULL, new_size); + if (new_size != cache->size) { + cache->misc_entries_indices = MEM_reallocN(cache->misc_entries_indices, + sizeof(*cache->misc_entries_indices) * new_size); + } + copy_vn_i(cache->misc_entries_indices, new_size, -1); + + BLI_ghash_clear_ex(cache->uuids, NULL, NULL, new_size * 2); + + cache->size = new_size; +} + +FileList *filelist_new(short type) +{ + FileList *p = MEM_callocN(sizeof(*p), __func__); + + filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT); + + p->selection_state = BLI_ghash_new(BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__); + + switch (type) { + case FILE_MAIN: + p->checkdirf = filelist_checkdir_main; + p->read_jobf = filelist_readjob_main; + p->filterf = is_filtered_main; + break; + case FILE_LOADLIB: + p->checkdirf = filelist_checkdir_lib; + p->read_jobf = filelist_readjob_lib; + p->filterf = is_filtered_lib; + break; + default: + p->checkdirf = filelist_checkdir_dir; + p->read_jobf = filelist_readjob_dir; + p->filterf = is_filtered_file; + break; + } + return p; +} + +void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection) +{ + if (!filelist) { + return; + } + + filelist_filter_clear(filelist); + + if (do_cache) { + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + } + + filelist_intern_free(&filelist->filelist_intern); + + filelist_direntryarr_free(&filelist->filelist); + + if (do_selection && filelist->selection_state) { + BLI_ghash_clear(filelist->selection_state, MEM_freeN, NULL); + } +} + +void filelist_clear(struct FileList *filelist) +{ + filelist_clear_ex(filelist, true, true); +} + +void filelist_free(struct FileList *filelist) +{ + if (!filelist) { + printf("Attempting to delete empty filelist.\n"); + return; + } + + filelist_clear_ex(filelist, false, false); /* No need to clear cache & selection_state, we free them anyway. */ + filelist_cache_free(&filelist->filelist_cache); + + if (filelist->selection_state) { + BLI_ghash_free(filelist->selection_state, MEM_freeN, NULL); + filelist->selection_state = NULL; + } + + memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); + + filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); + filelist->sort = FILE_SORT_NONE; +} + +void filelist_freelib(struct FileList *filelist) +{ + if (filelist->libfiledata) + BLO_blendhandle_close(filelist->libfiledata); + filelist->libfiledata = NULL; +} + +BlendHandle *filelist_lib(struct FileList *filelist) +{ + return filelist->libfiledata; +} + +static const char *fileentry_uiname(const char *root, const char *relpath, const int typeflag, char *buff) +{ + char *name = NULL; + + if (typeflag & FILE_TYPE_BLENDERLIB) { + char abspath[FILE_MAX_LIBEXTRA]; + char *group; + + BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); + BLO_library_path_explode(abspath, buff, &group, &name); + if (!name) { + name = group; + } + } + /* Depending on platforms, 'my_file.blend/..' might be viewed as dir or not... */ + if (!name) { + if (typeflag & FILE_TYPE_DIR) { + 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; +} + +/** + * 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) +{ +#ifndef NDEBUG + size_t len = strlen(r_dir); + BLI_assert((len < FILE_MAX_LIBEXTRA) && ELEM(r_dir[len - 1], SEP, ALTSEP)); +#endif + + BLI_cleanup_dir(G.main->name, r_dir); + BLI_add_slash(r_dir); + filelist->checkdirf(filelist, r_dir); + + 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]; + 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; + + BLI_addtail(&filelist->filelist.entries, ret); + return ret; +} + +static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry) +{ + BLI_remlink(&filelist->filelist.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, SET_INT_IN_POINTER(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, SET_INT_IN_POINTER(old_index), NULL))) { + BLI_ghash_remove(cache->uuids, old->uuid, NULL, NULL); + filelist_file_release_entry(filelist, old); + } + BLI_ghash_insert(cache->misc_entries, SET_INT_IN_POINTER(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; + } + } + + { + int fidx; + + for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { + FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; + if (memcmp(entry->uuid, uuid, sizeof(entry->uuid)) == 0) { + return filelist_file(filelist, fidx); + } + } + } + + return NULL; +} + +void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size) +{ + /* Always keep it power of 2, in [256, 8192] range for now, cache being app. twice bigger than requested window. */ + size_t size = 256; + window_size *= 2; + + while (size < window_size && size < 8192) { + size *= 2; + } + + if (size != filelist->filelist_cache.size) { + filelist_cache_clear(&filelist->filelist_cache, size); + } +} + +/* Helpers, low-level, they assume cursor + size <= cache_size */ +static bool filelist_file_cache_block_create(FileList *filelist, const int start_index, const int size, int cursor) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i, idx; + + for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) { + FileDirEntry *entry = filelist_file_create_entry(filelist, idx); + cache->block_entries[cursor] = entry; + BLI_ghash_insert(cache->uuids, entry->uuid, entry); + } + return true; + } + + return false; +} + +static void filelist_file_cache_block_release(struct FileList *filelist, const int size, int cursor) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i; + + for (i = 0; i < size; i++, cursor++) { + FileDirEntry *entry = cache->block_entries[cursor]; +// printf("%s: release cacheidx %d (%%p %%s)\n", __func__, cursor/*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/); + BLI_ghash_remove(cache->uuids, entry->uuid, NULL, NULL); + filelist_file_release_entry(filelist, entry); +#ifndef NDEBUG + cache->block_entries[cursor] = NULL; +#endif + } + } +} + +/* Load in cache all entries "around" given index (as much as block cache may hold). */ +bool filelist_file_cache_block(struct FileList *filelist, const int index) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + const size_t cache_size = cache->size; + + const int nbr_entries = filelist->filelist.nbr_entries_filtered; + int start_index = max_ii(0, index - (cache_size / 2)); + int end_index = min_ii(nbr_entries, index + (cache_size / 2)); + int i; + + if ((index < 0) || (index >= nbr_entries)) { +// printf("Wrong index %d ([%d:%d])", index, 0, nbr_entries); + return false; + } + + /* Maximize cached range! */ + if ((end_index - start_index) < cache_size) { + if (start_index == 0) { + end_index = min_ii(nbr_entries, start_index + cache_size); + } + else if (end_index == nbr_entries) { + start_index = max_ii(0, end_index - cache_size); + } + } + + BLI_assert((end_index - start_index) <= cache_size) ; + +// printf("%s: [%d:%d] around index %d (current cache: [%d:%d])\n", __func__, +// start_index, end_index, index, cache->block_start_index, cache->block_end_index); + + /* If we have something to (re)cache... */ + if ((start_index != cache->block_start_index) || (end_index != cache->block_end_index)) { + if ((start_index >= cache->block_end_index) || (end_index <= cache->block_start_index)) { + int size1 = cache->block_end_index - cache->block_start_index; + int size2 = 0; + int idx1 = cache->block_cursor, idx2 = 0; + +// printf("Full Recaching!\n"); + + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + filelist_cache_previews_clear(cache); + } + + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_start_index = cache->block_end_index = cache->block_cursor = 0; + + /* New cached block does not overlap existing one, simple. */ + if (!filelist_file_cache_block_create(filelist, start_index, end_index - start_index, 0)) { + return false; + } + + cache->block_start_index = start_index; + cache->block_end_index = end_index; + } + else { +// printf("Partial Recaching!\n"); + + /* At this point, we know we keep part of currently cached entries, so update previews if needed, + * and remove everything from working queue - we'll add all newly needed entries at the end. */ + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + filelist_cache_previews_update(filelist); + filelist_cache_previews_clear(cache); + } + +// printf("\tpreview cleaned up...\n"); + + if (start_index > cache->block_start_index) { + int size1 = start_index - cache->block_start_index; + int size2 = 0; + int idx1 = cache->block_cursor, idx2 = 0; + +// printf("\tcache releasing: [%d:%d] (%d, %d)\n", cache->block_start_index, cache->block_start_index + size1, cache->block_cursor, size1); + + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_cursor = (idx1 + size1 + size2) % cache_size; + cache->block_start_index = start_index; + } + if (end_index < cache->block_end_index) { + int size1 = cache->block_end_index - end_index; + int size2 = 0; + int idx1, idx2 = 0; + +// printf("\tcache releasing: [%d:%d] (%d)\n", cache->block_end_index - size1, cache->block_end_index, cache->block_cursor); + + idx1 = (cache->block_cursor + end_index - cache->block_start_index) % cache_size; + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_end_index = end_index; + } + +// printf("\tcache cleaned up...\n"); + + if (start_index < cache->block_start_index) { + /* Add (request) needed entries before already cached ones. */ + /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */ + int size1 = cache->block_start_index - start_index; + int size2 = 0; + int idx1, idx2; + + if (size1 > cache->block_cursor) { + size2 = size1; + size1 -= cache->block_cursor; + size2 -= size1; + idx2 = 0; + idx1 = cache_size - size1; + } + else { + idx1 = cache->block_cursor - size1; + } + + if (size2) { + if (!filelist_file_cache_block_create(filelist, start_index + size1, size2, idx2)) { + return false; + } + } + if (!filelist_file_cache_block_create(filelist, start_index, size1, idx1)) { + return false; + } + + cache->block_cursor = idx1; + cache->block_start_index = start_index; + } +// printf("\tstart-extended...\n"); + if (end_index > cache->block_end_index) { + /* Add (request) needed entries after already cached ones. */ + /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */ + int size1 = end_index - cache->block_end_index; + int size2 = 0; + int idx1, idx2; + + idx1 = (cache->block_cursor + end_index - cache->block_start_index - size1) % cache_size; + if ((idx1 + size1) > cache_size) { + size2 = size1; + size1 = cache_size - idx1; + size2 -= size1; + idx2 = 0; + } + + if (size2) { + if (!filelist_file_cache_block_create(filelist, end_index - size2, size2, idx2)) { + return false; + } + } + if (!filelist_file_cache_block_create(filelist, end_index - size1 - size2, size1, idx1)) { + return false; + } + + cache->block_end_index = end_index; + } + +// printf("\tend-extended...\n"); + } + } + else if ((cache->block_center_index != index) && (cache->flags & FLC_PREVIEWS_ACTIVE)) { + /* We try to always preview visible entries first, so 'restart' preview background task. */ + filelist_cache_previews_update(filelist); + filelist_cache_previews_clear(cache); + } + +// printf("Re-queueing previews...\n"); + + /* Note we try to preview first images around given index - i.e. assumed visible ones. */ + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + for (i = 0; ((index + i) < end_index) || ((index - i) >= start_index); i++) { + if ((index - i) >= start_index) { + const int idx = (cache->block_cursor + (index - start_index) - i) % cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[idx], index - i); + } + if ((index + i) < end_index) { + const int idx = (cache->block_cursor + (index - start_index) + i) % cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[idx], index + i); + } + } + } + + cache->block_center_index = index; + +// printf("%s Finished!\n", __func__); + + return true; +} + +void filelist_cache_previews_set(FileList *filelist, const bool use_previews) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + if (use_previews == ((cache->flags & FLC_PREVIEWS_ACTIVE) != 0)) { + return; + } + /* Do not start preview work while listing, gives nasty flickering! */ + else if (use_previews && (filelist->flags & FL_IS_READY)) { + cache->flags |= FLC_PREVIEWS_ACTIVE; + + BLI_assert((cache->previews_pool == NULL) && (cache->previews_todo == NULL) && (cache->previews_done == NULL)); + +// printf("%s: Init Previews...\n", __func__); + + /* No need to populate preview queue here, filelist_file_cache_block() handles this. */ + } + else { +// printf("%s: Clear Previews...\n", __func__); + + filelist_cache_previews_free(cache, true); + } +} + +bool filelist_cache_previews_update(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + TaskPool *pool = cache->previews_pool; + bool changed = false; + + if (!pool) { + return changed; + } + +// printf("%s: Update Previews...\n", __func__); + + while (!BLI_thread_queue_is_empty(cache->previews_done)) { + FileListEntryPreview *preview = BLI_thread_queue_pop(cache->previews_done); + + /* entry might have been removed from cache in the mean while, we do not want to cache it again here. */ + FileDirEntry *entry = filelist_file_ex(filelist, preview->index, false); + +// printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + + if (preview->img) { + /* Due to asynchronous process, a preview for a given image may be generated several times, i.e. + * entry->image may already be set at this point. */ + if (entry && !entry->image) { + entry->image = preview->img; + changed = true; + } + else { + IMB_freeImBuf(preview->img); + } + } + else if (entry) { + /* We want to avoid re-processing this entry continuously! + * Note that, since entries only live in cache, preview will be retried quite often anyway. */ + entry->flags |= FILE_ENTRY_INVALID_PREVIEW; + } + + MEM_freeN(preview); + cache->previews_pending--; + BLI_assert(cache->previews_pending >= 0); + } + +// printf("%s: Previews pending: %d\n", __func__, cache->previews_pending); + if (cache->previews_pending == 0) { + if (cache->previews_timestamp == 0.0) { + cache->previews_timestamp = PIL_check_seconds_timer(); + } + else if (PIL_check_seconds_timer() - cache->previews_timestamp > 1.0) { + /* Preview task is IDLE since more than (approximatively) 1000ms, + * kill workers & task for now - they will be automatically restarted when needed. */ +// printf("%s: Inactive preview task, sleeping (%f vs %f)!\n", __func__, PIL_check_seconds_timer(), cache->previews_timestamp); + filelist_cache_previews_free(cache, false); + } + } + + return changed; +} + +bool filelist_cache_previews_running(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + return (cache->previews_pool != NULL); +} + +/* would recognize .blend as well */ +static bool file_is_blend_backup(const char *str) +{ + const size_t a = strlen(str); + size_t b = 7; + bool retval = 0; + + if (a == 0 || b >= a) { + /* pass */ + } + else { + const char *loc; + + if (a > b + 1) + b++; + + /* allow .blend1 .blend2 .blend32 */ + loc = BLI_strcasestr(str + a - b, ".blend"); + + if (loc) + retval = 1; + } + + return (retval); +} + +static int path_extension_type(const char *path) +{ + if (BLO_has_bfile_extension(path)) { + return FILE_TYPE_BLENDER; + } + else if (file_is_blend_backup(path)) { + return FILE_TYPE_BLENDER_BACKUP; + } + else if (BLI_testextensie(path, ".app")) { + return FILE_TYPE_APPLICATIONBUNDLE; } else if (BLI_testextensie(path, ".py")) { return FILE_TYPE_PYSCRIPT; @@ -857,10 +1966,10 @@ static int path_extension_type(const char *path) return 0; } -static int file_extension_type(const char *dir, const char *relname) +static int file_extension_type(const char *dir, const char *relpath) { char path[FILE_MAX]; - BLI_join_dirfile(path, sizeof(path), dir, relname); + BLI_join_dirfile(path, sizeof(path), dir, relpath); return path_extension_type(path); } @@ -868,185 +1977,137 @@ int ED_file_extension_icon(const char *path) { int type = path_extension_type(path); - if (type == FILE_TYPE_BLENDER) - return ICON_FILE_BLEND; - else if (type == FILE_TYPE_BLENDER_BACKUP) - return ICON_FILE_BACKUP; - else if (type == FILE_TYPE_IMAGE) - return ICON_FILE_IMAGE; - else if (type == FILE_TYPE_MOVIE) - return ICON_FILE_MOVIE; - else if (type == FILE_TYPE_PYSCRIPT) - return ICON_FILE_SCRIPT; - else if (type == FILE_TYPE_SOUND) - return ICON_FILE_SOUND; - else if (type == FILE_TYPE_FTFONT) - return ICON_FILE_FONT; - else if (type == FILE_TYPE_BTX) - return ICON_FILE_BLANK; - else if (type == FILE_TYPE_COLLADA) - return ICON_FILE_BLANK; - else if (type == FILE_TYPE_TEXT) - return ICON_FILE_TEXT; - - return ICON_FILE_BLANK; -} - -static void filelist_setfiletypes(struct FileList *filelist) -{ - struct direntry *file; - int num; - - file = filelist->filelist; - - for (num = 0; num < filelist->numfiles; num++, file++) { -#ifndef __APPLE__ - /* Don't check extensions for directories, allow in OSX cause bundles have extensions*/ - if (file->type & S_IFDIR) { - continue; - } -#endif - if (filelist->filter_data.filter_glob[0] && - BLI_testextensie_glob(file->relname, filelist->filter_data.filter_glob)) - { - file->flags = FILE_TYPE_OPERATOR; - } - else { - file->flags = file_extension_type(filelist->dir, file->relname); - } + switch (type) { + case FILE_TYPE_BLENDER: + return ICON_FILE_BLEND; + case FILE_TYPE_BLENDER_BACKUP: + return ICON_FILE_BACKUP; + case FILE_TYPE_IMAGE: + return ICON_FILE_IMAGE; + case FILE_TYPE_MOVIE: + return ICON_FILE_MOVIE; + case FILE_TYPE_PYSCRIPT: + return ICON_FILE_SCRIPT; + case FILE_TYPE_SOUND: + return ICON_FILE_SOUND; + case FILE_TYPE_FTFONT: + return ICON_FILE_FONT; + case FILE_TYPE_BTX: + return ICON_FILE_BLANK; + case FILE_TYPE_COLLADA: + return ICON_FILE_BLANK; + case FILE_TYPE_TEXT: + return ICON_FILE_TEXT; + default: + return ICON_FILE_BLANK; } } -static void filelist_read_dir(struct FileList *filelist) -{ - if (!filelist) return; - - filelist->fidx = NULL; - filelist->filelist = NULL; - - BLI_make_exist(filelist->dir); - BLI_cleanup_dir(G.main->name, filelist->dir); - filelist->numfiles = BLI_filelist_dir_contents(filelist->dir, &(filelist->filelist)); - - /* We shall *never* get an empty list here, since we now the dir exists and is readable - * (ensured by BLI_make_exist()). So we expect at the very least the parent '..' entry. */ - BLI_assert(filelist->numfiles != 0); - - filelist_setfiletypes(filelist); -} - -static void filelist_read_main(struct FileList *filelist) +int filelist_empty(struct FileList *filelist) { - if (!filelist) return; - filelist_from_main(filelist); + return (filelist->filelist.nbr_entries == 0); } -static void filelist_read_library(struct FileList *filelist) +unsigned int filelist_entry_select_set( + const FileList *filelist, const FileDirEntry *entry, FileSelType select, unsigned int flag, FileCheckType check) { - if (!filelist) return; - BLI_cleanup_dir(G.main->name, filelist->dir); - filelist_from_library(filelist); - if (!filelist->libfiledata) { - int num; - struct direntry *file; + /* Default NULL pointer if not found is fine here! */ + void **es_p = BLI_ghash_lookup_p(filelist->selection_state, entry->uuid); + unsigned int entry_flag = es_p ? GET_UINT_FROM_POINTER(*es_p) : 0; + const unsigned int org_entry_flag = entry_flag; - filelist_read_dir(filelist); - file = filelist->filelist; - for (num = 0; num < filelist->numfiles; num++, file++) { - if (BLO_has_bfile_extension(file->relname)) { - char name[FILE_MAX]; + BLI_assert(entry); + BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); - BLI_join_dirfile(name, sizeof(name), filelist->dir, file->relname); + if (((check == CHECK_ALL)) || + ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || + ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) + { + switch (select) { + case FILE_SEL_REMOVE: + entry_flag &= ~flag; + break; + case FILE_SEL_ADD: + entry_flag |= flag; + break; + case FILE_SEL_TOGGLE: + entry_flag ^= flag; + break; + } + } - /* prevent current file being used as acceptable dir */ - if (BLI_path_cmp(G.main->name, name) != 0) { - file->type &= ~S_IFMT; - file->type |= S_IFDIR; - } + if (entry_flag != org_entry_flag) { + if (es_p) { + if (entry_flag) { + *es_p = SET_UINT_IN_POINTER(entry_flag); } + else { + BLI_ghash_remove(filelist->selection_state, entry->uuid, MEM_freeN, NULL); + } + } + else if (entry_flag) { + void *key = MEM_mallocN(sizeof(entry->uuid), __func__); + memcpy(key, entry->uuid, sizeof(entry->uuid)); + BLI_ghash_insert(filelist->selection_state, key, SET_UINT_IN_POINTER(entry_flag)); } } -} - -void filelist_readdir(struct FileList *filelist) -{ - filelist->readf(filelist); - filelist->need_sorting = true; - filelist->need_thumbnails = true; - filelist_filter_clear(filelist); + return entry_flag; } -int filelist_empty(struct FileList *filelist) +void filelist_entry_select_index_set(FileList *filelist, const int index, FileSelType select, unsigned int flag, FileCheckType check) { - return filelist->filelist == NULL; -} + FileDirEntry *entry = filelist_file(filelist, index); -void filelist_select_file(struct FileList *filelist, int index, FileSelType select, unsigned int flag, FileCheckType check) -{ - struct direntry *file = filelist_file(filelist, index); - if (file != NULL) { - int check_ok = 0; - switch (check) { - case CHECK_DIRS: - check_ok = S_ISDIR(file->type); - break; - case CHECK_ALL: - check_ok = 1; - break; - case CHECK_FILES: - default: - check_ok = !S_ISDIR(file->type); - break; - } - if (check_ok) { - switch (select) { - case FILE_SEL_REMOVE: - file->selflag &= ~flag; - break; - case FILE_SEL_ADD: - file->selflag |= flag; - break; - case FILE_SEL_TOGGLE: - file->selflag ^= flag; - break; - } - } + if (entry) { + filelist_entry_select_set(filelist, entry, select, flag, check); } } -void filelist_select(struct FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check) +void filelist_entries_select_index_range_set( + FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check) { /* select all valid files between first and last indicated */ - if ((sel->first >= 0) && (sel->first < filelist->numfiltered) && (sel->last >= 0) && (sel->last < filelist->numfiltered)) { + if ((sel->first >= 0) && (sel->first < filelist->filelist.nbr_entries_filtered) && + (sel->last >= 0) && (sel->last < filelist->filelist.nbr_entries_filtered)) + { int current_file; for (current_file = sel->first; current_file <= sel->last; current_file++) { - filelist_select_file(filelist, current_file, select, flag, check); + filelist_entry_select_index_set(filelist, current_file, select, flag, check); } } } -bool filelist_is_selected(struct FileList *filelist, int index, FileCheckType check) +unsigned int filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileCheckType check) { - struct direntry *file = filelist_file(filelist, index); - if (!file) { - return 0; - } - switch (check) { - case CHECK_DIRS: - return S_ISDIR(file->type) && (file->selflag & FILE_SEL_SELECTED); - case CHECK_FILES: - return S_ISREG(file->type) && (file->selflag & FILE_SEL_SELECTED); - case CHECK_ALL: - default: - return (file->selflag & FILE_SEL_SELECTED) != 0; + BLI_assert(entry); + BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); + + if (((check == CHECK_ALL)) || + ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || + ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) + { + /* Default NULL pointer if not found is fine here! */ + return GET_UINT_FROM_POINTER(BLI_ghash_lookup(filelist->selection_state, entry->uuid)); } + + return 0; } +unsigned int filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check) +{ + FileDirEntry *entry = filelist_file(filelist, index); + + if (entry) { + return filelist_entry_select_get(filelist, entry, check); + } + + return 0; +} bool filelist_islibrary(struct FileList *filelist, char *dir, char **group) { - return BLO_library_path_explode(filelist->dir, dir, group, NULL); + return BLO_library_path_explode(filelist->filelist.root, dir, group, NULL); } static int groupname_to_code(const char *group) @@ -1054,6 +2115,8 @@ 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) @@ -1062,182 +2125,231 @@ static int groupname_to_code(const char *group) return buf[0] ? BKE_idcode_from_name(buf) : 0; } -static void filelist_from_library(struct FileList *filelist) +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 bacground 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; + + 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); + if (S_ISDIR(files[i].s.st_mode)) { + entry->typeflag |= FILE_TYPE_DIR; + } + entry->st = files[i].s; + + /* Set file type. */ + /* If we are considering .blend files as libs, promote them to directory status! */ + if (do_lib && BLO_has_bfile_extension(entry->relpath)) { + char name[FILE_MAX]; + + entry->typeflag = FILE_TYPE_BLENDER; + + BLI_join_dirfile(name, sizeof(name), root, entry->relpath); + + /* prevent current file being used as acceptable dir */ + if (BLI_path_cmp(main_name, name) != 0) { + entry->typeflag |= FILE_TYPE_DIR; + } + } + /* Otherwise, do not check extensions for directories! */ + else if (!(entry->typeflag & FILE_TYPE_DIR)) { + if (filter_glob[0] && BLI_testextensie_glob(entry->relpath, filter_glob)) { + entry->typeflag = FILE_TYPE_OPERATOR; + } + else { + entry->typeflag = file_extension_type(root, entry->relpath); + } + } + + 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) { - LinkNode *l, *names, *previews; - struct ImBuf *ima; - int ok, i, nprevs, nnames, idcode; - char filename[FILE_MAX]; + FileListInternEntry *entry; + LinkNode *ln, *names; + int i, nnames, idcode = 0, nbr_entries = 0; char dir[FILE_MAX], *group; + bool ok; + + struct BlendHandle *libfiledata = NULL; /* name test */ - ok = filelist_islibrary(filelist, dir, &group); + ok = BLO_library_path_explode(root, dir, &group, NULL); if (!ok) { - /* free */ - if (filelist->libfiledata) BLO_blendhandle_close(filelist->libfiledata); - filelist->libfiledata = NULL; - return; + return nbr_entries; } - - BLI_strncpy(filename, G.main->name, sizeof(filename)); /* there we go */ - /* for the time being only read filedata when libfiledata==0 */ - if (filelist->libfiledata == NULL) { - filelist->libfiledata = BLO_blendhandle_from_file(dir, NULL); - if (filelist->libfiledata == NULL) return; + libfiledata = BLO_blendhandle_from_file(dir, NULL); + if (libfiledata == NULL) { + return nbr_entries; } - - idcode = group ? groupname_to_code(group) : 0; - - /* memory for strings is passed into filelist[i].relname - * and freed in freefilelist */ - if (idcode) { - previews = BLO_blendhandle_get_previews(filelist->libfiledata, idcode, &nprevs); - names = BLO_blendhandle_get_datablock_names(filelist->libfiledata, idcode, &nnames); - /* ugh, no rewind, need to reopen */ - BLO_blendhandle_close(filelist->libfiledata); - filelist->libfiledata = BLO_blendhandle_from_file(dir, NULL); - + + /* 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 { - previews = NULL; - nprevs = 0; - names = BLO_blendhandle_get_linkable_groups(filelist->libfiledata); + names = BLO_blendhandle_get_linkable_groups(libfiledata); nnames = BLI_linklist_count(names); } - filelist->numfiles = nnames + 1; - filelist->filelist = MEM_mallocN(filelist->numfiles * sizeof(*filelist->filelist), __func__); - memset(filelist->filelist, 0, filelist->numfiles * sizeof(*filelist->filelist)); - - filelist->filelist[0].relname = BLI_strdup(FILENAME_PARENT); - filelist->filelist[0].type |= S_IFDIR; - - for (i = 0, l = names; i < nnames; i++, l = l->next) { - const char *blockname = l->link; - - filelist->filelist[i + 1].relname = BLI_strdup(blockname); - if (idcode) { - filelist->filelist[i + 1].type |= S_IFREG; - } - else { - filelist->filelist[i + 1].type |= S_IFDIR; - } - } + BLO_blendhandle_close(libfiledata); - if (previews && (nnames != nprevs)) { - printf("filelist_from_library: error, found %d items, %d previews\n", nnames, nprevs); + 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++; } - else if (previews) { - for (i = 0, l = previews; i < nnames; i++, l = l->next) { - PreviewImage *img = l->link; - if (img) { - unsigned int w = img->w[ICON_SIZE_PREVIEW]; - unsigned int h = img->h[ICON_SIZE_PREVIEW]; - unsigned int *rect = img->rect[ICON_SIZE_PREVIEW]; + for (i = 0, ln = names; i < nnames; i++, ln = ln->next) { + const char *blockname = ln->link; - /* first allocate imbuf for copying preview into it */ - if (w > 0 && h > 0 && rect) { - ima = IMB_allocImBuf(w, h, 32, IB_rect); - memcpy(ima->rect, rect, w * h * sizeof(unsigned int)); - filelist->filelist[i + 1].image = ima; - filelist->filelist[i + 1].flags = FILE_TYPE_IMAGE; - } - } + 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); - if (previews) { - BLI_linklist_free(previews, BKE_previewimg_freefunc); - } - BLI_strncpy(G.main->name, filename, sizeof(filename)); /* prevent G.main->name to change */ + return nbr_entries; } -static void filelist_from_main(struct FileList *filelist) +#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(struct FileList *filelist) { ID *id; - struct direntry *files, *firstlib = NULL; + FileDirEntry *files, *firstlib = NULL; ListBase *lb; int a, fake, idcode, ok, totlib, totbl; + + // filelist->type = FILE_MAIN; // XXX TODO: add modes to filebrowser - // filelist->type = FILE_MAIN; // XXXXX TODO: add modes to filebrowser + BLI_assert(filelist->filelist.entries == NULL); - if (filelist->dir[0] == '/') filelist->dir[0] = 0; + if (filelist->filelist.root[0] == '/') filelist->filelist.root[0] = '\0'; - if (filelist->dir[0]) { - idcode = groupname_to_code(filelist->dir); - if (idcode == 0) filelist->dir[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) { /* make directories */ #ifdef WITH_FREESTYLE - filelist->numfiles = 24; + filelist->filelist.nbr_entries = 24; #else - filelist->numfiles = 23; + filelist->filelist.nbr_entries = 23; #endif - filelist->filelist = MEM_mallocN(sizeof(*filelist->filelist) * filelist->numfiles, __func__); - - for (a = 0; a < filelist->numfiles; a++) { - memset(&(filelist->filelist[a]), 0, sizeof(struct direntry)); - filelist->filelist[a].type |= S_IFDIR; - } - - filelist->filelist[0].relname = BLI_strdup(FILENAME_PARENT); - filelist->filelist[1].relname = BLI_strdup("Scene"); - filelist->filelist[2].relname = BLI_strdup("Object"); - filelist->filelist[3].relname = BLI_strdup("Mesh"); - filelist->filelist[4].relname = BLI_strdup("Curve"); - filelist->filelist[5].relname = BLI_strdup("Metaball"); - filelist->filelist[6].relname = BLI_strdup("Material"); - filelist->filelist[7].relname = BLI_strdup("Texture"); - filelist->filelist[8].relname = BLI_strdup("Image"); - filelist->filelist[9].relname = BLI_strdup("Ika"); - filelist->filelist[10].relname = BLI_strdup("Wave"); - filelist->filelist[11].relname = BLI_strdup("Lattice"); - filelist->filelist[12].relname = BLI_strdup("Lamp"); - filelist->filelist[13].relname = BLI_strdup("Camera"); - filelist->filelist[14].relname = BLI_strdup("Ipo"); - filelist->filelist[15].relname = BLI_strdup("World"); - filelist->filelist[16].relname = BLI_strdup("Screen"); - filelist->filelist[17].relname = BLI_strdup("VFont"); - filelist->filelist[18].relname = BLI_strdup("Text"); - filelist->filelist[19].relname = BLI_strdup("Armature"); - filelist->filelist[20].relname = BLI_strdup("Action"); - filelist->filelist[21].relname = BLI_strdup("NodeTree"); - filelist->filelist[22].relname = BLI_strdup("Speaker"); + filelist_resize(filelist, filelist->filelist.nbr_entries); + + for (a = 0; a < filelist->filelist.nbr_entries; a++) { + filelist->filelist.entries[a].typeflag |= FILE_TYPE_DIR; + } + + filelist->filelist.entries[0].entry->relpath = BLI_strdup(FILENAME_PARENT); + filelist->filelist.entries[1].entry->relpath = BLI_strdup("Scene"); + filelist->filelist.entries[2].entry->relpath = BLI_strdup("Object"); + filelist->filelist.entries[3].entry->relpath = BLI_strdup("Mesh"); + filelist->filelist.entries[4].entry->relpath = BLI_strdup("Curve"); + filelist->filelist.entries[5].entry->relpath = BLI_strdup("Metaball"); + filelist->filelist.entries[6].entry->relpath = BLI_strdup("Material"); + filelist->filelist.entries[7].entry->relpath = BLI_strdup("Texture"); + filelist->filelist.entries[8].entry->relpath = BLI_strdup("Image"); + filelist->filelist.entries[9].entry->relpath = BLI_strdup("Ika"); + filelist->filelist.entries[10].entry->relpath = BLI_strdup("Wave"); + filelist->filelist.entries[11].entry->relpath = BLI_strdup("Lattice"); + filelist->filelist.entries[12].entry->relpath = BLI_strdup("Lamp"); + filelist->filelist.entries[13].entry->relpath = BLI_strdup("Camera"); + filelist->filelist.entries[14].entry->relpath = BLI_strdup("Ipo"); + filelist->filelist.entries[15].entry->relpath = BLI_strdup("World"); + filelist->filelist.entries[16].entry->relpath = BLI_strdup("Screen"); + filelist->filelist.entries[17].entry->relpath = BLI_strdup("VFont"); + filelist->filelist.entries[18].entry->relpath = BLI_strdup("Text"); + filelist->filelist.entries[19].entry->relpath = BLI_strdup("Armature"); + filelist->filelist.entries[20].entry->relpath = BLI_strdup("Action"); + filelist->filelist.entries[21].entry->relpath = BLI_strdup("NodeTree"); + filelist->filelist.entries[22].entry->relpath = BLI_strdup("Speaker"); #ifdef WITH_FREESTYLE - filelist->filelist[23].relname = BLI_strdup("FreestyleLineStyle"); + filelist->filelist.entries[23].entry->relpath = BLI_strdup("FreestyleLineStyle"); #endif } else { /* make files */ - idcode = groupname_to_code(filelist->dir); + idcode = groupname_to_code(filelist->filelist.root); lb = which_libbase(G.main, idcode); if (lb == NULL) return; - filelist->numfiles = 0; + filelist->filelist.nbr_entries = 0; for (id = lb->first; id; id = id->next) { - if (!filelist->filter_data.hide_dot || id->name[2] != '.') { - filelist->numfiles++; + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { + filelist->filelist.nbr_entries++; } } - /* XXXXX TODO: if databrowse F4 or append/link filelist->hide_parent has to be set */ - if (!filelist->filter_data.hide_parent) filelist->numfiles += 1; - filelist->filelist = filelist->numfiles > 0 ? MEM_mallocN(sizeof(*filelist->filelist) * filelist->numfiles, __func__) : NULL; + /* XXX TODO: if databrowse F4 or append/link filelist->flags & FLF_HIDE_PARENT has to be set */ + if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) + filelist->filelist.nbr_entries++; - files = filelist->filelist; + if (filelist->filelist.nbr_entries > 0) { + filelist_resize(filelist, filelist->filelist.nbr_entries); + } - if (files && !filelist->filter_data.hide_parent) { - memset(&(filelist->filelist[0]), 0, sizeof(struct direntry)); - filelist->filelist[0].relname = BLI_strdup(FILENAME_PARENT); - filelist->filelist[0].type |= S_IFDIR; + files = filelist->filelist.entries; + + if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) { + files->entry->relpath = BLI_strdup(FILENAME_PARENT); + files->typeflag |= FILE_TYPE_DIR; files++; } @@ -1246,37 +2358,36 @@ static void filelist_from_main(struct FileList *filelist) for (id = lb->first; id; id = id->next) { ok = 1; if (ok) { - if (files && (!filelist->filter_data.hide_dot || id->name[2] != '.')) { - memset(files, 0, sizeof(struct direntry)); + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { if (id->lib == NULL) { - files->relname = BLI_strdup(id->name + 2); + files->entry->relpath = BLI_strdup(id->name + 2); } else { - char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3]; + char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3]; BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->name, id->name + 2); - files->relname = BLI_strdup(relname); + files->entry->relpath = BLI_strdup(relname); } - files->type |= S_IFREG; -#if 0 /* XXXXX TODO show the selection status of the objects */ +// files->type |= S_IFREG; +#if 0 /* XXX TODO show the selection status of the objects */ if (!filelist->has_func) { /* F4 DATA BROWSE */ if (idcode == ID_OB) { - if ( ((Object *)id)->flag & SELECT) files->selflag |= FILE_SEL_SELECTED; + if ( ((Object *)id)->flag & SELECT) files->entry->selflag |= FILE_SEL_SELECTED; } else if (idcode == ID_SCE) { - if ( ((Scene *)id)->r.scemode & R_BG_RENDER) files->selflag |= FILE_SEL_SELECTED; + if ( ((Scene *)id)->r.scemode & R_BG_RENDER) files->entry->selflag |= FILE_SEL_SELECTED; } } #endif - files->nr = totbl + 1; - files->poin = id; +// files->entry->nr = totbl + 1; + files->entry->poin = id; fake = id->flag & LIB_FAKEUSER; if (idcode == ID_MA || idcode == ID_TE || idcode == ID_LA || idcode == ID_WO || idcode == ID_IM) { - files->flags |= FILE_TYPE_IMAGE; + files->typeflag |= FILE_TYPE_IMAGE; } - if (id->lib && fake) BLI_snprintf(files->extra, sizeof(files->extra), "LF %d", id->us); - else if (id->lib) BLI_snprintf(files->extra, sizeof(files->extra), "L %d", id->us); - else if (fake) BLI_snprintf(files->extra, sizeof(files->extra), "F %d", id->us); - else BLI_snprintf(files->extra, sizeof(files->extra), " %d", id->us); +// if (id->lib && fake) BLI_snprintf(files->extra, sizeof(files->entry->extra), "LF %d", id->us); +// else if (id->lib) BLI_snprintf(files->extra, sizeof(files->entry->extra), "L %d", id->us); +// else if (fake) BLI_snprintf(files->extra, sizeof(files->entry->extra), "F %d", id->us); +// else BLI_snprintf(files->extra, sizeof(files->entry->extra), " %d", id->us); if (id->lib) { if (totlib == 0) firstlib = files; @@ -1291,152 +2402,279 @@ static void filelist_from_main(struct FileList *filelist) /* only qsort of library blocks */ if (totlib > 1) { - qsort(firstlib, totlib, sizeof(struct direntry), compare_name); + qsort(firstlib, totlib, sizeof(*files), compare_name); } } } +#endif -/* ********** Thumbnails job ********** */ +static void filelist_readjob_do( + const bool do_lib, + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) +{ + ListBase entries = {0}; + BLI_Stack *todo_dirs; + TodoDir *td_dir; + char dir[FILE_MAX_LIBEXTRA]; + char filter_glob[64]; /* TODO should be define! */ + const char *root = filelist->filelist.root; + const int max_recursion = filelist->max_recursion; + int nbr_done_dirs = 0, nbr_todo_dirs = 1; -typedef struct ThumbnailJob { - ListBase loadimages; - ImBuf *static_icons_buffers[BIFICONID_LAST]; - const short *stop; - const short *do_update; - struct FileList *filelist; - ReportList reports; -} ThumbnailJob; +// BLI_assert(filelist->filtered == NULL); + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == 0)); -bool filelist_need_thumbnails(FileList *filelist) -{ - return filelist->need_thumbnails; -} + todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = 1; -static void thumbnail_joblist_free(ThumbnailJob *tj) -{ - FileImage *limg = tj->loadimages.first; - - /* free the images not yet copied to the filelist -> these will get freed with the filelist */ - for (; limg; limg = limg->next) { - if ((limg->img) && (!limg->done)) { - IMB_freeImBuf(limg->img); - } - } - BLI_freelistN(&tj->loadimages); -} + BLI_strncpy(dir, filelist->filelist.root, sizeof(dir)); + BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob)); -static void thumbnails_startjob(void *tjv, short *stop, short *do_update, float *UNUSED(progress)) -{ - ThumbnailJob *tj = tjv; - FileImage *limg = tj->loadimages.first; + BLI_cleanup_dir(main_name, dir); + td_dir->dir = BLI_strdup(dir); - tj->stop = stop; - tj->do_update = do_update; + while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { + FileListInternEntry *entry; + int nbr_entries = 0; + bool is_lib = do_lib; - while ((*stop == 0) && (limg)) { - ThumbSource source = 0; + char *subdir; + int recursion_level; + bool skip_currpar; - BLI_assert(limg->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)); - if (limg->flags & FILE_TYPE_IMAGE) { - source = THB_SOURCE_IMAGE; + td_dir = BLI_stack_peek(todo_dirs); + subdir = td_dir->dir; + recursion_level = td_dir->level; + skip_currpar = (recursion_level > 1); + + BLI_stack_discard(todo_dirs); + + if (do_lib) { + nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar); } - else if (limg->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - source = THB_SOURCE_BLEND; + if (!nbr_entries) { + is_lib = false; + nbr_entries = filelist_readjob_list_dir(subdir, &entries, filter_glob, do_lib, main_name, skip_currpar); } - else if (limg->flags & FILE_TYPE_MOVIE) { - source = THB_SOURCE_MOVIE; + + for (entry = entries.first; entry; entry = entry->next) { + BLI_join_dirfile(dir, sizeof(dir), subdir, entry->relpath); + BLI_cleanup_file(root, dir); + + /* Generate our entry uuid. Abusing uuid as an uint64, shall be more than enough here, + * things would crash way before we overflow that counter! + * Using an atomic operation to avoid having to lock thread... + * Note that we do not really need this here currently, since there is a single listing thread, but better + * remain consistent about threading! */ + *((uint64_t *)entry->uuid) = atomic_add_uint64((uint64_t *)filelist->filelist_intern.curr_uuid, 1); + + BLI_path_rel(dir, root); + /* Only thing we change in direntry here, so we need to free it first. */ + MEM_freeN(entry->relpath); + entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' added by BLI_path_rel */ + entry->name = BLI_strdup(fileentry_uiname(root, entry->relpath, entry->typeflag, dir)); + + /* Here we decide whether current filedirentry is to be listed too, or not. */ + if (max_recursion && (is_lib || (recursion_level <= max_recursion))) { + if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) { + /* Skip... */ + } + else if (!is_lib && (recursion_level >= max_recursion) && + ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) + { + /* Do not recurse in real directories in this case, only in .blend libs. */ + } + else { + /* We have a directory we want to list, add it to todo list! */ + BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); + BLI_cleanup_dir(main_name, dir); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = recursion_level + 1; + td_dir->dir = BLI_strdup(dir); + nbr_todo_dirs++; + } + } } - else if (limg->flags & FILE_TYPE_FTFONT) { - source = THB_SOURCE_FONT; + + if (nbr_entries) { + BLI_mutex_lock(lock); + + *do_update = true; + + BLI_movelisttolist(&filelist->filelist.entries, &entries); + filelist->filelist.nbr_entries += nbr_entries; + + BLI_mutex_unlock(lock); } - limg->img = IMB_thumb_manage(limg->path, THB_LARGE, source); - *do_update = true; - PIL_sleep_ms(10); - limg = limg->next; + + nbr_done_dirs++; + *progress = (float)nbr_done_dirs / (float)nbr_todo_dirs; + MEM_freeN(subdir); + } + + /* If we were interrupted by stop, stack may not be empty and we need to free pending dir paths. */ + while (!BLI_stack_is_empty(todo_dirs)) { + td_dir = BLI_stack_peek(todo_dirs); + MEM_freeN(td_dir->dir); + BLI_stack_discard(todo_dirs); } + BLI_stack_free(todo_dirs); } -static void thumbnails_update(void *tjv) +static void filelist_readjob_dir( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) { - ThumbnailJob *tj = tjv; + filelist_readjob_do(false, filelist, main_name, stop, do_update, progress, lock); +} - if (tj->filelist && tj->filelist->filelist) { - FileImage *limg = tj->loadimages.first; - while (limg) { - if (!limg->done && limg->img) { - tj->filelist->filelist[limg->index].image = IMB_dupImBuf(limg->img); - limg->done = true; - IMB_freeImBuf(limg->img); - limg->img = NULL; - } - limg = limg->next; - } - } +static void filelist_readjob_lib( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) +{ + filelist_readjob_do(true, filelist, main_name, stop, do_update, progress, lock); +} + +static void filelist_readjob_main( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) +{ + /* TODO! */ + filelist_readjob_dir(filelist, main_name, stop, do_update, progress, lock); +} + + +typedef struct FileListReadJob { + ThreadMutex lock; + char main_name[FILE_MAX]; + struct FileList *filelist; + struct FileList *tmp_filelist; /* XXX We may use a simpler struct here... just a linked list and root path? */ +} FileListReadJob; + +static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress) +{ + FileListReadJob *flrj = flrjv; + +// printf("START filelist reading (%d files, main thread: %d)\n", +// flrj->filelist->filelist.nbr_entries, BLI_thread_is_main()); + + BLI_mutex_lock(&flrj->lock); + + BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist); + + flrj->tmp_filelist = MEM_dupallocN(flrj->filelist); + + BLI_listbase_clear(&flrj->tmp_filelist->filelist.entries); + flrj->tmp_filelist->filelist.nbr_entries = 0; + + flrj->tmp_filelist->filelist_intern.filtered = NULL; + BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries); + memset(flrj->tmp_filelist->filelist_intern.curr_uuid, 0, sizeof(flrj->tmp_filelist->filelist_intern.curr_uuid)); + + flrj->tmp_filelist->libfiledata = NULL; + memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache)); + flrj->tmp_filelist->selection_state = NULL; + + BLI_mutex_unlock(&flrj->lock); + + flrj->tmp_filelist->read_jobf(flrj->tmp_filelist, flrj->main_name, stop, do_update, progress, &flrj->lock); } -static void thumbnails_endjob(void *tjv) +static void filelist_readjob_update(void *flrjv) { - ThumbnailJob *tj = tjv; + FileListReadJob *flrj = flrjv; + FileListIntern *fl_intern = &flrj->filelist->filelist_intern; + ListBase new_entries = {NULL}; + int nbr_entries, new_nbr_entries = 0; + + BLI_movelisttolist(&new_entries, &fl_intern->entries); + nbr_entries = flrj->filelist->filelist.nbr_entries; + + BLI_mutex_lock(&flrj->lock); + + if (flrj->tmp_filelist->filelist.nbr_entries) { + /* We just move everything out of 'thread context' into final list. */ + new_nbr_entries = flrj->tmp_filelist->filelist.nbr_entries; + BLI_movelisttolist(&new_entries, &flrj->tmp_filelist->filelist.entries); + flrj->tmp_filelist->filelist.nbr_entries = 0; + } + + BLI_mutex_unlock(&flrj->lock); - if (!*tj->stop) { - tj->filelist->need_thumbnails = false; + if (new_nbr_entries) { + /* Do not clear selection cache, we can assume already 'selected' uuids are still valid! */ + filelist_clear_ex(flrj->filelist, true, false); + + flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); } + + /* if no new_nbr_entries, this is NOP */ + BLI_movelisttolist(&fl_intern->entries, &new_entries); + flrj->filelist->filelist.nbr_entries = nbr_entries + new_nbr_entries; } -static void thumbnails_free(void *tjv) +static void filelist_readjob_endjob(void *flrjv) { - ThumbnailJob *tj = tjv; - thumbnail_joblist_free(tj); - MEM_freeN(tj); + FileListReadJob *flrj = flrjv; + + /* In case there would be some dangling update... */ + filelist_readjob_update(flrjv); + + flrj->filelist->flags &= ~FL_IS_PENDING; + flrj->filelist->flags |= FL_IS_READY; } +static void filelist_readjob_free(void *flrjv) +{ + FileListReadJob *flrj = flrjv; + +// printf("END filelist reading (%d files)\n", flrj->filelist->filelist.nbr_entries); + + if (flrj->tmp_filelist) { + /* tmp_filelist shall never ever be filtered! */ + BLI_assert(flrj->tmp_filelist->filelist.nbr_entries == 0); + BLI_assert(BLI_listbase_is_empty(&flrj->tmp_filelist->filelist.entries)); + + filelist_freelib(flrj->tmp_filelist); + filelist_free(flrj->tmp_filelist); + MEM_freeN(flrj->tmp_filelist); + } + + BLI_mutex_end(&flrj->lock); + + MEM_freeN(flrj); +} -void thumbnails_start(FileList *filelist, const bContext *C) +void filelist_readjob_start(FileList *filelist, const bContext *C) { wmJob *wm_job; - ThumbnailJob *tj; - int idx; + FileListReadJob *flrj; /* prepare job data */ - tj = MEM_callocN(sizeof(*tj), __func__); - tj->filelist = filelist; - for (idx = 0; idx < filelist->numfiles; idx++) { - if (!filelist->filelist[idx].path) { - continue; - } - if (!filelist->filelist[idx].image) { - if (filelist->filelist[idx].flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) - { - FileImage *limg = MEM_callocN(sizeof(*limg), __func__); - BLI_strncpy(limg->path, filelist->filelist[idx].path, sizeof(limg->path)); - limg->index = idx; - limg->flags = filelist->filelist[idx].flags; - BLI_addtail(&tj->loadimages, limg); - } - } - } + flrj = MEM_callocN(sizeof(*flrj), __func__); + flrj->filelist = filelist; + BLI_strncpy(flrj->main_name, G.main->name, sizeof(flrj->main_name)); + + filelist->flags &= ~(FL_FORCE_RESET | FL_IS_READY); + filelist->flags |= FL_IS_PENDING; - BKE_reports_init(&tj->reports, RPT_PRINT); + BLI_mutex_init(&flrj->lock); /* setup job */ - wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), filelist, "Thumbnails", - 0, WM_JOB_TYPE_FILESEL_THUMBNAIL); - WM_jobs_customdata_set(wm_job, tj, thumbnails_free); - WM_jobs_timer(wm_job, 0.5, NC_WINDOW, NC_WINDOW); - WM_jobs_callbacks(wm_job, thumbnails_startjob, NULL, thumbnails_update, thumbnails_endjob); + wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), CTX_wm_area(C), "Listing Dirs...", + WM_JOB_PROGRESS, WM_JOB_TYPE_FILESEL_READDIR); + WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free); + WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST); + WM_jobs_callbacks(wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob); /* start the job */ WM_jobs_start(CTX_wm_manager(C), wm_job); } -void thumbnails_stop(wmWindowManager *wm, FileList *filelist) +void filelist_readjob_stop(wmWindowManager *wm, ScrArea *sa) { - WM_jobs_kill_type(wm, filelist, WM_JOB_TYPE_FILESEL_THUMBNAIL); + WM_jobs_kill_type(wm, sa, WM_JOB_TYPE_FILESEL_READDIR); } -int thumbnails_running(wmWindowManager *wm, FileList *filelist) +int filelist_readjob_running(wmWindowManager *wm, ScrArea *sa) { - return WM_jobs_test(wm, filelist, WM_JOB_TYPE_FILESEL_THUMBNAIL); + return WM_jobs_test(wm, sa, WM_JOB_TYPE_FILESEL_READDIR); } -- cgit v1.2.3