diff options
author | Dalai Felinto <dfelinto@gmail.com> | 2015-09-24 18:24:20 +0300 |
---|---|---|
committer | Dalai Felinto <dfelinto@gmail.com> | 2015-09-24 18:24:20 +0300 |
commit | 27b3ea622f8bd313a8e2827dfec752bf2125566c (patch) | |
tree | f212e49d224ce8e1cfc3b17a64ae524711494391 /source/blender/editors/space_file/filelist.c | |
parent | 372dff8d1dc7e24d4b2cd37de245588ecfce8bfa (diff) | |
parent | de80e687689032cb85179a1f7e89750573631d5d (diff) |
Merge remote-tracking branch 'origin/master' into cycles_camera_nodescycles_camera_nodes
Note: the branch currently crashes in blender_camera_nodes.cpp:
BL::NodeTree b_ntree = b_data.node_groups[nodes_tree_name];
The crash was introduced in:
cb7cf523e5c000609f32a382e2c0fcc57f635a42
Conflicts:
intern/cycles/SConscript
intern/cycles/blender/addon/__init__.py
intern/cycles/blender/addon/properties.py
intern/cycles/blender/blender_camera.cpp
intern/cycles/kernel/kernel_types.h
intern/cycles/kernel/svm/svm.h
intern/cycles/kernel/svm/svm_types.h
intern/cycles/render/camera.cpp
intern/cycles/render/camera.h
Diffstat (limited to 'source/blender/editors/space_file/filelist.c')
-rw-r--r-- | source/blender/editors/space_file/filelist.c | 2919 |
1 files changed, 2110 insertions, 809 deletions
diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 871abbda48a..0d1aff09e9c 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -35,19 +35,29 @@ #include <stdlib.h> #include <math.h> #include <string.h> +#include <sys/stat.h> +#include <time.h> #ifndef WIN32 -#include <unistd.h> +# include <unistd.h> #else -#include <io.h> -#include <direct.h> +# include <io.h> +# include <direct.h> #endif #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" -#include "BLI_fileops_types.h" #ifdef WIN32 # include "BLI_winstuff.h" @@ -57,15 +67,15 @@ #include "BKE_global.h" #include "BKE_library.h" #include "BKE_icons.h" +#include "BKE_idcode.h" #include "BKE_main.h" -#include "BKE_report.h" #include "BLO_readfile.h" -#include "BKE_idcode.h" #include "DNA_space_types.h" -#include "ED_fileselect.h" #include "ED_datafiles.h" +#include "ED_fileselect.h" +#include "ED_screen.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -77,315 +87,687 @@ #include "WM_types.h" #include "UI_resources.h" +#include "UI_interface_icons.h" + +#include "atomic_ops.h" #include "filelist.h" -struct FileList; -typedef struct FileImage { - struct FileImage *next, *prev; +/* ----------------- FOLDERLIST (previous/next) -------------- */ + +typedef struct FolderList { + struct FolderList *next, *prev; + char *foldername; +} FolderList; + +ListBase *folderlist_new(void) +{ + ListBase *p = MEM_callocN(sizeof(*p), __func__); + return p; +} + +void folderlist_popdir(struct ListBase *folderlist, char *dir) +{ + const char *prev_dir; + struct FolderList *folder; + folder = folderlist->last; + + if (folder) { + /* remove the current directory */ + MEM_freeN(folder->foldername); + BLI_freelinkN(folderlist, folder); + + folder = folderlist->last; + if (folder) { + prev_dir = folder->foldername; + BLI_strncpy(dir, prev_dir, FILE_MAXDIR); + } + } + /* delete the folder next or use setdir directly before PREVIOUS OP */ +} + +void folderlist_pushdir(ListBase *folderlist, const char *dir) +{ + struct FolderList *folder, *previous_folder; + previous_folder = folderlist->last; + + /* check if already exists */ + if (previous_folder && previous_folder->foldername) { + if (BLI_path_cmp(previous_folder->foldername, dir) == 0) { + return; + } + } + + /* create next folder element */ + folder = MEM_mallocN(sizeof(*folder), __func__); + folder->foldername = BLI_strdup(dir); + + /* add it to the end of the list */ + BLI_addtail(folderlist, folder); +} + +const char *folderlist_peeklastdir(ListBase *folderlist) +{ + struct FolderList *folder; + + if (!folderlist->last) + return NULL; + + folder = folderlist->last; + return folder->foldername; +} + +int folderlist_clear_next(struct SpaceFile *sfile) +{ + struct FolderList *folder; + + /* if there is no folder_next there is nothing we can clear */ + if (!sfile->folders_next) + return 0; + + /* if previous_folder, next_folder or refresh_folder operators are executed it doesn't clear folder_next */ + folder = sfile->folders_prev->last; + if ((!folder) || (BLI_path_cmp(folder->foldername, sfile->params->dir) == 0)) + return 0; + + /* eventually clear flist->folders_next */ + return 1; +} + +/* not listbase itself */ +void folderlist_free(ListBase *folderlist) +{ + if (folderlist) { + FolderList *folder; + for (folder = folderlist->first; folder; folder = folder->next) + MEM_freeN(folder->foldername); + BLI_freelistN(folderlist); + } +} + +ListBase *folderlist_duplicate(ListBase *folderlist) +{ + + if (folderlist) { + ListBase *folderlistn = MEM_callocN(sizeof(*folderlistn), __func__); + FolderList *folder; + + BLI_duplicatelist(folderlistn, folderlist); + + for (folder = folderlistn->first; folder; folder = folder->next) { + folder->foldername = MEM_dupallocN(folder->foldername); + } + return folderlistn; + } + return NULL; +} + + +/* ------------------FILELIST------------------------ */ + +typedef struct FileListInternEntry { + struct FileListInternEntry *next, *prev; + + 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; + + /* This one gathers all entries from both block and misc caches. Used for easy bulk-freing. */ + ListBase cached_entries; + + /* Block cache: all entries between start and end index. used for part of the list on 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. */ + TaskScheduler *previews_scheduler; + 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 FileListEntryPreview { char path[FILE_MAX]; unsigned int flags; int index; - short done; ImBuf *img; -} FileImage; +} FileListEntryPreview; -typedef struct ThumbnailJob { - ListBase loadimages; - const short *stop; - const short *do_update; - struct FileList *filelist; - ReportList reports; -} ThumbnailJob; +typedef struct FileListFilter { + 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 *fidx; - int numfiles; - int numfiltered; - char dir[FILE_MAX]; + FileDirEntryArr filelist; + short prv_w; short prv_h; - short hide_dot; - unsigned int filter; - char filter_glob[64]; - short changed; + + short flags; + + short sort; + + FileListFilter filter_data; + + struct FileListIntern filelist_intern; + + struct FileListEntryCache filelist_cache; + + /* We need to keep those info outside of actual filelist items, because those are no more persistent + * (only generated on demand, and freed as soon as possible). + * Persistent part (mere list of paths + stat info) is kept as small as possible, and filebrowser-agnostic. + */ + GHash *selection_state; + + short max_recursion; + short recursion_level; struct BlendHandle *libfiledata; - short hide_parent; - void (*readf)(struct FileList *); - bool (*filterf)(struct direntry *file, const char *dir, unsigned int filter, short hide_dot); + /* 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; -typedef struct FolderList { - struct FolderList *next, *prev; - char *foldername; -} FolderList; +/* 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 -#define SPECIAL_IMG_FOLDER 0 -#define SPECIAL_IMG_PARENT 1 -#define SPECIAL_IMG_REFRESH 2 -#define SPECIAL_IMG_BLENDFILE 3 -#define SPECIAL_IMG_SOUNDFILE 4 -#define SPECIAL_IMG_MOVIEFILE 5 -#define SPECIAL_IMG_PYTHONFILE 6 -#define SPECIAL_IMG_TEXTFILE 7 -#define SPECIAL_IMG_FONTFILE 8 -#define SPECIAL_IMG_UNKNOWNFILE 9 -#define SPECIAL_IMG_LOADING 10 -#define SPECIAL_IMG_BACKUP 11 -#define SPECIAL_IMG_MAX SPECIAL_IMG_BACKUP + 1 +enum { + SPECIAL_IMG_FOLDER = 0, + SPECIAL_IMG_PARENT = 1, + SPECIAL_IMG_REFRESH = 2, + SPECIAL_IMG_BLENDFILE = 3, + SPECIAL_IMG_SOUNDFILE = 4, + SPECIAL_IMG_MOVIEFILE = 5, + SPECIAL_IMG_PYTHONFILE = 6, + SPECIAL_IMG_TEXTFILE = 7, + SPECIAL_IMG_FONTFILE = 8, + SPECIAL_IMG_UNKNOWNFILE = 9, + SPECIAL_IMG_LOADING = 10, + SPECIAL_IMG_BACKUP = 11, + SPECIAL_IMG_MAX +}; static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX]; -/* ******************* SORT ******************* */ +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 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 & (BLENDERFILE | BLENDERFILE_BACKUP)); - - return false; -} +/* 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 int compare_name(const void *a1, const void *a2) -{ - const struct direntry *entry1 = a1, *entry2 = a2; +static void filelist_filter_clear(FileList *filelist); +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size); +/* ********** Sort helpers ********** */ + +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); - } - else { - if (compare_is_directory(entry2)) return (1); - } - if (S_ISREG(entry1->type)) { - if (S_ISREG(entry2->type) == 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 { + 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 (strcmp(entry1->relname, ".") == 0) return (-1); - if (strcmp(entry2->relname, ".") == 0) return (1); - if (strcmp(entry1->relname, "..") == 0) return (-1); - if (strcmp(entry2->relname, "..") == 0) 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 (BLI_natstrcmp(entry1->relname, entry2->relname)); + return 0; } -static int compare_date(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; - - /* type is equal to stat.st_mode */ + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + int ret; - if (compare_is_directory(entry1)) { - if (compare_is_directory(entry2) == 0) return (-1); + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; } - else { - if (compare_is_directory(entry2)) return (1); - } - if (S_ISREG(entry1->type)) { - if (S_ISREG(entry2->type) == 0) return (-1); - } - else { - if (S_ISREG(entry2->type)) 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 (strcmp(entry1->relname, ".") == 0) return (-1); - if (strcmp(entry2->relname, ".") == 0) return (1); - if (strcmp(entry1->relname, "..") == 0) return (-1); - if (strcmp(entry2->relname, "..") == 0) return (1); - - if (entry1->s.st_mtime < entry2->s.st_mtime) return 1; - if (entry1->s.st_mtime > entry2->s.st_mtime) return -1; - - else return BLI_natstrcmp(entry1->relname, entry2->relname); + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } -static int compare_size(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; - - /* type is equal to stat.st_mode */ + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + int64_t time1, time2; + int ret; - if (compare_is_directory(entry1)) { - if (compare_is_directory(entry2) == 0) return (-1); - } - else { - if (compare_is_directory(entry2)) return (1); - } - if (S_ISREG(entry1->type)) { - if (S_ISREG(entry2->type) == 0) return (-1); - } - else { - if (S_ISREG(entry2->type)) return (1); + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; } - 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 (strcmp(entry1->relname, ".") == 0) return (-1); - if (strcmp(entry2->relname, ".") == 0) return (1); - if (strcmp(entry1->relname, "..") == 0) return (-1); - if (strcmp(entry2->relname, "..") == 0) return (1); - if (entry1->s.st_size < entry2->s.st_size) return 1; - if (entry1->s.st_size > entry2->s.st_size) return -1; - else return BLI_natstrcmp(entry1->relname, entry2->relname); + 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(name1, name2); } -static int compare_extension(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 char *sufix1, *sufix2; - const char *nil = ""; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + uint64_t size1, size2; + int 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 ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; + } + + size1 = entry1->st.st_size; + size2 = entry2->st.st_size; + if (size1 < size2) return 1; + if (size1 > size2) return -1; - /* type is equal to stat.st_mode */ + name1 = entry1->name; + name2 = entry2->name; - if (compare_is_directory(entry1)) { - if (compare_is_directory(entry2) == 0) return (-1); + return BLI_natstrcmp(name1, name2); +} + +static int compare_extension(void *UNUSED(user_data), const void *a1, const void *a2) +{ + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + int ret; + + if ((ret = compare_direntry_generic(entry1, entry2))) { + return ret; + } + + 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 { - if (compare_is_directory(entry2)) return (1); + const char *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 = ""; + + if ((ret = BLI_strcasecmp(sufix1, sufix2))) { + return ret; + } } - if (S_ISREG(entry1->type)) { - if (S_ISREG(entry2->type) == 0) return (-1); + + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); +} + +void filelist_sort(struct FileList *filelist) +{ + if ((filelist->flags & FL_NEED_SORTING) && (filelist->sort != FILE_SORT_NONE)) { + switch (filelist->sort) { + case FILE_SORT_ALPHA: + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_name, NULL); + break; + case FILE_SORT_TIME: + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_date, NULL); + break; + case FILE_SORT_SIZE: + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_size, NULL); + break; + case FILE_SORT_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); + } + + filelist_filter_clear(filelist); + filelist->flags &= ~FL_NEED_SORTING; } - else { - if (S_ISREG(entry2->type)) return (1); +} + +void filelist_setsorting(struct FileList *filelist, const short sort) +{ + if (filelist->sort != sort) { + filelist->sort = sort; + filelist->flags |= FL_NEED_SORTING; } - 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 (strcmp(entry1->relname, ".") == 0) return (-1); - if (strcmp(entry2->relname, ".") == 0) return (1); - if (strcmp(entry1->relname, "..") == 0) return (-1); - if (strcmp(entry2->relname, "..") == 0) return (1); - - return (BLI_strcasecmp(sufix1, sufix2)); } -static bool is_hidden_file(const char *filename, short hide_dot) +/* ********** Filter helpers ********** */ + +static bool is_hidden_file(const char *filename, FileListFilter *filter) { + char *sep = (char *)BLI_last_slash(filename); bool is_hidden = false; - if (hide_dot) { - if (filename[0] == '.' && filename[1] != '.' && filename[1] != 0) { - is_hidden = 1; /* ignore .file */ - } - else if (((filename[0] == '.') && (filename[1] == 0))) { - is_hidden = 1; /* ignore . */ + if (filter->flags & FLF_HIDE_DOT) { + if (filename[0] == '.' && filename[1] != '.' && filename[1] != '\0') { + is_hidden = true; /* ignore .file */ } else { int len = strlen(filename); if ((len > 0) && (filename[len - 1] == '~')) { - is_hidden = 1; /* ignore file~ */ + is_hidden = true; /* ignore file~ */ } } } - else { - if (((filename[0] == '.') && (filename[1] == 0))) { - is_hidden = 1; /* ignore . */ + if (!is_hidden && (filter->flags & FLF_HIDE_PARENT)) { + if (filename[0] == '.' && filename[1] == '.' && filename[2] == '\0') { + is_hidden = true; /* ignore .. */ + } + } + 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(dir), unsigned int filter, short hide_dot) +static bool is_filtered_file(FileListInternEntry *file, const char *UNUSED(root), FileListFilter *filter) { - bool is_filtered = false; - if (filter) { - if (file->flags & filter) { - is_filtered = 1; + bool 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; + } + } } - else if (file->type & S_IFDIR) { - if (filter & FOLDERFILE) { - is_filtered = 1; + else { + if (!(file->typeflag & filter->filter)) { + is_filtered = false; + } + } + if (is_filtered && (filter->filter_search[0] != '\0')) { + if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { + is_filtered = false; } } } - else { - is_filtered = 1; - } - return is_filtered && !is_hidden_file(file->relname, hide_dot); + + return is_filtered; } -static bool is_filtered_lib(struct direntry *file, const char *dir, unsigned int filter, short hide_dot) +static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) { - bool is_filtered = false; - char tdir[FILE_MAX], tgroup[BLO_GROUP_MAX]; - if (BLO_is_a_library(dir, tdir, tgroup)) { - is_filtered = !is_hidden_file(file->relname, hide_dot); + 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(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->relpath, FNM_CASEFOLD) != 0) { + is_filtered = false; + } + } + } } else { - is_filtered = is_filtered_file(file, dir, filter, hide_dot); + is_filtered = is_filtered_file(file, root, filter); } + return is_filtered; } -static bool is_filtered_main(struct direntry *file, const char *UNUSED(dir), unsigned int UNUSED(filter), short hide_dot) +static bool is_filtered_main(FileListInternEntry *file, const char *UNUSED(dir), FileListFilter *filter) +{ + return !is_hidden_file(file->relpath, filter); +} + +static void filelist_filter_clear(FileList *filelist) { - return !is_hidden_file(file->relname, hide_dot); + filelist->flags |= FL_NEED_FILTERING; } void filelist_filter(FileList *filelist) { int num_filtered = 0; - int i, j; + const int num_files = filelist->filelist.nbr_entries; + FileListInternEntry **filtered_tmp, *file; - if (!filelist->filelist) + if (filelist->filelist.nbr_entries == 0) { return; + } - /* How many files are left after filter ? */ - for (i = 0; i < filelist->numfiles; ++i) { - struct direntry *file = &filelist->filelist[i]; - if (filelist->filterf(file, filelist->dir, filelist->filter, filelist->hide_dot)) { - num_filtered++; - } + if (!(filelist->flags & FL_NEED_FILTERING)) { + /* Assume it has already been filtered, nothing else to do! */ + return; } - - if (filelist->fidx) { - MEM_freeN(filelist->fidx); - filelist->fidx = NULL; + + 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; + } } - filelist->fidx = (int *)MEM_callocN(num_filtered * sizeof(int), "filteridx"); - filelist->numfiltered = num_filtered; - for (i = 0, j = 0; i < filelist->numfiles; ++i) { - struct direntry *file = &filelist->filelist[i]; - if (filelist->filterf(file, filelist->dir, filelist->filter, filelist->hide_dot)) { - filelist->fidx[j++] = i; + filtered_tmp = MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__); + + /* 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; } } + + 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(filtered_tmp); } +void filelist_setfilter_options(FileList *filelist, const bool hide_dot, const bool hide_parent, + const unsigned int filter, const unsigned int filter_id, + const char *filter_glob, const char *filter_search) +{ + 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; + } + + if (update) { + /* And now, free filtered data so that we know we have to filter again. */ + filelist_filter_clear(filelist); + } +} + +/* ********** Icon/image helpers ********** */ + void filelist_init_icons(void) { short x, y, k; @@ -428,136 +810,517 @@ void filelist_free_icons(void) } } -/* -----------------FOLDERLIST (previous/next) -------------- */ -ListBase *folderlist_new(void) +void filelist_imgsize(struct FileList *filelist, short w, short h) { - ListBase *p = MEM_callocN(sizeof(ListBase), "folderlist"); - return p; + filelist->prv_w = w; + filelist->prv_h = h; } -void folderlist_popdir(struct ListBase *folderlist, char *dir) +static FileDirEntry *filelist_geticon_get_file(struct FileList *filelist, const int index) { - const char *prev_dir; - struct FolderList *folder; - folder = folderlist->last; + BLI_assert(G.background == false); - if (folder) { - /* remove the current directory */ - MEM_freeN(folder->foldername); - BLI_freelinkN(folderlist, folder); + return filelist_file(filelist, index); +} - folder = folderlist->last; - if (folder) { - prev_dir = folder->foldername; - BLI_strncpy(dir, prev_dir, FILE_MAXDIR); +ImBuf *filelist_getimage(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return file->image; +} + +static ImBuf *filelist_geticon_image_ex(const unsigned int typeflag, const char *relpath) +{ + ImBuf *ibuf = NULL; + + if (typeflag & FILE_TYPE_DIR) { + if (FILENAME_IS_PARENT(relpath)) { + ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT]; + } + else if (FILENAME_IS_CURRENT(relpath)) { + ibuf = gSpecialFileImages[SPECIAL_IMG_REFRESH]; + } + else { + ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER]; } } - /* delete the folder next or use setdir directly before PREVIOUS OP */ + else if (typeflag & FILE_TYPE_BLENDER) { + ibuf = gSpecialFileImages[SPECIAL_IMG_BLENDFILE]; + } + else if (typeflag & FILE_TYPE_BLENDERLIB) { + ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + } + else if (typeflag & (FILE_TYPE_MOVIE)) { + ibuf = gSpecialFileImages[SPECIAL_IMG_MOVIEFILE]; + } + else if (typeflag & FILE_TYPE_SOUND) { + ibuf = gSpecialFileImages[SPECIAL_IMG_SOUNDFILE]; + } + else if (typeflag & FILE_TYPE_PYSCRIPT) { + ibuf = gSpecialFileImages[SPECIAL_IMG_PYTHONFILE]; + } + else if (typeflag & FILE_TYPE_FTFONT) { + ibuf = gSpecialFileImages[SPECIAL_IMG_FONTFILE]; + } + else if (typeflag & FILE_TYPE_TEXT) { + ibuf = gSpecialFileImages[SPECIAL_IMG_TEXTFILE]; + } + else if (typeflag & FILE_TYPE_IMAGE) { + ibuf = gSpecialFileImages[SPECIAL_IMG_LOADING]; + } + else if (typeflag & FILE_TYPE_BLENDER_BACKUP) { + ibuf = gSpecialFileImages[SPECIAL_IMG_BACKUP]; + } + else { + ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + } + + return ibuf; } -void folderlist_pushdir(ListBase *folderlist, const char *dir) +ImBuf *filelist_geticon_image(struct FileList *filelist, const int index) { - struct FolderList *folder, *previous_folder; - previous_folder = folderlist->last; + FileDirEntry *file = filelist_geticon_get_file(filelist, index); - /* check if already exists */ - if (previous_folder && previous_folder->foldername) { - if (BLI_path_cmp(previous_folder->foldername, dir) == 0) { - return; + 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) +{ + 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; } } - /* create next folder element */ - folder = (FolderList *)MEM_mallocN(sizeof(FolderList), "FolderList"); - folder->foldername = BLI_strdup(dir); + 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 is_main ? ICON_FILE_BLANK : ICON_NONE; +} - /* add it to the end of the list */ - BLI_addtail(folderlist, folder); +int filelist_geticon(struct FileList *filelist, const int index, const bool is_main) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return filelist_geticon_ex(file->typeflag, file->blentype, file->relpath, is_main, false); } -const char *folderlist_peeklastdir(ListBase *folderlist) +/* ********** Main ********** */ + +static void filelist_checkdir_dir(struct FileList *UNUSED(filelist), char *r_dir) { - struct FolderList *folder; + BLI_make_exist(r_dir); +} - if (!folderlist->last) - return NULL; +static void filelist_checkdir_lib(struct FileList *UNUSED(filelist), char *r_dir) +{ + 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); + } +} - folder = folderlist->last; - return folder->foldername; +static void filelist_checkdir_main(struct FileList *filelist, char *r_dir) +{ + /* TODO */ + filelist_checkdir_lib(filelist, r_dir); } -int folderlist_clear_next(struct SpaceFile *sfile) +static void filelist_entry_clear(FileDirEntry *entry) { - struct FolderList *folder; + 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 there is no folder_next there is nothing we can clear */ - if (!sfile->folders_next) - return 0; + if (!BLI_listbase_is_empty(&entry->variants)) { + FileDirEntryVariant *var; - /* if previous_folder, next_folder or refresh_folder operators are executed it doesn't clear folder_next */ - folder = sfile->folders_prev->last; - if ((!folder) || (BLI_path_cmp(folder->foldername, sfile->params->dir) == 0)) - return 0; + for (var = entry->variants.first; var; var = var->next) { + if (var->name) { + MEM_freeN(var->name); + } + if (var->description) { + MEM_freeN(var->description); + } - /* eventually clear flist->folders_next */ - return 1; + 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); + } } -/* not listbase itself */ -void folderlist_free(ListBase *folderlist) +static void filelist_entry_free(FileDirEntry *entry) { - if (folderlist) { - FolderList *folder; - for (folder = folderlist->first; folder; folder = folder->next) - MEM_freeN(folder->foldername); - BLI_freelistN(folderlist); + filelist_entry_clear(entry); + MEM_freeN(entry); +} + +static void filelist_direntryarr_free(FileDirEntryArr *array) +{ +#if 0 + FileDirEntry *entry, *entry_next; + + for (entry = array->entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); } + BLI_listbase_clear(&array->entries); +#else + BLI_assert(BLI_listbase_is_empty(&array->entries)); +#endif + array->nbr_entries = 0; + array->nbr_entries_filtered = -1; + array->entry_idx_start = -1; + array->entry_idx_end = -1; } -ListBase *folderlist_duplicate(ListBase *folderlist) +static void filelist_intern_entry_free(FileListInternEntry *entry) { - - if (folderlist) { - ListBase *folderlistn = MEM_callocN(sizeof(ListBase), "copy folderlist"); - FolderList *folder; - - BLI_duplicatelist(folderlistn, folderlist); - - for (folder = folderlistn->first; folder; folder = folder->next) { - folder->foldername = MEM_dupallocN(folder->foldername); + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + if (entry->name) { + MEM_freeN(entry->name); + } + MEM_freeN(entry); +} + +static void filelist_intern_free(FileListIntern *filelist_intern) +{ + FileListInternEntry *entry, *entry_next; + + for (entry = filelist_intern->entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_intern_entry_free(entry); + } + BLI_listbase_clear(&filelist_intern->entries); + + MEM_SAFE_FREE(filelist_intern->filtered); +} + +static void filelist_cache_previewf(TaskPool *pool, void *taskdata, int UNUSED(threadid)) +{ + FileListEntryCache *cache = taskdata; + FileListEntryPreview *preview; + +// printf("%s: Start (%d)...\n", __func__, threadid); + + /* 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; } - return folderlistn; + 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 NULL; + +// printf("%s: End (%d)...\n", __func__, threadid); } +static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) +{ + if (!cache->previews_pool) { + TaskScheduler *scheduler; + TaskPool *pool; + int num_tasks = max_ii(1, (BLI_system_thread_count() / 2) + 1); + + scheduler = cache->previews_scheduler = BLI_task_scheduler_create(num_tasks + 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(); + } + cache->previews_timestamp = 0.0; +} -static void filelist_read_main(struct FileList *filelist); -static void filelist_read_library(struct FileList *filelist); -static void filelist_read_dir(struct FileList *filelist); +static void filelist_cache_previews_clear(FileListEntryCache *cache) +{ + 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--; + } + } +// 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); + BLI_task_scheduler_free(cache->previews_scheduler); + cache->previews_scheduler = NULL; + cache->previews_pool = NULL; + cache->previews_todo = NULL; + cache->previews_done = NULL; + + IMB_thumb_locks_release(); + } + 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) +{ + BLI_listbase_clear(&cache->cached_entries); + + 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) +{ + FileDirEntry *entry, *entry_next; + + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_free(cache, true); + + 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); + + for (entry = cache->cached_entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); + } + BLI_listbase_clear(&cache->cached_entries); +} + +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size) +{ + FileDirEntry *entry, *entry_next; + + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_clear(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; + + for (entry = cache->cached_entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); + } + BLI_listbase_clear(&cache->cached_entries); +} -/* ------------------FILELIST------------------------ */ FileList *filelist_new(short type) { - FileList *p = MEM_callocN(sizeof(FileList), "filelist"); + 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->readf = filelist_read_main; + p->checkdirf = filelist_checkdir_main; + p->read_jobf = filelist_readjob_main; p->filterf = is_filtered_main; break; case FILE_LOADLIB: - p->readf = filelist_read_library; + p->checkdirf = filelist_checkdir_lib; + p->read_jobf = filelist_readjob_lib; p->filterf = is_filtered_lib; break; default: - p->readf = filelist_read_dir; + 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) { @@ -566,18 +1329,18 @@ void filelist_free(struct FileList *filelist) return; } - if (filelist->fidx) { - MEM_freeN(filelist->fidx); - filelist->fidx = NULL; + 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; } - BLI_free_filelist(filelist->filelist, filelist->numfiles); - filelist->numfiles = 0; - filelist->filelist = NULL; - filelist->filter = 0; - filelist->filter_glob[0] = '\0'; - filelist->numfiltered = 0; - filelist->hide_dot = 0; + 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) @@ -592,155 +1355,575 @@ BlendHandle *filelist_lib(struct FileList *filelist) return filelist->libfiledata; } -int filelist_numfiles(struct FileList *filelist) +static const char *fileentry_uiname(const char *root, const char *relpath, const int typeflag, char *buff) { - return filelist->numfiltered; + 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->dir; + return filelist->filelist.root; } -void filelist_setdir(struct FileList *filelist, const char *dir) +/** + * May modify in place given r_dir, which is expected to be FILE_MAX_LIBEXTRA length. + */ +void filelist_setdir(struct FileList *filelist, char *r_dir) { - BLI_strncpy(filelist->dir, dir, sizeof(filelist->dir)); + BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA); + + BLI_cleanup_dir(G.main->name, 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_imgsize(struct FileList *filelist, short w, short h) +void filelist_setrecursion(struct FileList *filelist, const int recursion_level) { - filelist->prv_w = w; - filelist->prv_h = h; + if (filelist->max_recursion != recursion_level) { + filelist->max_recursion = recursion_level; + filelist->flags |= FL_FORCE_RESET; + } } -short filelist_changed(struct FileList *filelist) +bool filelist_force_reset(struct FileList *filelist) { - return filelist->changed; + return (filelist->flags & FL_FORCE_RESET) != 0; } -ImBuf *filelist_getimage(struct FileList *filelist, int index) +bool filelist_is_ready(struct FileList *filelist) { - ImBuf *ibuf = NULL; - int fidx = 0; + return (filelist->flags & FL_IS_READY) != 0; +} - BLI_assert(G.background == false); +bool filelist_pending(struct FileList *filelist) +{ + return (filelist->flags & FL_IS_PENDING) != 0; +} - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; +/** + * 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); } - fidx = filelist->fidx[index]; - ibuf = filelist->filelist[fidx].image; - return ibuf; + return filelist->filelist.nbr_entries_filtered;; } -ImBuf *filelist_geticon(struct FileList *filelist, int index) +static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index) { - ImBuf *ibuf = NULL; - struct direntry *file = NULL; - int fidx = 0; + FileListInternEntry *entry = filelist->filelist_intern.filtered[index]; + FileListEntryCache *cache = &filelist->filelist_cache; + FileDirEntry *ret; + FileDirEntryRevision *rev; - BLI_assert(G.background == false); + ret = MEM_callocN(sizeof(*ret), __func__); + rev = MEM_callocN(sizeof(*rev), __func__); - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; + 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(&cache->cached_entries, ret); + return ret; +} + +static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry) +{ + BLI_remlink(&filelist->filelist_cache.cached_entries, entry); + filelist_entry_free(entry); +} + +static FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request) +{ + FileDirEntry *ret = NULL, *old; + FileListEntryCache *cache = &filelist->filelist_cache; + const size_t cache_size = cache->size; + int old_index; + + if ((index < 0) || (index >= filelist->filelist.nbr_entries_filtered)) { + return ret; } - fidx = filelist->fidx[index]; - file = &filelist->filelist[fidx]; - if (file->type & S_IFDIR) { - if (strcmp(filelist->filelist[fidx].relname, "..") == 0) { - ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT]; - } - else if (strcmp(filelist->filelist[fidx].relname, ".") == 0) { - ibuf = gSpecialFileImages[SPECIAL_IMG_REFRESH]; - } - else { - ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER]; - } + + 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]; } - else { - ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + + if ((ret = BLI_ghash_lookup(cache->misc_entries, SET_INT_IN_POINTER(index)))) { + return ret; } - if (file->flags & BLENDERFILE) { - ibuf = gSpecialFileImages[SPECIAL_IMG_BLENDFILE]; + if (!use_request) { + return NULL; } - else if ((file->flags & MOVIEFILE) || (file->flags & MOVIEFILE_ICON)) { - ibuf = gSpecialFileImages[SPECIAL_IMG_MOVIEFILE]; + +// 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); } - else if (file->flags & SOUNDFILE) { - ibuf = gSpecialFileImages[SPECIAL_IMG_SOUNDFILE]; + 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); } - else if (file->flags & PYSCRIPTFILE) { - ibuf = gSpecialFileImages[SPECIAL_IMG_PYTHONFILE]; +#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; } - else if (file->flags & FTFONTFILE) { - ibuf = gSpecialFileImages[SPECIAL_IMG_FONTFILE]; + + /* 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; + } } - else if (file->flags & TEXTFILE) { - ibuf = gSpecialFileImages[SPECIAL_IMG_TEXTFILE]; + + return -1; +} + +FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]) +{ + if (filelist->filelist.nbr_entries_filtered < 0) { + return NULL; } - else if (file->flags & IMAGEFILE) { - ibuf = gSpecialFileImages[SPECIAL_IMG_LOADING]; + + if (filelist->filelist_cache.uuids) { + FileDirEntry *entry = BLI_ghash_lookup(filelist->filelist_cache.uuids, uuid); + if (entry) { + return entry; + } } - else if (file->flags & BLENDERFILE_BACKUP) { - ibuf = gSpecialFileImages[SPECIAL_IMG_BACKUP]; + + { + 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 ibuf; + return NULL; } -struct direntry *filelist_file(struct FileList *filelist, int index) +void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size) { - int fidx = 0; - - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; + /* 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; } - fidx = filelist->fidx[index]; - return &filelist->filelist[fidx]; + if (size != filelist->filelist_cache.size) { + filelist_cache_clear(&filelist->filelist_cache, size); + } } -int filelist_find(struct FileList *filelist, const char *filename) +/* 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) { - int index = -1; + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i, idx; + + for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) { + FileDirEntry *entry; + + /* That entry might have already been requested and stored in misc cache... */ + if ((entry = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(idx), NULL)) == NULL) { + entry = filelist_file_create_entry(filelist, idx); + BLI_ghash_insert(cache->uuids, entry->uuid, entry); + } + cache->block_entries[cursor] = 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; - int fidx = -1; - - if (!filelist->fidx) - return fidx; - - for (i = 0; i < filelist->numfiles; ++i) { - if (strcmp(filelist->filelist[i].relname, filename) == 0) { /* not dealing with user input so don't need BLI_path_cmp */ - index = i; - break; + 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); } } - for (i = 0; i < filelist->numfiltered; ++i) { - if (filelist->fidx[i] == index) { - fidx = i; - break; + 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); + } } } - return fidx; + + cache->block_center_index = index; + +// printf("%s Finished!\n", __func__); + + return true; } -void filelist_hidedot(struct FileList *filelist, short hide) +void filelist_cache_previews_set(FileList *filelist, const bool use_previews) { - filelist->hide_dot = hide; + 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); + } } -void filelist_setfilter(struct FileList *filelist, unsigned int filter) +bool filelist_cache_previews_update(FileList *filelist) { - filelist->filter = filter; + 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); + FileDirEntry *entry; + + /* Paranoid (should never happen currently since we consume this queue from a single thread), but... */ + if (!preview) { + continue; + } + /* entry might have been removed from cache in the mean while, we do not want to cache it again here. */ + 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; } -void filelist_setfilter_types(struct FileList *filelist, const char *filter_glob) +bool filelist_cache_previews_running(FileList *filelist) { - BLI_strncpy(filelist->filter_glob, filter_glob, sizeof(filelist->filter_glob)); + FileListEntryCache *cache = &filelist->filelist_cache; + + return (cache->previews_pool != NULL); } /* would recognize .blend as well */ @@ -772,55 +1955,55 @@ static bool file_is_blend_backup(const char *str) static int path_extension_type(const char *path) { if (BLO_has_bfile_extension(path)) { - return BLENDERFILE; + return FILE_TYPE_BLENDER; } else if (file_is_blend_backup(path)) { - return BLENDERFILE_BACKUP; + return FILE_TYPE_BLENDER_BACKUP; } else if (BLI_testextensie(path, ".app")) { - return APPLICATIONBUNDLE; + return FILE_TYPE_APPLICATIONBUNDLE; } else if (BLI_testextensie(path, ".py")) { - return PYSCRIPTFILE; + return FILE_TYPE_PYSCRIPT; } else if (BLI_testextensie_n(path, ".txt", ".glsl", ".osl", ".data", NULL)) { - return TEXTFILE; + return FILE_TYPE_TEXT; } else if (BLI_testextensie_n(path, ".ttf", ".ttc", ".pfb", ".otf", ".otc", NULL)) { - return FTFONTFILE; + return FILE_TYPE_FTFONT; } else if (BLI_testextensie(path, ".btx")) { - return BTXFILE; + return FILE_TYPE_BTX; } else if (BLI_testextensie(path, ".dae")) { - return COLLADAFILE; + return FILE_TYPE_COLLADA; } else if (BLI_testextensie_array(path, imb_ext_image) || (G.have_quicktime && BLI_testextensie_array(path, imb_ext_image_qt))) { - return IMAGEFILE; + return FILE_TYPE_IMAGE; } else if (BLI_testextensie(path, ".ogg")) { if (IMB_isanim(path)) { - return MOVIEFILE; + return FILE_TYPE_MOVIE; } else { - return SOUNDFILE; + return FILE_TYPE_SOUND; } } else if (BLI_testextensie_array(path, imb_ext_movie)) { - return MOVIEFILE; + return FILE_TYPE_MOVIE; } else if (BLI_testextensie_array(path, imb_ext_audio)) { - return SOUNDFILE; + return FILE_TYPE_SOUND; } 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); } @@ -828,213 +2011,146 @@ int ED_file_extension_icon(const char *path) { int type = path_extension_type(path); - if (type == BLENDERFILE) - return ICON_FILE_BLEND; - else if (type == BLENDERFILE_BACKUP) - return ICON_FILE_BACKUP; - else if (type == IMAGEFILE) - return ICON_FILE_IMAGE; - else if (type == MOVIEFILE) - return ICON_FILE_MOVIE; - else if (type == PYSCRIPTFILE) - return ICON_FILE_SCRIPT; - else if (type == SOUNDFILE) - return ICON_FILE_SOUND; - else if (type == FTFONTFILE) - return ICON_FILE_FONT; - else if (type == BTXFILE) - return ICON_FILE_BLANK; - else if (type == COLLADAFILE) - return ICON_FILE_BLANK; - else if (type == TEXTFILE) - 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++) { - file->type = file->s.st_mode; /* restore the mess below */ -#ifndef __APPLE__ - /* Don't check extensions for directories, allow in OSX cause bundles have extensions*/ - if (file->type & S_IFDIR) { - continue; - } -#endif - file->flags = file_extension_type(filelist->dir, file->relname); - - if (filelist->filter_glob[0] && - BLI_testextensie_glob(file->relname, filelist->filter_glob)) - { - file->flags = OPERATORFILE; - } - + 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_cleanup_dir(G.main->name, filelist->dir); - filelist->numfiles = BLI_dir_contents(filelist->dir, &(filelist->filelist)); - - filelist_setfiletypes(filelist); - filelist_filter(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; - BLI_make_exist(filelist->dir); - 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); -} - -int filelist_empty(struct FileList *filelist) -{ - return filelist->filelist == NULL; + return entry_flag; } -void filelist_parent(struct FileList *filelist) +void filelist_entry_select_index_set(FileList *filelist, const int index, FileSelType select, unsigned int flag, FileCheckType check) { - BLI_parent_dir(filelist->dir); - BLI_make_exist(filelist->dir); - filelist_readdir(filelist); -} + 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 & SELECTED_FILE); - case CHECK_FILES: - return S_ISREG(file->type) && (file->selflag & SELECTED_FILE); - case CHECK_ALL: - default: - return (file->selflag & SELECTED_FILE) != 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; } -void filelist_sort(struct FileList *filelist, short sort) +unsigned int filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check) { - switch (sort) { - case FILE_SORT_ALPHA: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_name); - break; - case FILE_SORT_TIME: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_date); - break; - case FILE_SORT_SIZE: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_size); - break; - case FILE_SORT_EXTENSION: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_extension); - break; + FileDirEntry *entry = filelist_file(filelist, index); + + if (entry) { + return filelist_entry_select_get(filelist, entry, check); } - filelist_filter(filelist); + return 0; } - -bool filelist_islibrary(struct FileList *filelist, char *dir, char *group) +bool filelist_islibrary(struct FileList *filelist, char *dir, char **group) { - return BLO_is_a_library(filelist->dir, dir, group); + return BLO_library_path_explode(filelist->filelist.root, dir, group, NULL); } static int groupname_to_code(const char *group) { char buf[BLO_GROUP_MAX]; char *lslash; - + + BLI_assert(group); + BLI_strncpy(buf, group, sizeof(buf)); lslash = (char *)BLI_last_slash(buf); if (lslash) @@ -1042,371 +2158,556 @@ static int groupname_to_code(const char *group) return buf[0] ? BKE_idcode_from_name(buf) : 0; } - -void filelist_from_library(struct FileList *filelist) + +static unsigned int groupname_to_filter_id(const char *group) { - LinkNode *l, *names, *previews; - struct ImBuf *ima; - int ok, i, nprevs, nnames, idcode; - char filename[FILE_MAX]; - char dir[FILE_MAX], group[BLO_GROUP_MAX]; - + int id_code = groupname_to_code(group); + + return BKE_idcode_to_idfilter(id_code); +} + +/** + * From here, we are in 'Job Context', i.e. have to be careful about sharing stuff between background working thread + * and main one (used by UI among other things). + */ +typedef struct TodoDir { + int level; + char *dir; +} TodoDir; + +static int filelist_readjob_list_dir( + const char *root, ListBase *entries, const char *filter_glob, + const bool do_lib, const char *main_name, const bool skip_currpar) +{ + struct direntry *files; + int nbr_files, nbr_entries = 0; + + nbr_files = BLI_filelist_dir_contents(root, &files); + if (files) { + int i = nbr_files; + while (i--) { + FileListInternEntry *entry; + + if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) { + continue; + } + + entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = MEM_dupallocN(files[i].relname); + entry->st = files[i].s; + + /* Set file type. */ + if (S_ISDIR(files[i].s.st_mode)) { + entry->typeflag = FILE_TYPE_DIR; + } + else if (do_lib && BLO_has_bfile_extension(entry->relpath)) { + /* If we are considering .blend files as libs, promote them to directory status. */ + 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) +{ + 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 = groupname_to_code(group); - - /* 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); - nnames = BLI_linklist_length(names); + names = BLO_blendhandle_get_linkable_groups(libfiledata); + nnames = BLI_linklist_count(names); } - filelist->numfiles = nnames + 1; - filelist->filelist = malloc(filelist->numfiles * sizeof(*filelist->filelist)); - memset(filelist->filelist, 0, filelist->numfiles * sizeof(*filelist->filelist)); + BLO_blendhandle_close(libfiledata); - filelist->filelist[0].relname = BLI_strdup(".."); - filelist->filelist[0].type |= S_IFDIR; - - for (i = 0, l = names; i < nnames; i++, l = l->next) { - const char *blockname = l->link; + if (!skip_currpar) { + entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = BLI_strdup(FILENAME_PARENT); + entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); + BLI_addtail(entries, entry); + nbr_entries++; + } + + for (i = 0, ln = names; i < nnames; i++, ln = ln->next) { + const char *blockname = ln->link; - filelist->filelist[i + 1].relname = BLI_strdup(blockname); - if (idcode) { - filelist->filelist[i + 1].type |= S_IFREG; + 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 { - filelist->filelist[i + 1].type |= S_IFDIR; - } - } - - if (previews && (nnames != nprevs)) { - printf("filelist_from_library: error, found %d items, %d previews\n", nnames, nprevs); - } - 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]; - - /* 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 = IMAGEFILE; - } - } + entry->blentype = idcode; } + BLI_addtail(entries, entry); + nbr_entries++; } BLI_linklist_free(names, free); - if (previews) BLI_linklist_free(previews, BKE_previewimg_freefunc); - - filelist_sort(filelist, FILE_SORT_ALPHA); - BLI_strncpy(G.main->name, filename, sizeof(filename)); /* prevent G.main->name to change */ - - filelist->filter = 0; - filelist_filter(filelist); + return nbr_entries; } -void filelist_hideparent(struct FileList *filelist, short hide) -{ - filelist->hide_parent = hide; -} - -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; // XXXXX TODO: add modes to filebrowser + // filelist->type = FILE_MAIN; // XXX TODO: add modes to filebrowser - if (filelist->dir[0] == '/') filelist->dir[0] = 0; - - if (filelist->dir[0]) { - idcode = groupname_to_code(filelist->dir); - if (idcode == 0) filelist->dir[0] = 0; + BLI_assert(filelist->filelist.entries == NULL); + + if (filelist->filelist.root[0] == '/') filelist->filelist.root[0] = '\0'; + + if (filelist->filelist.root[0]) { + idcode = groupname_to_code(filelist->filelist.root); + if (idcode == 0) filelist->filelist.root[0] = '\0'; } - + if (filelist->dir[0] == 0) { - /* make directories */ #ifdef WITH_FREESTYLE - filelist->numfiles = 25; + filelist->filelist.nbr_entries = 24; #else - filelist->numfiles = 24; + filelist->filelist.nbr_entries = 23; #endif - filelist->filelist = (struct direntry *)malloc(filelist->numfiles * sizeof(struct direntry)); - - for (a = 0; a < filelist->numfiles; a++) { - memset(&(filelist->filelist[a]), 0, sizeof(struct direntry)); - filelist->filelist[a].type |= S_IFDIR; + 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[0].relname = BLI_strdup(".."); - filelist->filelist[2].relname = BLI_strdup("Scene"); - filelist->filelist[3].relname = BLI_strdup("Object"); - filelist->filelist[4].relname = BLI_strdup("Mesh"); - filelist->filelist[5].relname = BLI_strdup("Curve"); - filelist->filelist[6].relname = BLI_strdup("Metaball"); - filelist->filelist[7].relname = BLI_strdup("Material"); - filelist->filelist[8].relname = BLI_strdup("Texture"); - filelist->filelist[9].relname = BLI_strdup("Image"); - filelist->filelist[10].relname = BLI_strdup("Ika"); - filelist->filelist[11].relname = BLI_strdup("Wave"); - filelist->filelist[12].relname = BLI_strdup("Lattice"); - filelist->filelist[13].relname = BLI_strdup("Lamp"); - filelist->filelist[14].relname = BLI_strdup("Camera"); - filelist->filelist[15].relname = BLI_strdup("Ipo"); - filelist->filelist[16].relname = BLI_strdup("World"); - filelist->filelist[17].relname = BLI_strdup("Screen"); - filelist->filelist[18].relname = BLI_strdup("VFont"); - filelist->filelist[19].relname = BLI_strdup("Text"); - filelist->filelist[20].relname = BLI_strdup("Armature"); - filelist->filelist[21].relname = BLI_strdup("Action"); - filelist->filelist[22].relname = BLI_strdup("NodeTree"); - filelist->filelist[23].relname = BLI_strdup("Speaker"); + + 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[24].relname = BLI_strdup("FreestyleLineStyle"); + filelist->filelist.entries[23].entry->relpath = BLI_strdup("FreestyleLineStyle"); #endif - filelist_sort(filelist, FILE_SORT_ALPHA); } 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; - - id = lb->first; - filelist->numfiles = 0; - while (id) { - if (!filelist->hide_dot || id->name[2] != '.') { - filelist->numfiles++; + + filelist->filelist.nbr_entries = 0; + for (id = lb->first; id; id = id->next) { + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { + filelist->filelist.nbr_entries++; } - - id = id->next; } - - /* XXXXX TODO: if databrowse F4 or append/link filelist->hide_parent has to be set */ - if (!filelist->hide_parent) filelist->numfiles += 1; - filelist->filelist = filelist->numfiles > 0 ? (struct direntry *)malloc(filelist->numfiles * sizeof(struct direntry)) : NULL; - files = filelist->filelist; - - if (!filelist->hide_parent) { - memset(&(filelist->filelist[0]), 0, sizeof(struct direntry)); - filelist->filelist[0].relname = BLI_strdup(".."); - filelist->filelist[0].type |= S_IFDIR; + /* 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++; + + if (filelist->filelist.nbr_entries > 0) { + filelist_resize(filelist, filelist->filelist.nbr_entries); + } + + 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++; } - - id = lb->first; + totlib = totbl = 0; - - while (id) { + for (id = lb->first; id; id = id->next) { ok = 1; if (ok) { - if (!filelist->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 { - files->relname = MEM_mallocN(FILE_MAX + (MAX_ID_NAME - 2), "filename for lib"); - BLI_snprintf(files->relname, FILE_MAX + (MAX_ID_NAME - 2) + 3, "%s | %s", id->lib->name, id->name + 2); + char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3]; + BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->name, id->name + 2); + 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 |= SELECTED_FILE; + 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 |= SELECTED_FILE; + 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 |= IMAGEFILE; + 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; totlib++; } - + files++; } totbl++; } - - id = id->next; } - + /* only qsort of library blocks */ if (totlib > 1) { - qsort(firstlib, totlib, sizeof(struct direntry), compare_name); + qsort(firstlib, totlib, sizeof(*files), compare_name); } } - filelist->filter = 0; - filelist_filter(filelist); } +#endif -static void thumbnail_joblist_free(ThumbnailJob *tj) +static void filelist_readjob_do( + const bool do_lib, + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) { - 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); + 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; + +// BLI_assert(filelist->filtered == NULL); + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == 0)); + + todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = 1; + + BLI_strncpy(dir, filelist->filelist.root, sizeof(dir)); + BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob)); + + BLI_cleanup_dir(main_name, dir); + td_dir->dir = BLI_strdup(dir); + + while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { + FileListInternEntry *entry; + int nbr_entries = 0; + bool is_lib = do_lib; + + char *subdir; + int recursion_level; + bool skip_currpar; + + 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); + } + if (!nbr_entries) { + is_lib = false; + nbr_entries = filelist_readjob_list_dir(subdir, &entries, filter_glob, do_lib, main_name, skip_currpar); + } + + 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 uint32, 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! */ + *((uint32_t *)entry->uuid) = atomic_add_uint32((uint32_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++; + } + } + } + + 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); } + + nbr_done_dirs++; + *progress = (float)nbr_done_dirs / (float)nbr_todo_dirs; + MEM_freeN(subdir); } - BLI_freelistN(&tj->loadimages); + + /* 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_startjob(void *tjv, short *stop, short *do_update, float *UNUSED(progress)) +static void filelist_readjob_dir( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) { - ThumbnailJob *tj = tjv; - FileImage *limg = tj->loadimages.first; + filelist_readjob_do(false, filelist, main_name, stop, do_update, progress, lock); +} - tj->stop = stop; - tj->do_update = do_update; +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); +} - while ((*stop == 0) && (limg)) { - if (limg->flags & IMAGEFILE) { - limg->img = IMB_thumb_manage(limg->path, THB_NORMAL, THB_SOURCE_IMAGE); - } - else if (limg->flags & (BLENDERFILE | BLENDERFILE_BACKUP)) { - limg->img = IMB_thumb_manage(limg->path, THB_NORMAL, THB_SOURCE_BLEND); - } - else if (limg->flags & MOVIEFILE) { - limg->img = IMB_thumb_manage(limg->path, THB_NORMAL, THB_SOURCE_MOVIE); - if (!limg->img) { - /* remember that file can't be loaded via IMB_open_anim */ - limg->flags &= ~MOVIEFILE; - limg->flags |= MOVIEFILE_ICON; - } - } - *do_update = true; - PIL_sleep_ms(10); - limg = limg->next; - } +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); } -static void thumbnails_update(void *tjv) + +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) { - ThumbnailJob *tj = tjv; + FileListReadJob *flrj = flrjv; - if (tj->filelist && tj->filelist->filelist) { - FileImage *limg = tj->loadimages.first; - while (limg) { - if (!limg->done && limg->img) { - tj->filelist->filelist[limg->index].image = limg->img; - /* update flag for movie files where thumbnail can't be created */ - if (limg->flags & MOVIEFILE_ICON) { - tj->filelist->filelist[limg->index].flags &= ~MOVIEFILE; - tj->filelist->filelist[limg->index].flags |= MOVIEFILE_ICON; - } - limg->done = true; - } - limg = limg->next; - } +// 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 filelist_readjob_update(void *flrjv) +{ + 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 (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)); -void thumbnails_start(FileList *filelist, const bContext *C) + filelist_freelib(flrj->tmp_filelist); + filelist_free(flrj->tmp_filelist); + MEM_freeN(flrj->tmp_filelist); + } + + BLI_mutex_end(&flrj->lock); + + MEM_freeN(flrj); +} + +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(ThumbnailJob), "thumbnails\n"); - tj->filelist = filelist; - for (idx = 0; idx < filelist->numfiles; idx++) { - if (!filelist->filelist[idx].image) { - if ((filelist->filelist[idx].flags & (IMAGEFILE | MOVIEFILE | BLENDERFILE | BLENDERFILE_BACKUP))) { - FileImage *limg = MEM_callocN(sizeof(FileImage), "loadimage"); - BLI_strncpy(limg->path, filelist->filelist[idx].path, FILE_MAX); - 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, NULL); + 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(wm, filelist, NULL); + 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); } |