b6e4991bf52d967625c846c0e4d9c130e3c0cbf3
[blender.git] / source / blender / editors / space_file / filelist.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version. 
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2007 Blender Foundation.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/space_file/filelist.c
29  *  \ingroup spfile
30  */
31
32
33 /* global includes */
34
35 #include <stdlib.h>
36 #include <math.h>
37 #include <string.h>
38 #include <sys/stat.h>
39 #include <time.h>
40
41 #ifndef WIN32
42 #  include <unistd.h>
43 #else
44 #  include <io.h>
45 #  include <direct.h>
46 #endif   
47 #include "MEM_guardedalloc.h"
48
49 #include "BLI_blenlib.h"
50 #include "BLI_fileops.h"
51 #include "BLI_fileops_types.h"
52 #include "BLI_fnmatch.h"
53 #include "BLI_ghash.h"
54 #include "BLI_hash_md5.h"
55 #include "BLI_linklist.h"
56 #include "BLI_math.h"
57 #include "BLI_stack.h"
58 #include "BLI_task.h"
59 #include "BLI_threads.h"
60 #include "BLI_utildefines.h"
61
62 #ifdef WIN32
63 #  include "BLI_winstuff.h"
64 #endif
65
66 #include "BKE_context.h"
67 #include "BKE_global.h"
68 #include "BKE_library.h"
69 #include "BKE_icons.h"
70 #include "BKE_idcode.h"
71 #include "BKE_main.h"
72 #include "BLO_readfile.h"
73
74 #include "DNA_space_types.h"
75
76 #include "ED_datafiles.h"
77 #include "ED_fileselect.h"
78 #include "ED_screen.h"
79
80 #include "IMB_imbuf.h"
81 #include "IMB_imbuf_types.h"
82 #include "IMB_thumbs.h"
83
84 #include "PIL_time.h"
85
86 #include "WM_api.h"
87 #include "WM_types.h"
88
89 #include "UI_resources.h"
90 #include "UI_interface_icons.h"
91
92 #include "atomic_ops.h"
93
94 #include "filelist.h"
95
96
97 /* ----------------- FOLDERLIST (previous/next) -------------- */
98
99 typedef struct FolderList {
100         struct FolderList *next, *prev;
101         char *foldername;
102 } FolderList;
103
104 ListBase *folderlist_new(void)
105 {
106         ListBase *p = MEM_callocN(sizeof(*p), __func__);
107         return p;
108 }
109
110 void folderlist_popdir(struct ListBase *folderlist, char *dir)
111 {
112         const char *prev_dir;
113         struct FolderList *folder;
114         folder = folderlist->last;
115
116         if (folder) {
117                 /* remove the current directory */
118                 MEM_freeN(folder->foldername);
119                 BLI_freelinkN(folderlist, folder);
120
121                 folder = folderlist->last;
122                 if (folder) {
123                         prev_dir = folder->foldername;
124                         BLI_strncpy(dir, prev_dir, FILE_MAXDIR);
125                 }
126         }
127         /* delete the folder next or use setdir directly before PREVIOUS OP */
128 }
129
130 void folderlist_pushdir(ListBase *folderlist, const char *dir)
131 {
132         struct FolderList *folder, *previous_folder;
133         previous_folder = folderlist->last;
134
135         /* check if already exists */
136         if (previous_folder && previous_folder->foldername) {
137                 if (BLI_path_cmp(previous_folder->foldername, dir) == 0) {
138                         return;
139                 }
140         }
141
142         /* create next folder element */
143         folder = MEM_mallocN(sizeof(*folder), __func__);
144         folder->foldername = BLI_strdup(dir);
145
146         /* add it to the end of the list */
147         BLI_addtail(folderlist, folder);
148 }
149
150 const char *folderlist_peeklastdir(ListBase *folderlist)
151 {
152         struct FolderList *folder;
153
154         if (!folderlist->last)
155                 return NULL;
156
157         folder = folderlist->last;
158         return folder->foldername;
159 }
160
161 int folderlist_clear_next(struct SpaceFile *sfile)
162 {
163         struct FolderList *folder;
164
165         /* if there is no folder_next there is nothing we can clear */
166         if (!sfile->folders_next)
167                 return 0;
168
169         /* if previous_folder, next_folder or refresh_folder operators are executed it doesn't clear folder_next */
170         folder = sfile->folders_prev->last;
171         if ((!folder) || (BLI_path_cmp(folder->foldername, sfile->params->dir) == 0))
172                 return 0;
173
174         /* eventually clear flist->folders_next */
175         return 1;
176 }
177
178 /* not listbase itself */
179 void folderlist_free(ListBase *folderlist)
180 {
181         if (folderlist) {
182                 FolderList *folder;
183                 for (folder = folderlist->first; folder; folder = folder->next)
184                         MEM_freeN(folder->foldername);
185                 BLI_freelistN(folderlist);
186         }
187 }
188
189 ListBase *folderlist_duplicate(ListBase *folderlist)
190 {
191         
192         if (folderlist) {
193                 ListBase *folderlistn = MEM_callocN(sizeof(*folderlistn), __func__);
194                 FolderList *folder;
195                 
196                 BLI_duplicatelist(folderlistn, folderlist);
197                 
198                 for (folder = folderlistn->first; folder; folder = folder->next) {
199                         folder->foldername = MEM_dupallocN(folder->foldername);
200                 }
201                 return folderlistn;
202         }
203         return NULL;
204 }
205
206
207 /* ------------------FILELIST------------------------ */
208
209 typedef struct FileListInternEntry {
210         struct FileListInternEntry *next, *prev;
211
212         char uuid[16];  /* ASSET_UUID_LENGTH */
213
214         int typeflag;  /* eFileSel_File_Types */
215         int blentype;  /* ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */
216
217         char *relpath;
218         char *name;  /* not striclty needed, but used during sorting, avoids to have to recompute it there... */
219
220         BLI_stat_t st;
221 } FileListInternEntry;
222
223 typedef struct FileListIntern {
224         ListBase entries;  /* FileListInternEntry items. */
225         FileListInternEntry **filtered;
226
227         char curr_uuid[16];  /* Used to generate uuid during internal listing. */
228 } FileListIntern;
229
230 #define FILELIST_ENTRYCACHESIZE_DEFAULT 1024  /* Keep it a power of two! */
231 typedef struct FileListEntryCache {
232         size_t size;  /* The size of the cache... */
233
234         int flags;
235
236         /* This one gathers all entries from both block and misc caches. Used for easy bulk-freing. */
237         ListBase cached_entries;
238
239         /* Block cache: all entries between start and end index. used for part of the list on diplay. */
240         FileDirEntry **block_entries;
241         int block_start_index, block_end_index, block_center_index, block_cursor;
242
243         /* Misc cache: random indices, FIFO behavior.
244          * Note: Not 100% sure we actually need that, time will say. */
245         int misc_cursor;
246         int *misc_entries_indices;
247         GHash *misc_entries;
248
249         /* Allows to quickly get a cached entry from its UUID. */
250         GHash *uuids;
251
252         /* Previews handling. */
253         TaskPool *previews_pool;
254         ThreadQueue *previews_done;
255 } FileListEntryCache;
256
257 /* FileListCache.flags */
258 enum {
259         FLC_IS_INIT              = 1 << 0,
260         FLC_PREVIEWS_ACTIVE      = 1 << 1,
261 };
262
263 typedef struct FileListEntryPreview {
264         char path[FILE_MAX];
265         unsigned int flags;
266         int index;
267         ImBuf *img;
268 } FileListEntryPreview;
269
270 typedef struct FileListFilter {
271         unsigned int filter;
272         unsigned int filter_id;
273         char filter_glob[256];
274         char filter_search[66];  /* + 2 for heading/trailing implicit '*' wildcards. */
275         short flags;
276 } FileListFilter;
277
278 /* FileListFilter.flags */
279 enum {
280         FLF_HIDE_DOT     = 1 << 0,
281         FLF_HIDE_PARENT  = 1 << 1,
282         FLF_HIDE_LIB_DIR = 1 << 2,
283 };
284
285 typedef struct FileList {
286         FileDirEntryArr filelist;
287
288         short prv_w;
289         short prv_h;
290
291         short flags;
292
293         short sort;
294
295         FileListFilter filter_data;
296
297         struct FileListIntern filelist_intern;
298
299         struct FileListEntryCache filelist_cache;
300
301         /* We need to keep those info outside of actual filelist items, because those are no more persistent
302          * (only generated on demand, and freed as soon as possible).
303          * Persistent part (mere list of paths + stat info) is kept as small as possible, and filebrowser-agnostic.
304          */
305         GHash *selection_state;
306
307         short max_recursion;
308         short recursion_level;
309
310         struct BlendHandle *libfiledata;
311
312         /* Set given path as root directory, may change given string in place to a valid value. */
313         void (*checkdirf)(struct FileList *, char *);
314
315         /* Fill filelist (to be called by read job). */
316         void (*read_jobf)(struct FileList *, const char *, short *, short *, float *, ThreadMutex *);
317
318         /* Filter an entry of current filelist. */
319         bool (*filterf)(struct FileListInternEntry *, const char *, FileListFilter *);
320 } FileList;
321
322 /* FileList.flags */
323 enum {
324         FL_FORCE_RESET    = 1 << 0,
325         FL_IS_READY       = 1 << 1,
326         FL_IS_PENDING     = 1 << 2,
327         FL_NEED_SORTING   = 1 << 3,
328         FL_NEED_FILTERING = 1 << 4,
329 };
330
331 #define SPECIAL_IMG_SIZE 48
332 #define SPECIAL_IMG_ROWS 4
333 #define SPECIAL_IMG_COLS 4
334
335 enum {
336         SPECIAL_IMG_FOLDER      = 0,
337         SPECIAL_IMG_PARENT      = 1,
338         SPECIAL_IMG_REFRESH     = 2,
339         SPECIAL_IMG_BLENDFILE   = 3,
340         SPECIAL_IMG_SOUNDFILE   = 4,
341         SPECIAL_IMG_MOVIEFILE   = 5,
342         SPECIAL_IMG_PYTHONFILE  = 6,
343         SPECIAL_IMG_TEXTFILE    = 7,
344         SPECIAL_IMG_FONTFILE    = 8,
345         SPECIAL_IMG_UNKNOWNFILE = 9,
346         SPECIAL_IMG_LOADING     = 10,
347         SPECIAL_IMG_BACKUP      = 11,
348         SPECIAL_IMG_MAX
349 };
350
351 static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX];
352
353
354 static void filelist_readjob_main(struct FileList *, const char *, short *, short *, float *, ThreadMutex *);
355 static void filelist_readjob_lib(struct FileList *, const char *, short *, short *, float *, ThreadMutex *);
356 static void filelist_readjob_dir(struct FileList *, const char *, short *, short *, float *, ThreadMutex *);
357
358 /* helper, could probably go in BKE actually? */
359 static int groupname_to_code(const char *group);
360 static unsigned int groupname_to_filter_id(const char *group);
361
362 static void filelist_filter_clear(FileList *filelist);
363 static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size);
364
365 /* ********** Sort helpers ********** */
366
367 static int compare_direntry_generic(const FileListInternEntry *entry1, const FileListInternEntry *entry2)
368 {
369         /* type is equal to stat.st_mode */
370
371         if (entry1->typeflag & FILE_TYPE_DIR) {
372                 if (entry2->typeflag & FILE_TYPE_DIR) {
373                         /* If both entries are tagged as dirs, we make a 'sub filter' that shows first the real dirs,
374                          * then libs (.blend files), then categories in libs. */
375                         if (entry1->typeflag & FILE_TYPE_BLENDERLIB) {
376                                 if (!(entry2->typeflag & FILE_TYPE_BLENDERLIB)) {
377                                         return 1;
378                                 }
379                         }
380                         else if (entry2->typeflag & FILE_TYPE_BLENDERLIB) {
381                                 return -1;
382                         }
383                         else if (entry1->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) {
384                                 if (!(entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) {
385                                         return 1;
386                                 }
387                         }
388                         else if (entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) {
389                                 return -1;
390                         }
391                 }
392                 else {
393                         return -1;
394                 }
395         }
396         else if (entry2->typeflag & FILE_TYPE_DIR) {
397                 return 1;
398         }
399
400         /* make sure "." and ".." are always first */
401         if (FILENAME_IS_CURRENT(entry1->relpath)) return -1;
402         if (FILENAME_IS_CURRENT(entry2->relpath)) return 1;
403         if (FILENAME_IS_PARENT(entry1->relpath)) return -1;
404         if (FILENAME_IS_PARENT(entry2->relpath)) return 1;
405         
406         return 0;
407 }
408
409 static int compare_name(void *UNUSED(user_data), const void *a1, const void *a2)
410 {
411         const FileListInternEntry *entry1 = a1;
412         const FileListInternEntry *entry2 = a2;
413         char *name1, *name2;
414         int ret;
415
416         if ((ret = compare_direntry_generic(entry1, entry2))) {
417                 return ret;
418         }
419
420         name1 = entry1->name;
421         name2 = entry2->name;
422
423         return BLI_natstrcmp(name1, name2);
424 }
425
426 static int compare_date(void *UNUSED(user_data), const void *a1, const void *a2)
427 {
428         const FileListInternEntry *entry1 = a1;
429         const FileListInternEntry *entry2 = a2;
430         char *name1, *name2;
431         int64_t time1, time2;
432         int ret;
433
434         if ((ret = compare_direntry_generic(entry1, entry2))) {
435                 return ret;
436         }
437         
438         time1 = (int64_t)entry1->st.st_mtime;
439         time2 = (int64_t)entry2->st.st_mtime;
440         if (time1 < time2) return 1;
441         if (time1 > time2) return -1;
442
443         name1 = entry1->name;
444         name2 = entry2->name;
445
446         return BLI_natstrcmp(name1, name2);
447 }
448
449 static int compare_size(void *UNUSED(user_data), const void *a1, const void *a2)
450 {
451         const FileListInternEntry *entry1 = a1;
452         const FileListInternEntry *entry2 = a2;
453         char *name1, *name2;
454         uint64_t size1, size2;
455         int ret;
456
457         if ((ret = compare_direntry_generic(entry1, entry2))) {
458                 return ret;
459         }
460         
461         size1 = entry1->st.st_size;
462         size2 = entry2->st.st_size;
463         if (size1 < size2) return 1;
464         if (size1 > size2) return -1;
465
466         name1 = entry1->name;
467         name2 = entry2->name;
468
469         return BLI_natstrcmp(name1, name2);
470 }
471
472 static int compare_extension(void *UNUSED(user_data), const void *a1, const void *a2)
473 {
474         const FileListInternEntry *entry1 = a1;
475         const FileListInternEntry *entry2 = a2;
476         char *name1, *name2;
477         int ret;
478
479         if ((ret = compare_direntry_generic(entry1, entry2))) {
480                 return ret;
481         }
482
483         if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && !(entry2->typeflag & FILE_TYPE_BLENDERLIB)) return -1;
484         if (!(entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) return 1;
485         if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) {
486                 if ((entry1->typeflag & FILE_TYPE_DIR) && !(entry2->typeflag & FILE_TYPE_DIR)) return 1;
487                 if (!(entry1->typeflag & FILE_TYPE_DIR) && (entry2->typeflag & FILE_TYPE_DIR)) return -1;
488                 if (entry1->blentype < entry2->blentype) return -1;
489                 if (entry1->blentype > entry2->blentype) return 1;
490         }
491         else {
492                 const char *sufix1, *sufix2;
493
494                 if (!(sufix1 = strstr(entry1->relpath, ".blend.gz")))
495                         sufix1 = strrchr(entry1->relpath, '.');
496                 if (!(sufix2 = strstr(entry2->relpath, ".blend.gz")))
497                         sufix2 = strrchr(entry2->relpath, '.');
498                 if (!sufix1) sufix1 = "";
499                 if (!sufix2) sufix2 = "";
500
501                 if ((ret = BLI_strcasecmp(sufix1, sufix2))) {
502                         return ret;
503                 }
504         }
505
506         name1 = entry1->name;
507         name2 = entry2->name;
508
509         return BLI_natstrcmp(name1, name2);
510 }
511
512 void filelist_sort(struct FileList *filelist)
513 {
514         if ((filelist->flags & FL_NEED_SORTING) && (filelist->sort != FILE_SORT_NONE)) {
515                 switch (filelist->sort) {
516                         case FILE_SORT_ALPHA:
517                                 BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_name, NULL);
518                                 break;
519                         case FILE_SORT_TIME:
520                                 BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_date, NULL);
521                                 break;
522                         case FILE_SORT_SIZE:
523                                 BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_size, NULL);
524                                 break;
525                         case FILE_SORT_EXTENSION:
526                                 BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_extension, NULL);
527                                 break;
528                         case FILE_SORT_NONE:  /* Should never reach this point! */
529                         default:
530                                 BLI_assert(0);
531                                 break;
532                 }
533
534                 filelist_filter_clear(filelist);
535                 filelist->flags &= ~FL_NEED_SORTING;
536         }
537 }
538
539 void filelist_setsorting(struct FileList *filelist, const short sort)
540 {
541         if (filelist->sort != sort) {
542                 filelist->sort = sort;
543                 filelist->flags |= FL_NEED_SORTING;
544         }
545 }
546
547 /* ********** Filter helpers ********** */
548
549 static bool is_hidden_file(const char *filename, FileListFilter *filter)
550 {
551         char *sep = (char *)BLI_last_slash(filename);
552         bool is_hidden = false;
553
554         if (filter->flags & FLF_HIDE_DOT) {
555                 if (filename[0] == '.' && filename[1] != '.' && filename[1] != '\0') {
556                         is_hidden = true; /* ignore .file */
557                 }
558                 else {
559                         int len = strlen(filename);
560                         if ((len > 0) && (filename[len - 1] == '~')) {
561                                 is_hidden = true;  /* ignore file~ */
562                         }
563                 }
564         }
565         if (!is_hidden && (filter->flags & FLF_HIDE_PARENT)) {
566                 if (filename[0] == '.' && filename[1] == '.' && filename[2] == '\0') {
567                         is_hidden = true; /* ignore .. */
568                 }
569         }
570         if (!is_hidden && ((filename[0] == '.') && (filename[1] == '\0'))) {
571                 is_hidden = true; /* ignore . */
572         }
573         /* filename might actually be a piece of path, in which case we have to check all its parts. */
574         if (!is_hidden && sep) {
575                 char tmp_filename[FILE_MAX_LIBEXTRA];
576
577                 BLI_strncpy(tmp_filename, filename, sizeof(tmp_filename));
578                 sep = tmp_filename + (sep - filename);
579                 while (sep) {
580                         BLI_assert(sep[1] != '\0');
581                         if (is_hidden_file(sep + 1, filter)) {
582                                 is_hidden = true;
583                                 break;
584                         }
585                         *sep = '\0';
586                         sep = (char *)BLI_last_slash(tmp_filename);
587                 }
588         }
589         return is_hidden;
590 }
591
592 static bool is_filtered_file(FileListInternEntry *file, const char *UNUSED(root), FileListFilter *filter)
593 {
594         bool is_filtered = !is_hidden_file(file->relpath, filter);
595
596         if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relpath)) {
597                 if (file->typeflag & FILE_TYPE_DIR) {
598                         if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) {
599                                 if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) {
600                                         is_filtered = false;
601                                 }
602                         }
603                         else {
604                                 if (!(filter->filter & FILE_TYPE_FOLDER)) {
605                                         is_filtered = false;
606                                 }
607                         }
608                 }
609                 else {
610                         if (!(file->typeflag & filter->filter)) {
611                                 is_filtered = false;
612                         }
613                 }
614                 if (is_filtered && (filter->filter_search[0] != '\0')) {
615                         if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) {
616                                 is_filtered = false;
617                         }
618                 }
619         }
620
621         return is_filtered;
622 }
623
624 static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter)
625 {
626         bool is_filtered;
627         char path[FILE_MAX_LIBEXTRA], dir[FILE_MAX_LIBEXTRA], *group, *name;
628
629         BLI_join_dirfile(path, sizeof(path), root, file->relpath);
630
631         if (BLO_library_path_explode(path, dir, &group, &name)) {
632                 is_filtered = !is_hidden_file(file->relpath, filter);
633                 if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relpath)) {
634                         if (file->typeflag & FILE_TYPE_DIR) {
635                                 if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) {
636                                         if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) {
637                                                 is_filtered = false;
638                                         }
639                                 }
640                                 else {
641                                         if (!(filter->filter & FILE_TYPE_FOLDER)) {
642                                                 is_filtered = false;
643                                         }
644                                 }
645                         }
646                         if (is_filtered && group) {
647                                 if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) {
648                                         is_filtered = false;
649                                 }
650                                 else {
651                                         unsigned int filter_id = groupname_to_filter_id(group);
652                                         if (!(filter_id & filter->filter_id)) {
653                                                 is_filtered = false;
654                                         }
655                                 }
656                         }
657                         if (is_filtered && (filter->filter_search[0] != '\0')) {
658                                 if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) {
659                                         is_filtered = false;
660                                 }
661                         }
662                 }
663         }
664         else {
665                 is_filtered = is_filtered_file(file, root, filter);
666         }
667
668         return is_filtered;
669 }
670
671 static bool is_filtered_main(FileListInternEntry *file, const char *UNUSED(dir), FileListFilter *filter)
672 {
673         return !is_hidden_file(file->relpath, filter);
674 }
675
676 static void filelist_filter_clear(FileList *filelist)
677 {
678         filelist->flags |= FL_NEED_FILTERING;
679 }
680
681 void filelist_filter(FileList *filelist)
682 {
683         int num_filtered = 0;
684         const int num_files = filelist->filelist.nbr_entries;
685         FileListInternEntry **filtered_tmp, *file;
686
687         if (filelist->filelist.nbr_entries == 0) {
688                 return;
689         }
690
691         if (!(filelist->flags & FL_NEED_FILTERING)) {
692                 /* Assume it has already been filtered, nothing else to do! */
693                 return;
694         }
695
696         filelist->filter_data.flags &= ~FLF_HIDE_LIB_DIR;
697         if (filelist->max_recursion) {
698                 /* Never show lib ID 'categories' directories when we are in 'flat' mode, unless
699                  * root path is a blend file. */
700                 char dir[FILE_MAX_LIBEXTRA];
701                 if (!filelist_islibrary(filelist, dir, NULL)) {
702                         filelist->filter_data.flags |= FLF_HIDE_LIB_DIR;
703                 }
704         }
705
706         filtered_tmp = MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__);
707
708         /* Filter remap & count how many files are left after filter in a single loop. */
709         for (file = filelist->filelist_intern.entries.first; file; file = file->next) {
710                 if (filelist->filterf(file, filelist->filelist.root, &filelist->filter_data)) {
711                         filtered_tmp[num_filtered++] = file;
712                 }
713         }
714
715         if (filelist->filelist_intern.filtered) {
716                 MEM_freeN(filelist->filelist_intern.filtered);
717         }
718         filelist->filelist_intern.filtered = MEM_mallocN(sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered,
719                                                          __func__);
720         memcpy(filelist->filelist_intern.filtered, filtered_tmp,
721                sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered);
722         filelist->filelist.nbr_entries_filtered = num_filtered;
723 //      printf("Filetered: %d over %d entries\n", num_filtered, filelist->filelist.nbr_entries);
724
725         filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size);
726         filelist->flags &= ~FL_NEED_FILTERING;
727
728         MEM_freeN(filtered_tmp);
729 }
730
731 void filelist_setfilter_options(FileList *filelist, const bool hide_dot, const bool hide_parent,
732                                 const unsigned int filter, const unsigned int filter_id,
733                                 const char *filter_glob, const char *filter_search)
734 {
735         bool update = false;
736
737         if (((filelist->filter_data.flags & FLF_HIDE_DOT) != 0) != (hide_dot != 0)) {
738                 filelist->filter_data.flags ^= FLF_HIDE_DOT;
739                 update = true;
740         }
741         if (((filelist->filter_data.flags & FLF_HIDE_PARENT) != 0) != (hide_parent != 0)) {
742                 filelist->filter_data.flags ^= FLF_HIDE_PARENT;
743                 update = true;
744         }
745         if ((filelist->filter_data.filter != filter) || (filelist->filter_data.filter_id != filter_id)) {
746                 filelist->filter_data.filter = filter;
747                 filelist->filter_data.filter_id = filter_id;
748                 update = true;
749         }
750         if (!STREQ(filelist->filter_data.filter_glob, filter_glob)) {
751                 BLI_strncpy(filelist->filter_data.filter_glob, filter_glob, sizeof(filelist->filter_data.filter_glob));
752                 update = true;
753         }
754         if ((BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) {
755                 BLI_strncpy_ensure_pad(filelist->filter_data.filter_search, filter_search, '*',
756                                        sizeof(filelist->filter_data.filter_search));
757                 update = true;
758         }
759
760         if (update) {
761                 /* And now, free filtered data so that we know we have to filter again. */
762                 filelist_filter_clear(filelist);
763         }
764 }
765
766 /* ********** Icon/image helpers ********** */
767
768 void filelist_init_icons(void)
769 {
770         short x, y, k;
771         ImBuf *bbuf;
772         ImBuf *ibuf;
773
774         BLI_assert(G.background == false);
775
776 #ifdef WITH_HEADLESS
777         bbuf = NULL;
778 #else
779         bbuf = IMB_ibImageFromMemory((unsigned char *)datatoc_prvicons_png, datatoc_prvicons_png_size, IB_rect, NULL, "<splash>");
780 #endif
781         if (bbuf) {
782                 for (y = 0; y < SPECIAL_IMG_ROWS; y++) {
783                         for (x = 0; x < SPECIAL_IMG_COLS; x++) {
784                                 int tile = SPECIAL_IMG_COLS * y + x;
785                                 if (tile < SPECIAL_IMG_MAX) {
786                                         ibuf = IMB_allocImBuf(SPECIAL_IMG_SIZE, SPECIAL_IMG_SIZE, 32, IB_rect);
787                                         for (k = 0; k < SPECIAL_IMG_SIZE; k++) {
788                                                 memcpy(&ibuf->rect[k * SPECIAL_IMG_SIZE], &bbuf->rect[(k + y * SPECIAL_IMG_SIZE) * SPECIAL_IMG_SIZE * SPECIAL_IMG_COLS + x * SPECIAL_IMG_SIZE], SPECIAL_IMG_SIZE * sizeof(int));
789                                         }
790                                         gSpecialFileImages[tile] = ibuf;
791                                 }
792                         }
793                 }
794                 IMB_freeImBuf(bbuf);
795         }
796 }
797
798 void filelist_free_icons(void)
799 {
800         int i;
801
802         BLI_assert(G.background == false);
803
804         for (i = 0; i < SPECIAL_IMG_MAX; ++i) {
805                 IMB_freeImBuf(gSpecialFileImages[i]);
806                 gSpecialFileImages[i] = NULL;
807         }
808 }
809
810 void filelist_imgsize(struct FileList *filelist, short w, short h)
811 {
812         filelist->prv_w = w;
813         filelist->prv_h = h;
814 }
815
816 static FileDirEntry *filelist_geticon_get_file(struct FileList *filelist, const int index)
817 {
818         BLI_assert(G.background == false);
819
820         return filelist_file(filelist, index);
821 }
822
823 ImBuf *filelist_getimage(struct FileList *filelist, const int index)
824 {
825         FileDirEntry *file = filelist_geticon_get_file(filelist, index);
826
827         return file->image;
828 }
829
830 static ImBuf *filelist_geticon_image_ex(const unsigned int typeflag, const char *relpath)
831 {
832         ImBuf *ibuf = NULL;
833
834         if (typeflag & FILE_TYPE_DIR) {
835                 if (FILENAME_IS_PARENT(relpath)) {
836                         ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT];
837                 }
838                 else if (FILENAME_IS_CURRENT(relpath)) {
839                         ibuf = gSpecialFileImages[SPECIAL_IMG_REFRESH];
840                 }
841                 else {
842                         ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER];
843                 }
844         }
845         else if (typeflag & FILE_TYPE_BLENDER) {
846                 ibuf = gSpecialFileImages[SPECIAL_IMG_BLENDFILE];
847         }
848         else if (typeflag & FILE_TYPE_BLENDERLIB) {
849                 ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE];
850         }
851         else if (typeflag & (FILE_TYPE_MOVIE)) {
852                 ibuf = gSpecialFileImages[SPECIAL_IMG_MOVIEFILE];
853         }
854         else if (typeflag & FILE_TYPE_SOUND) {
855                 ibuf = gSpecialFileImages[SPECIAL_IMG_SOUNDFILE];
856         }
857         else if (typeflag & FILE_TYPE_PYSCRIPT) {
858                 ibuf = gSpecialFileImages[SPECIAL_IMG_PYTHONFILE];
859         }
860         else if (typeflag & FILE_TYPE_FTFONT) {
861                 ibuf = gSpecialFileImages[SPECIAL_IMG_FONTFILE];
862         }
863         else if (typeflag & FILE_TYPE_TEXT) {
864                 ibuf = gSpecialFileImages[SPECIAL_IMG_TEXTFILE];
865         }
866         else if (typeflag & FILE_TYPE_IMAGE) {
867                 ibuf = gSpecialFileImages[SPECIAL_IMG_LOADING];
868         }
869         else if (typeflag & FILE_TYPE_BLENDER_BACKUP) {
870                 ibuf = gSpecialFileImages[SPECIAL_IMG_BACKUP];
871         }
872         else {
873                 ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE];
874         }
875
876         return ibuf;
877 }
878
879 ImBuf *filelist_geticon_image(struct FileList *filelist, const int index)
880 {
881         FileDirEntry *file = filelist_geticon_get_file(filelist, index);
882
883         return filelist_geticon_image_ex(file->typeflag, file->relpath);
884 }
885
886 static int filelist_geticon_ex(
887         const int typeflag, const int blentype, const char *relpath, const bool is_main, const bool ignore_libdir)
888 {
889         if ((typeflag & FILE_TYPE_DIR) && !(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER)))) {
890                 if (FILENAME_IS_PARENT(relpath)) {
891                         return is_main ? ICON_FILE_PARENT : ICON_NONE;
892                 }
893                 else if (typeflag & FILE_TYPE_APPLICATIONBUNDLE) {
894                         return ICON_UGLYPACKAGE;
895                 }
896                 else if (typeflag & FILE_TYPE_BLENDER) {
897                         return ICON_FILE_BLEND;
898                 }
899                 else if (is_main) {
900                         /* Do not return icon for folders if icons are not 'main' draw type (e.g. when used over previews). */
901                         return ICON_FILE_FOLDER;
902                 }
903         }
904
905         if (typeflag & FILE_TYPE_BLENDER)
906                 return ICON_FILE_BLEND;
907         else if (typeflag & FILE_TYPE_BLENDER_BACKUP)
908                 return ICON_FILE_BACKUP;
909         else if (typeflag & FILE_TYPE_IMAGE)
910                 return ICON_FILE_IMAGE;
911         else if (typeflag & FILE_TYPE_MOVIE)
912                 return ICON_FILE_MOVIE;
913         else if (typeflag & FILE_TYPE_PYSCRIPT)
914                 return ICON_FILE_SCRIPT;
915         else if (typeflag & FILE_TYPE_SOUND)
916                 return ICON_FILE_SOUND;
917         else if (typeflag & FILE_TYPE_FTFONT)
918                 return ICON_FILE_FONT;
919         else if (typeflag & FILE_TYPE_BTX)
920                 return ICON_FILE_BLANK;
921         else if (typeflag & FILE_TYPE_COLLADA)
922                 return ICON_FILE_BLANK;
923         else if (typeflag & FILE_TYPE_ALEMBIC)
924                 return ICON_FILE_BLANK;
925         else if (typeflag & FILE_TYPE_TEXT)
926                 return ICON_FILE_TEXT;
927         else if (typeflag & FILE_TYPE_BLENDERLIB) {
928                 const int ret = UI_idcode_icon_get(blentype);
929                 if (ret != ICON_NONE) {
930                         return ret;
931                 }
932         }
933         return is_main ? ICON_FILE_BLANK : ICON_NONE;
934 }
935
936 int filelist_geticon(struct FileList *filelist, const int index, const bool is_main)
937 {
938         FileDirEntry *file = filelist_geticon_get_file(filelist, index);
939
940         return filelist_geticon_ex(file->typeflag, file->blentype, file->relpath, is_main, false);
941 }
942
943 /* ********** Main ********** */
944
945 static void filelist_checkdir_dir(struct FileList *UNUSED(filelist), char *r_dir)
946 {
947         BLI_make_exist(r_dir);
948 }
949
950 static void filelist_checkdir_lib(struct FileList *UNUSED(filelist), char *r_dir)
951 {
952         char dir[FILE_MAX_LIBEXTRA];
953         if (!BLO_library_path_explode(r_dir, dir, NULL, NULL)) {
954                 /* if not a valid library, we need it to be a valid directory! */
955                 BLI_make_exist(r_dir);
956         }
957 }
958
959 static void filelist_checkdir_main(struct FileList *filelist, char *r_dir)
960 {
961         /* TODO */
962         filelist_checkdir_lib(filelist, r_dir);
963 }
964
965 static void filelist_entry_clear(FileDirEntry *entry)
966 {
967         if (entry->name) {
968                 MEM_freeN(entry->name);
969         }
970         if (entry->description) {
971                 MEM_freeN(entry->description);
972         }
973         if (entry->relpath) {
974                 MEM_freeN(entry->relpath);
975         }
976         if (entry->image) {
977                 IMB_freeImBuf(entry->image);
978         }
979         /* For now, consider FileDirEntryRevision::poin as not owned here, so no need to do anything about it */
980
981         if (!BLI_listbase_is_empty(&entry->variants)) {
982                 FileDirEntryVariant *var;
983
984                 for (var = entry->variants.first; var; var = var->next) {
985                         if (var->name) {
986                                 MEM_freeN(var->name);
987                         }
988                         if (var->description) {
989                                 MEM_freeN(var->description);
990                         }
991
992                         if (!BLI_listbase_is_empty(&var->revisions)) {
993                                 FileDirEntryRevision *rev;
994
995                                 for (rev = var->revisions.first; rev; rev = rev->next) {
996                                         if (rev->comment) {
997                                                 MEM_freeN(rev->comment);
998                                         }
999                                 }
1000
1001                                 BLI_freelistN(&var->revisions);
1002                         }
1003                 }
1004
1005                 /* TODO: tags! */
1006
1007                 BLI_freelistN(&entry->variants);
1008         }
1009         else if (entry->entry) {
1010                 MEM_freeN(entry->entry);
1011         }
1012 }
1013
1014 static void filelist_entry_free(FileDirEntry *entry)
1015 {
1016         filelist_entry_clear(entry);
1017         MEM_freeN(entry);
1018 }
1019
1020 static void filelist_direntryarr_free(FileDirEntryArr *array)
1021 {
1022 #if 0
1023         FileDirEntry *entry, *entry_next;
1024
1025         for (entry = array->entries.first; entry; entry = entry_next) {
1026                 entry_next = entry->next;
1027                 filelist_entry_free(entry);
1028         }
1029         BLI_listbase_clear(&array->entries);
1030 #else
1031         BLI_assert(BLI_listbase_is_empty(&array->entries));
1032 #endif
1033         array->nbr_entries = 0;
1034         array->nbr_entries_filtered = -1;
1035         array->entry_idx_start = -1;
1036         array->entry_idx_end = -1;
1037 }
1038
1039 static void filelist_intern_entry_free(FileListInternEntry *entry)
1040 {
1041         if (entry->relpath) {
1042                 MEM_freeN(entry->relpath);
1043         }
1044         if (entry->name) {
1045                 MEM_freeN(entry->name);
1046         }
1047         MEM_freeN(entry);
1048 }
1049
1050 static void filelist_intern_free(FileListIntern *filelist_intern)
1051 {
1052         FileListInternEntry *entry, *entry_next;
1053
1054         for (entry = filelist_intern->entries.first; entry; entry = entry_next) {
1055                 entry_next = entry->next;
1056                 filelist_intern_entry_free(entry);
1057         }
1058         BLI_listbase_clear(&filelist_intern->entries);
1059
1060         MEM_SAFE_FREE(filelist_intern->filtered);
1061 }
1062
1063 static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata, int UNUSED(threadid))
1064 {
1065         FileListEntryCache *cache = BLI_task_pool_userdata(pool);
1066         FileListEntryPreview *preview = taskdata;
1067
1068         ThumbSource source = 0;
1069
1070 //      printf("%s: Start (%d)...\n", __func__, threadid);
1071
1072 //      printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1073         BLI_assert(preview->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT |
1074                                      FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB));
1075
1076         if (preview->flags & FILE_TYPE_IMAGE) {
1077                 source = THB_SOURCE_IMAGE;
1078         }
1079         else if (preview->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)) {
1080                 source = THB_SOURCE_BLEND;
1081         }
1082         else if (preview->flags & FILE_TYPE_MOVIE) {
1083                 source = THB_SOURCE_MOVIE;
1084         }
1085         else if (preview->flags & FILE_TYPE_FTFONT) {
1086                 source = THB_SOURCE_FONT;
1087         }
1088
1089         IMB_thumb_path_lock(preview->path);
1090         preview->img = IMB_thumb_manage(preview->path, THB_LARGE, source);
1091         IMB_thumb_path_unlock(preview->path);
1092
1093         preview->flags = 0;  /* Used to tell free func to not free anything! */
1094         BLI_thread_queue_push(cache->previews_done, preview);
1095
1096 //      printf("%s: End (%d)...\n", __func__, threadid);
1097 }
1098
1099 static void filelist_cache_preview_freef(TaskPool * __restrict UNUSED(pool), void *taskdata, int UNUSED(threadid))
1100 {
1101         FileListEntryPreview *preview = taskdata;
1102
1103         /* If preview->flag is empty, it means that preview has already been generated and added to done queue,
1104          * we do not own it anymore. */
1105         if (preview->flags) {
1106                 if (preview->img) {
1107                         IMB_freeImBuf(preview->img);
1108                 }
1109                 MEM_freeN(preview);
1110         }
1111 }
1112
1113 static void filelist_cache_preview_ensure_running(FileListEntryCache *cache)
1114 {
1115         if (!cache->previews_pool) {
1116                 TaskScheduler *scheduler = BLI_task_scheduler_get();
1117
1118                 cache->previews_pool = BLI_task_pool_create_background(scheduler, cache);
1119                 cache->previews_done = BLI_thread_queue_init();
1120
1121                 IMB_thumb_locks_acquire();
1122         }
1123 }
1124
1125 static void filelist_cache_previews_clear(FileListEntryCache *cache)
1126 {
1127         FileListEntryPreview *preview;
1128
1129         if (cache->previews_pool) {
1130                 BLI_task_pool_cancel(cache->previews_pool);
1131
1132                 while ((preview = BLI_thread_queue_pop_timeout(cache->previews_done, 0))) {
1133 //                      printf("%s: DONE %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1134                         if (preview->img) {
1135                                 IMB_freeImBuf(preview->img);
1136                         }
1137                         MEM_freeN(preview);
1138                 }
1139         }
1140 }
1141
1142 static void filelist_cache_previews_free(FileListEntryCache *cache)
1143 {
1144         if (cache->previews_pool) {
1145                 BLI_thread_queue_nowait(cache->previews_done);
1146
1147                 filelist_cache_previews_clear(cache);
1148
1149                 BLI_thread_queue_free(cache->previews_done);
1150                 BLI_task_pool_free(cache->previews_pool);
1151                 cache->previews_pool = NULL;
1152                 cache->previews_done = NULL;
1153
1154                 IMB_thumb_locks_release();
1155         }
1156
1157         cache->flags &= ~FLC_PREVIEWS_ACTIVE;
1158 }
1159
1160 static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry, const int index)
1161 {
1162         FileListEntryCache *cache = &filelist->filelist_cache;
1163
1164         BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE);
1165
1166         if (!entry->image &&
1167             !(entry->flags & FILE_ENTRY_INVALID_PREVIEW) &&
1168             (entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT |
1169                                 FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)))
1170         {
1171                 FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__);
1172                 BLI_join_dirfile(preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath);
1173                 preview->index = index;
1174                 preview->flags = entry->typeflag;
1175                 preview->img = NULL;
1176 //              printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1177
1178                 filelist_cache_preview_ensure_running(cache);
1179                 BLI_task_pool_push_ex(cache->previews_pool, filelist_cache_preview_runf, preview,
1180                                       true, filelist_cache_preview_freef, TASK_PRIORITY_LOW);
1181         }
1182 }
1183
1184 static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size)
1185 {
1186         BLI_listbase_clear(&cache->cached_entries);
1187
1188         cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0;
1189         cache->block_entries = MEM_mallocN(sizeof(*cache->block_entries) * cache_size, __func__);
1190
1191         cache->misc_entries = BLI_ghash_ptr_new_ex(__func__, cache_size);
1192         cache->misc_entries_indices = MEM_mallocN(sizeof(*cache->misc_entries_indices) * cache_size, __func__);
1193         copy_vn_i(cache->misc_entries_indices, cache_size, -1);
1194         cache->misc_cursor = 0;
1195
1196         /* XXX This assumes uint is 32 bits and uuid is 128 bits (char[16]), be careful! */
1197         cache->uuids = BLI_ghash_new_ex(
1198                            BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__, cache_size * 2);
1199
1200         cache->size = cache_size;
1201         cache->flags = FLC_IS_INIT;
1202 }
1203
1204 static void filelist_cache_free(FileListEntryCache *cache)
1205 {
1206         FileDirEntry *entry, *entry_next;
1207
1208         if (!(cache->flags & FLC_IS_INIT)) {
1209                 return;
1210         }
1211
1212         filelist_cache_previews_free(cache);
1213
1214         MEM_freeN(cache->block_entries);
1215
1216         BLI_ghash_free(cache->misc_entries, NULL, NULL);
1217         MEM_freeN(cache->misc_entries_indices);
1218
1219         BLI_ghash_free(cache->uuids, NULL, NULL);
1220
1221         for (entry = cache->cached_entries.first; entry; entry = entry_next) {
1222                 entry_next = entry->next;
1223                 filelist_entry_free(entry);
1224         }
1225         BLI_listbase_clear(&cache->cached_entries);
1226 }
1227
1228 static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size)
1229 {
1230         FileDirEntry *entry, *entry_next;
1231
1232         if (!(cache->flags & FLC_IS_INIT)) {
1233                 return;
1234         }
1235
1236         filelist_cache_previews_clear(cache);
1237
1238         cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0;
1239         if (new_size != cache->size) {
1240                 cache->block_entries = MEM_reallocN(cache->block_entries, sizeof(*cache->block_entries) * new_size);
1241         }
1242
1243         BLI_ghash_clear_ex(cache->misc_entries, NULL, NULL, new_size);
1244         if (new_size != cache->size) {
1245                 cache->misc_entries_indices = MEM_reallocN(cache->misc_entries_indices,
1246                                                            sizeof(*cache->misc_entries_indices) * new_size);
1247         }
1248         copy_vn_i(cache->misc_entries_indices, new_size, -1);
1249
1250         BLI_ghash_clear_ex(cache->uuids, NULL, NULL, new_size * 2);
1251
1252         cache->size = new_size;
1253
1254         for (entry = cache->cached_entries.first; entry; entry = entry_next) {
1255                 entry_next = entry->next;
1256                 filelist_entry_free(entry);
1257         }
1258         BLI_listbase_clear(&cache->cached_entries);
1259 }
1260
1261 FileList *filelist_new(short type)
1262 {
1263         FileList *p = MEM_callocN(sizeof(*p), __func__);
1264
1265         filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT);
1266
1267         p->selection_state = BLI_ghash_new(BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__);
1268
1269         switch (type) {
1270                 case FILE_MAIN:
1271                         p->checkdirf = filelist_checkdir_main;
1272                         p->read_jobf = filelist_readjob_main;
1273                         p->filterf = is_filtered_main;
1274                         break;
1275                 case FILE_LOADLIB:
1276                         p->checkdirf = filelist_checkdir_lib;
1277                         p->read_jobf = filelist_readjob_lib;
1278                         p->filterf = is_filtered_lib;
1279                         break;
1280                 default:
1281                         p->checkdirf = filelist_checkdir_dir;
1282                         p->read_jobf = filelist_readjob_dir;
1283                         p->filterf = is_filtered_file;
1284                         break;
1285         }
1286         return p;
1287 }
1288
1289 void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection)
1290 {
1291         if (!filelist) {
1292                 return;
1293         }
1294
1295         filelist_filter_clear(filelist);
1296
1297         if (do_cache) {
1298                 filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size);
1299         }
1300
1301         filelist_intern_free(&filelist->filelist_intern);
1302
1303         filelist_direntryarr_free(&filelist->filelist);
1304
1305         if (do_selection && filelist->selection_state) {
1306                 BLI_ghash_clear(filelist->selection_state, MEM_freeN, NULL);
1307         }
1308 }
1309
1310 void filelist_clear(struct FileList *filelist)
1311 {
1312         filelist_clear_ex(filelist, true, true);
1313 }
1314
1315 void filelist_free(struct FileList *filelist)
1316 {
1317         if (!filelist) {
1318                 printf("Attempting to delete empty filelist.\n");
1319                 return;
1320         }
1321         
1322         filelist_clear_ex(filelist, false, false);  /* No need to clear cache & selection_state, we free them anyway. */
1323         filelist_cache_free(&filelist->filelist_cache);
1324
1325         if (filelist->selection_state) {
1326                 BLI_ghash_free(filelist->selection_state, MEM_freeN, NULL);
1327                 filelist->selection_state = NULL;
1328         }
1329
1330         memset(&filelist->filter_data, 0, sizeof(filelist->filter_data));
1331
1332         filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING);
1333         filelist->sort = FILE_SORT_NONE;
1334 }
1335
1336 void filelist_freelib(struct FileList *filelist)
1337 {
1338         if (filelist->libfiledata)
1339                 BLO_blendhandle_close(filelist->libfiledata);
1340         filelist->libfiledata = NULL;
1341 }
1342
1343 BlendHandle *filelist_lib(struct FileList *filelist)
1344 {
1345         return filelist->libfiledata;
1346 }
1347
1348 static const char *fileentry_uiname(const char *root, const char *relpath, const int typeflag, char *buff)
1349 {
1350         char *name = NULL;
1351
1352         if (typeflag & FILE_TYPE_BLENDERLIB) {
1353                 char abspath[FILE_MAX_LIBEXTRA];
1354                 char *group;
1355
1356                 BLI_join_dirfile(abspath, sizeof(abspath), root, relpath);
1357                 BLO_library_path_explode(abspath, buff, &group, &name);
1358                 if (!name) {
1359                         name = group;
1360                 }
1361         }
1362         /* Depending on platforms, 'my_file.blend/..' might be viewed as dir or not... */
1363         if (!name) {
1364                 if (typeflag & FILE_TYPE_DIR) {
1365                         name = (char *)relpath;
1366                 }
1367                 else {
1368                         name = (char *)BLI_path_basename(relpath);
1369                 }
1370         }
1371         BLI_assert(name);
1372
1373         return name;
1374 }
1375
1376 const char *filelist_dir(struct FileList *filelist)
1377 {
1378         return filelist->filelist.root;
1379 }
1380
1381 /**
1382  * May modify in place given r_dir, which is expected to be FILE_MAX_LIBEXTRA length.
1383  */
1384 void filelist_setdir(struct FileList *filelist, char *r_dir)
1385 {
1386         BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA);
1387
1388         BLI_cleanup_dir(G.main->name, r_dir);
1389         filelist->checkdirf(filelist, r_dir);
1390
1391         if (!STREQ(filelist->filelist.root, r_dir)) {
1392                 BLI_strncpy(filelist->filelist.root, r_dir, sizeof(filelist->filelist.root));
1393                 filelist->flags |= FL_FORCE_RESET;
1394         }
1395 }
1396
1397 void filelist_setrecursion(struct FileList *filelist, const int recursion_level)
1398 {
1399         if (filelist->max_recursion != recursion_level) {
1400                 filelist->max_recursion = recursion_level;
1401                 filelist->flags |= FL_FORCE_RESET;
1402         }
1403 }
1404
1405 bool filelist_force_reset(struct FileList *filelist)
1406 {
1407         return (filelist->flags & FL_FORCE_RESET) != 0;
1408 }
1409
1410 bool filelist_is_ready(struct FileList *filelist)
1411 {
1412         return (filelist->flags & FL_IS_READY) != 0;
1413 }
1414
1415 bool filelist_pending(struct FileList *filelist)
1416 {
1417         return (filelist->flags & FL_IS_PENDING) != 0;
1418 }
1419
1420 /**
1421  * Limited version of full update done by space_file's file_refresh(), to be used by operators and such.
1422  * Ensures given filelist is ready to be used (i.e. it is filtered and sorted), unless it is tagged for a full refresh.
1423  */
1424 int filelist_files_ensure(FileList *filelist)
1425 {
1426         if (!filelist_force_reset(filelist) || !filelist_empty(filelist)) {
1427                 filelist_sort(filelist);
1428                 filelist_filter(filelist);
1429         }
1430
1431         return filelist->filelist.nbr_entries_filtered;
1432 }
1433
1434 static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index)
1435 {
1436         FileListInternEntry *entry = filelist->filelist_intern.filtered[index];
1437         FileListEntryCache *cache = &filelist->filelist_cache;
1438         FileDirEntry *ret;
1439         FileDirEntryRevision *rev;
1440
1441         ret = MEM_callocN(sizeof(*ret), __func__);
1442         rev = MEM_callocN(sizeof(*rev), __func__);
1443
1444         rev->size = (uint64_t)entry->st.st_size;
1445
1446         rev->time = (int64_t)entry->st.st_mtime;
1447
1448         ret->entry = rev;
1449         ret->relpath = BLI_strdup(entry->relpath);
1450         ret->name = BLI_strdup(entry->name);
1451         ret->description = BLI_strdupcat(filelist->filelist.root, entry->relpath);
1452         memcpy(ret->uuid, entry->uuid, sizeof(ret->uuid));
1453         ret->blentype = entry->blentype;
1454         ret->typeflag = entry->typeflag;
1455
1456         BLI_addtail(&cache->cached_entries, ret);
1457         return ret;
1458 }
1459
1460 static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry)
1461 {
1462         BLI_remlink(&filelist->filelist_cache.cached_entries, entry);
1463         filelist_entry_free(entry);
1464 }
1465
1466 static FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request)
1467 {
1468         FileDirEntry *ret = NULL, *old;
1469         FileListEntryCache *cache = &filelist->filelist_cache;
1470         const size_t cache_size = cache->size;
1471         int old_index;
1472
1473         if ((index < 0) || (index >= filelist->filelist.nbr_entries_filtered)) {
1474                 return ret;
1475         }
1476
1477         if (index >= cache->block_start_index && index < cache->block_end_index) {
1478                 const int idx = (index - cache->block_start_index + cache->block_cursor) % cache_size;
1479                 return cache->block_entries[idx];
1480         }
1481
1482         if ((ret = BLI_ghash_lookup(cache->misc_entries, SET_INT_IN_POINTER(index)))) {
1483                 return ret;
1484         }
1485
1486         if (!use_request) {
1487                 return NULL;
1488         }
1489
1490 //      printf("requesting file %d (not yet cached)\n", index);
1491
1492         /* Else, we have to add new entry to 'misc' cache - and possibly make room for it first! */
1493         ret = filelist_file_create_entry(filelist, index);
1494         old_index = cache->misc_entries_indices[cache->misc_cursor];
1495         if ((old = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(old_index), NULL))) {
1496                 BLI_ghash_remove(cache->uuids, old->uuid, NULL, NULL);
1497                 filelist_file_release_entry(filelist, old);
1498         }
1499         BLI_ghash_insert(cache->misc_entries, SET_INT_IN_POINTER(index), ret);
1500         BLI_ghash_insert(cache->uuids, ret->uuid, ret);
1501
1502         cache->misc_entries_indices[cache->misc_cursor] = index;
1503         cache->misc_cursor = (cache->misc_cursor + 1) % cache_size;
1504
1505 #if 0  /* Actually no, only block cached entries should have preview imho. */
1506         if (cache->previews_pool) {
1507                 filelist_cache_previews_push(filelist, ret, index);
1508         }
1509 #endif
1510
1511         return ret;
1512 }
1513
1514 FileDirEntry *filelist_file(struct FileList *filelist, int index)
1515 {
1516         return filelist_file_ex(filelist, index, true);
1517 }
1518
1519 int filelist_file_findpath(struct FileList *filelist, const char *filename)
1520 {
1521         int fidx = -1;
1522         
1523         if (filelist->filelist.nbr_entries_filtered < 0) {
1524                 return fidx;
1525         }
1526
1527         /* XXX TODO Cache could probably use a ghash on paths too? Not really urgent though.
1528          *          This is only used to find again renamed entry, annoying but looks hairy to get rid of it currently. */
1529
1530         for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) {
1531                 FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx];
1532                 if (STREQ(entry->relpath, filename)) {
1533                         return fidx;
1534                 }
1535         }
1536
1537         return -1;
1538 }
1539
1540 FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4])
1541 {
1542         if (filelist->filelist.nbr_entries_filtered < 0) {
1543                 return NULL;
1544         }
1545
1546         if (filelist->filelist_cache.uuids) {
1547                 FileDirEntry *entry = BLI_ghash_lookup(filelist->filelist_cache.uuids, uuid);
1548                 if (entry) {
1549                         return entry;
1550                 }
1551         }
1552
1553         {
1554                 int fidx;
1555
1556                 for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) {
1557                         FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx];
1558                         if (memcmp(entry->uuid, uuid, sizeof(entry->uuid)) == 0) {
1559                                 return filelist_file(filelist, fidx);
1560                         }
1561                 }
1562         }
1563
1564         return NULL;
1565 }
1566
1567 void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size)
1568 {
1569         /* Always keep it power of 2, in [256, 8192] range for now, cache being app. twice bigger than requested window. */
1570         size_t size = 256;
1571         window_size *= 2;
1572
1573         while (size < window_size && size < 8192) {
1574                 size *= 2;
1575         }
1576
1577         if (size != filelist->filelist_cache.size) {
1578                 filelist_cache_clear(&filelist->filelist_cache, size);
1579         }
1580 }
1581
1582 /* Helpers, low-level, they assume cursor + size <= cache_size */
1583 static bool filelist_file_cache_block_create(FileList *filelist, const int start_index, const int size, int cursor)
1584 {
1585         FileListEntryCache *cache = &filelist->filelist_cache;
1586
1587         {
1588                 int i, idx;
1589
1590                 for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) {
1591                         FileDirEntry *entry;
1592
1593                         /* That entry might have already been requested and stored in misc cache... */
1594                         if ((entry = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(idx), NULL)) == NULL) {
1595                                 entry = filelist_file_create_entry(filelist, idx);
1596                                 BLI_ghash_insert(cache->uuids, entry->uuid, entry);
1597                         }
1598                         cache->block_entries[cursor] = entry;
1599                 }
1600                 return true;
1601         }
1602
1603         return false;
1604 }
1605
1606 static void filelist_file_cache_block_release(struct FileList *filelist, const int size, int cursor)
1607 {
1608         FileListEntryCache *cache = &filelist->filelist_cache;
1609
1610         {
1611                 int i;
1612
1613                 for (i = 0; i < size; i++, cursor++) {
1614                         FileDirEntry *entry = cache->block_entries[cursor];
1615 //                      printf("%s: release cacheidx %d (%%p %%s)\n", __func__, cursor/*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/);
1616                         BLI_ghash_remove(cache->uuids, entry->uuid, NULL, NULL);
1617                         filelist_file_release_entry(filelist, entry);
1618 #ifndef NDEBUG
1619                         cache->block_entries[cursor] = NULL;
1620 #endif
1621                 }
1622         }
1623 }
1624
1625 /* Load in cache all entries "around" given index (as much as block cache may hold). */
1626 bool filelist_file_cache_block(struct FileList *filelist, const int index)
1627 {
1628         FileListEntryCache *cache = &filelist->filelist_cache;
1629         const size_t cache_size = cache->size;
1630
1631         const int nbr_entries = filelist->filelist.nbr_entries_filtered;
1632         int start_index = max_ii(0, index - (cache_size / 2));
1633         int end_index = min_ii(nbr_entries, index + (cache_size / 2));
1634         int i;
1635
1636         if ((index < 0) || (index >= nbr_entries)) {
1637 //              printf("Wrong index %d ([%d:%d])", index, 0, nbr_entries);
1638                 return false;
1639         }
1640
1641         /* Maximize cached range! */
1642         if ((end_index - start_index) < cache_size) {
1643                 if (start_index == 0) {
1644                         end_index = min_ii(nbr_entries, start_index + cache_size);
1645                 }
1646                 else if (end_index == nbr_entries) {
1647                         start_index = max_ii(0, end_index - cache_size);
1648                 }
1649         }
1650
1651         BLI_assert((end_index - start_index) <= cache_size) ;
1652
1653 //      printf("%s: [%d:%d] around index %d (current cache: [%d:%d])\n", __func__,
1654 //             start_index, end_index, index, cache->block_start_index, cache->block_end_index);
1655
1656         /* If we have something to (re)cache... */
1657         if ((start_index != cache->block_start_index) || (end_index != cache->block_end_index)) {
1658                 if ((start_index >= cache->block_end_index) || (end_index <= cache->block_start_index)) {
1659                         int size1 = cache->block_end_index - cache->block_start_index;
1660                         int size2 = 0;
1661                         int idx1 = cache->block_cursor, idx2 = 0;
1662
1663 //                      printf("Full Recaching!\n");
1664
1665                         if (cache->flags & FLC_PREVIEWS_ACTIVE) {
1666                                 filelist_cache_previews_clear(cache);
1667                         }
1668
1669                         if (idx1 + size1 > cache_size) {
1670                                 size2 = idx1 + size1 - cache_size;
1671                                 size1 -= size2;
1672                                 filelist_file_cache_block_release(filelist, size2, idx2);
1673                         }
1674                         filelist_file_cache_block_release(filelist, size1, idx1);
1675
1676                         cache->block_start_index = cache->block_end_index = cache->block_cursor = 0;
1677
1678                         /* New cached block does not overlap existing one, simple. */
1679                         if (!filelist_file_cache_block_create(filelist, start_index, end_index - start_index, 0)) {
1680                                 return false;
1681                         }
1682
1683                         cache->block_start_index = start_index;
1684                         cache->block_end_index = end_index;
1685                 }
1686                 else {
1687 //                      printf("Partial Recaching!\n");
1688
1689                         /* At this point, we know we keep part of currently cached entries, so update previews if needed,
1690                          * and remove everything from working queue - we'll add all newly needed entries at the end. */
1691                         if (cache->flags & FLC_PREVIEWS_ACTIVE) {
1692                                 filelist_cache_previews_update(filelist);
1693                                 filelist_cache_previews_clear(cache);
1694                         }
1695
1696 //                      printf("\tpreview cleaned up...\n");
1697
1698                         if (start_index > cache->block_start_index) {
1699                                 int size1 = start_index - cache->block_start_index;
1700                                 int size2 = 0;
1701                                 int idx1 = cache->block_cursor, idx2 = 0;
1702
1703 //                              printf("\tcache releasing: [%d:%d] (%d, %d)\n", cache->block_start_index, cache->block_start_index + size1, cache->block_cursor, size1);
1704
1705                                 if (idx1 + size1 > cache_size) {
1706                                         size2 = idx1 + size1 - cache_size;
1707                                         size1 -= size2;
1708                                         filelist_file_cache_block_release(filelist, size2, idx2);
1709                                 }
1710                                 filelist_file_cache_block_release(filelist, size1, idx1);
1711
1712                                 cache->block_cursor = (idx1 + size1 + size2) % cache_size;
1713                                 cache->block_start_index = start_index;
1714                         }
1715                         if (end_index < cache->block_end_index) {
1716                                 int size1 = cache->block_end_index - end_index;
1717                                 int size2 = 0;
1718                                 int idx1, idx2 = 0;
1719
1720 //                              printf("\tcache releasing: [%d:%d] (%d)\n", cache->block_end_index - size1, cache->block_end_index, cache->block_cursor);
1721
1722                                 idx1 = (cache->block_cursor + end_index - cache->block_start_index) % cache_size;
1723                                 if (idx1 + size1 > cache_size) {
1724                                         size2 = idx1 + size1 - cache_size;
1725                                         size1 -= size2;
1726                                         filelist_file_cache_block_release(filelist, size2, idx2);
1727                                 }
1728                                 filelist_file_cache_block_release(filelist, size1, idx1);
1729
1730                                 cache->block_end_index = end_index;
1731                         }
1732
1733 //                      printf("\tcache cleaned up...\n");
1734
1735                         if (start_index < cache->block_start_index) {
1736                                 /* Add (request) needed entries before already cached ones. */
1737                                 /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */
1738                                 int size1 = cache->block_start_index - start_index;
1739                                 int size2 = 0;
1740                                 int idx1, idx2;
1741
1742                                 if (size1 > cache->block_cursor) {
1743                                         size2 = size1;
1744                                         size1 -= cache->block_cursor;
1745                                         size2 -= size1;
1746                                         idx2 = 0;
1747                                         idx1 = cache_size - size1;
1748                                 }
1749                                 else {
1750                                         idx1 = cache->block_cursor - size1;
1751                                 }
1752
1753                                 if (size2) {
1754                                         if (!filelist_file_cache_block_create(filelist, start_index + size1, size2, idx2)) {
1755                                                 return false;
1756                                         }
1757                                 }
1758                                 if (!filelist_file_cache_block_create(filelist, start_index, size1, idx1)) {
1759                                         return false;
1760                                 }
1761
1762                                 cache->block_cursor = idx1;
1763                                 cache->block_start_index = start_index;
1764                         }
1765 //                      printf("\tstart-extended...\n");
1766                         if (end_index > cache->block_end_index) {
1767                                 /* Add (request) needed entries after already cached ones. */
1768                                 /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */
1769                                 int size1 = end_index - cache->block_end_index;
1770                                 int size2 = 0;
1771                                 int idx1, idx2;
1772
1773                                 idx1 = (cache->block_cursor + end_index - cache->block_start_index - size1) % cache_size;
1774                                 if ((idx1 + size1) > cache_size) {
1775                                         size2 = size1;
1776                                         size1 = cache_size - idx1;
1777                                         size2 -= size1;
1778                                         idx2 = 0;
1779                                 }
1780
1781                                 if (size2) {
1782                                         if (!filelist_file_cache_block_create(filelist, end_index - size2, size2, idx2)) {
1783                                                 return false;
1784                                         }
1785                                 }
1786                                 if (!filelist_file_cache_block_create(filelist, end_index - size1 - size2, size1, idx1)) {
1787                                         return false;
1788                                 }
1789
1790                                 cache->block_end_index = end_index;
1791                         }
1792
1793 //                      printf("\tend-extended...\n");
1794                 }
1795         }
1796         else if ((cache->block_center_index != index) && (cache->flags & FLC_PREVIEWS_ACTIVE)) {
1797                 /* We try to always preview visible entries first, so 'restart' preview background task. */
1798                 filelist_cache_previews_update(filelist);
1799                 filelist_cache_previews_clear(cache);
1800         }
1801
1802 //      printf("Re-queueing previews...\n");
1803
1804         /* Note we try to preview first images around given index - i.e. assumed visible ones. */
1805         if (cache->flags & FLC_PREVIEWS_ACTIVE) {
1806                 for (i = 0; ((index + i) < end_index) || ((index - i) >= start_index); i++) {
1807                         if ((index - i) >= start_index) {
1808                                 const int idx = (cache->block_cursor + (index - start_index) - i) % cache_size;
1809                                 filelist_cache_previews_push(filelist, cache->block_entries[idx], index - i);
1810                         }
1811                         if ((index + i) < end_index) {
1812                                 const int idx = (cache->block_cursor + (index - start_index) + i) % cache_size;
1813                                 filelist_cache_previews_push(filelist, cache->block_entries[idx], index + i);
1814                         }
1815                 }
1816         }
1817
1818         cache->block_center_index = index;
1819
1820 //      printf("%s Finished!\n", __func__);
1821
1822         return true;
1823 }
1824
1825 void filelist_cache_previews_set(FileList *filelist, const bool use_previews)
1826 {
1827         FileListEntryCache *cache = &filelist->filelist_cache;
1828
1829         if (use_previews == ((cache->flags & FLC_PREVIEWS_ACTIVE) != 0)) {
1830                 return;
1831         }
1832         /* Do not start preview work while listing, gives nasty flickering! */
1833         else if (use_previews && (filelist->flags & FL_IS_READY)) {
1834                 cache->flags |= FLC_PREVIEWS_ACTIVE;
1835
1836                 BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL));
1837
1838 //              printf("%s: Init Previews...\n", __func__);
1839
1840                 /* No need to populate preview queue here, filelist_file_cache_block() handles this. */
1841         }
1842         else {
1843 //              printf("%s: Clear Previews...\n", __func__);
1844
1845                 filelist_cache_previews_free(cache);
1846         }
1847 }
1848
1849 bool filelist_cache_previews_update(FileList *filelist)
1850 {
1851         FileListEntryCache *cache = &filelist->filelist_cache;
1852         TaskPool *pool = cache->previews_pool;
1853         bool changed = false;
1854
1855         if (!pool) {
1856                 return changed;
1857         }
1858
1859 //      printf("%s: Update Previews...\n", __func__);
1860
1861         while (!BLI_thread_queue_is_empty(cache->previews_done)) {
1862                 FileListEntryPreview *preview = BLI_thread_queue_pop(cache->previews_done);
1863                 FileDirEntry *entry;
1864
1865                 /* Paranoid (should never happen currently since we consume this queue from a single thread), but... */
1866                 if (!preview) {
1867                         continue;
1868                 }
1869                 /* entry might have been removed from cache in the mean time, we do not want to cache it again here. */
1870                 entry = filelist_file_ex(filelist, preview->index, false);
1871
1872 //              printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1873
1874                 if (preview->img) {
1875                         /* Due to asynchronous process, a preview for a given image may be generated several times, i.e.
1876                          * entry->image may already be set at this point. */
1877                         if (entry && !entry->image) {
1878                                 entry->image = preview->img;
1879                                 changed = true;
1880                         }
1881                         else {
1882                                 IMB_freeImBuf(preview->img);
1883                         }
1884                 }
1885                 else if (entry) {
1886                         /* We want to avoid re-processing this entry continuously!
1887                          * Note that, since entries only live in cache, preview will be retried quite often anyway. */
1888                         entry->flags |= FILE_ENTRY_INVALID_PREVIEW;
1889                 }
1890
1891                 MEM_freeN(preview);
1892         }
1893
1894         return changed;
1895 }
1896
1897 bool filelist_cache_previews_running(FileList *filelist)
1898 {
1899         FileListEntryCache *cache = &filelist->filelist_cache;
1900
1901         return (cache->previews_pool != NULL);
1902 }
1903
1904 /* would recognize .blend as well */
1905 static bool file_is_blend_backup(const char *str)
1906 {
1907         const size_t a = strlen(str);
1908         size_t b = 7;
1909         bool retval = 0;
1910
1911         if (a == 0 || b >= a) {
1912                 /* pass */
1913         }
1914         else {
1915                 const char *loc;
1916                 
1917                 if (a > b + 1)
1918                         b++;
1919                 
1920                 /* allow .blend1 .blend2 .blend32 */
1921                 loc = BLI_strcasestr(str + a - b, ".blend");
1922                 
1923                 if (loc)
1924                         retval = 1;
1925         }
1926         
1927         return (retval);
1928 }
1929
1930 /* TODO: Maybe we should move this to BLI? On the other hand, it's using defines from spacefile area, so not sure... */
1931 int ED_path_extension_type(const char *path)
1932 {
1933         if (BLO_has_bfile_extension(path)) {
1934                 return FILE_TYPE_BLENDER;
1935         }
1936         else if (file_is_blend_backup(path)) {
1937                 return FILE_TYPE_BLENDER_BACKUP;
1938         }
1939         else if (BLI_testextensie(path, ".app")) {
1940                 return FILE_TYPE_APPLICATIONBUNDLE;
1941         }
1942         else if (BLI_testextensie(path, ".py")) {
1943                 return FILE_TYPE_PYSCRIPT;
1944         }
1945         else if (BLI_testextensie_n(path, ".txt", ".glsl", ".osl", ".data", NULL)) {
1946                 return FILE_TYPE_TEXT;
1947         }
1948         else if (BLI_testextensie_n(path, ".ttf", ".ttc", ".pfb", ".otf", ".otc", NULL)) {
1949                 return FILE_TYPE_FTFONT;
1950         }
1951         else if (BLI_testextensie(path, ".btx")) {
1952                 return FILE_TYPE_BTX;
1953         }
1954         else if (BLI_testextensie(path, ".dae")) {
1955                 return FILE_TYPE_COLLADA;
1956         }
1957         else if (BLI_testextensie(path, ".abc")) {
1958                 return FILE_TYPE_ALEMBIC;
1959         }
1960         else if (BLI_testextensie_array(path, imb_ext_image) ||
1961                  (G.have_quicktime && BLI_testextensie_array(path, imb_ext_image_qt)))
1962         {
1963                 return FILE_TYPE_IMAGE;
1964         }
1965         else if (BLI_testextensie(path, ".ogg")) {
1966                 if (IMB_isanim(path)) {
1967                         return FILE_TYPE_MOVIE;
1968                 }
1969                 else {
1970                         return FILE_TYPE_SOUND;
1971                 }
1972         }
1973         else if (BLI_testextensie_array(path, imb_ext_movie)) {
1974                 return FILE_TYPE_MOVIE;
1975         }
1976         else if (BLI_testextensie_array(path, imb_ext_audio)) {
1977                 return FILE_TYPE_SOUND;
1978         }
1979         return 0;
1980 }
1981
1982 static int file_extension_type(const char *dir, const char *relpath)
1983 {
1984         char path[FILE_MAX];
1985         BLI_join_dirfile(path, sizeof(path), dir, relpath);
1986         return ED_path_extension_type(path);
1987 }
1988
1989 int ED_file_extension_icon(const char *path)
1990 {
1991         const int type = ED_path_extension_type(path);
1992         
1993         switch (type) {
1994                 case FILE_TYPE_BLENDER:
1995                         return ICON_FILE_BLEND;
1996                 case FILE_TYPE_BLENDER_BACKUP:
1997                         return ICON_FILE_BACKUP;
1998                 case FILE_TYPE_IMAGE:
1999                         return ICON_FILE_IMAGE;
2000                 case FILE_TYPE_MOVIE:
2001                         return ICON_FILE_MOVIE;
2002                 case FILE_TYPE_PYSCRIPT:
2003                         return ICON_FILE_SCRIPT;
2004                 case FILE_TYPE_SOUND:
2005                         return ICON_FILE_SOUND;
2006                 case FILE_TYPE_FTFONT:
2007                         return ICON_FILE_FONT;
2008                 case FILE_TYPE_BTX:
2009                         return ICON_FILE_BLANK;
2010                 case FILE_TYPE_COLLADA:
2011                         return ICON_FILE_BLANK;
2012                 case FILE_TYPE_ALEMBIC:
2013                         return ICON_FILE_BLANK;
2014                 case FILE_TYPE_TEXT:
2015                         return ICON_FILE_TEXT;
2016                 default:
2017                         return ICON_FILE_BLANK;
2018         }
2019 }
2020
2021 int filelist_empty(struct FileList *filelist)
2022 {
2023         return (filelist->filelist.nbr_entries == 0);
2024 }
2025
2026 unsigned int filelist_entry_select_set(
2027         const FileList *filelist, const FileDirEntry *entry, FileSelType select, unsigned int flag, FileCheckType check)
2028 {
2029         /* Default NULL pointer if not found is fine here! */
2030         void **es_p = BLI_ghash_lookup_p(filelist->selection_state, entry->uuid);
2031         unsigned int entry_flag = es_p ? GET_UINT_FROM_POINTER(*es_p) : 0;
2032         const unsigned int org_entry_flag = entry_flag;
2033
2034         BLI_assert(entry);
2035         BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL));
2036
2037         if (((check == CHECK_ALL)) ||
2038             ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) ||
2039             ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR)))
2040         {
2041                 switch (select) {
2042                         case FILE_SEL_REMOVE:
2043                                 entry_flag &= ~flag;
2044                                 break;
2045                         case FILE_SEL_ADD:
2046                                 entry_flag |= flag;
2047                                 break;
2048                         case FILE_SEL_TOGGLE:
2049                                 entry_flag ^= flag;
2050                                 break;
2051                 }
2052         }
2053
2054         if (entry_flag != org_entry_flag) {
2055                 if (es_p) {
2056                         if (entry_flag) {
2057                                 *es_p = SET_UINT_IN_POINTER(entry_flag);
2058                         }
2059                         else {
2060                                 BLI_ghash_remove(filelist->selection_state, entry->uuid, MEM_freeN, NULL);
2061                         }
2062                 }
2063                 else if (entry_flag) {
2064                         void *key = MEM_mallocN(sizeof(entry->uuid), __func__);
2065                         memcpy(key, entry->uuid, sizeof(entry->uuid));
2066                         BLI_ghash_insert(filelist->selection_state, key, SET_UINT_IN_POINTER(entry_flag));
2067                 }
2068         }
2069
2070         return entry_flag;
2071 }
2072
2073 void filelist_entry_select_index_set(FileList *filelist, const int index, FileSelType select, unsigned int flag, FileCheckType check)
2074 {
2075         FileDirEntry *entry = filelist_file(filelist, index);
2076
2077         if (entry) {
2078                 filelist_entry_select_set(filelist, entry, select, flag, check);
2079         }
2080 }
2081
2082 void filelist_entries_select_index_range_set(
2083         FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check)
2084 {
2085         /* select all valid files between first and last indicated */
2086         if ((sel->first >= 0) && (sel->first < filelist->filelist.nbr_entries_filtered) &&
2087             (sel->last >= 0) && (sel->last < filelist->filelist.nbr_entries_filtered))
2088         {
2089                 int current_file;
2090                 for (current_file = sel->first; current_file <= sel->last; current_file++) {
2091                         filelist_entry_select_index_set(filelist, current_file, select, flag, check);
2092                 }
2093         }
2094 }
2095
2096 unsigned int filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileCheckType check)
2097 {
2098         BLI_assert(entry);
2099         BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL));
2100
2101         if (((check == CHECK_ALL)) ||
2102             ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) ||
2103             ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR)))
2104         {
2105                 /* Default NULL pointer if not found is fine here! */
2106                 return GET_UINT_FROM_POINTER(BLI_ghash_lookup(filelist->selection_state, entry->uuid));
2107         }
2108
2109         return 0;
2110 }
2111
2112 unsigned int filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check)
2113 {
2114         FileDirEntry *entry = filelist_file(filelist, index);
2115
2116         if (entry) {
2117                 return filelist_entry_select_get(filelist, entry, check);
2118         }
2119
2120         return 0;
2121 }
2122
2123 /* WARNING! dir must be FILE_MAX_LIBEXTRA long! */
2124 bool filelist_islibrary(struct FileList *filelist, char *dir, char **group)
2125 {
2126         return BLO_library_path_explode(filelist->filelist.root, dir, group, NULL);
2127 }
2128
2129 static int groupname_to_code(const char *group)
2130 {
2131         char buf[BLO_GROUP_MAX];
2132         char *lslash;
2133
2134         BLI_assert(group);
2135
2136         BLI_strncpy(buf, group, sizeof(buf));
2137         lslash = (char *)BLI_last_slash(buf);
2138         if (lslash)
2139                 lslash[0] = '\0';
2140
2141         return buf[0] ? BKE_idcode_from_name(buf) : 0;
2142 }
2143
2144 static unsigned int groupname_to_filter_id(const char *group)
2145 {
2146         int id_code = groupname_to_code(group);
2147
2148         return BKE_idcode_to_idfilter(id_code);
2149 }
2150
2151 /**
2152  * From here, we are in 'Job Context', i.e. have to be careful about sharing stuff between background working thread
2153  * and main one (used by UI among other things).
2154  */
2155 typedef struct TodoDir {
2156         int level;
2157         char *dir;
2158 } TodoDir;
2159
2160 static int filelist_readjob_list_dir(
2161         const char *root, ListBase *entries, const char *filter_glob,
2162         const bool do_lib, const char *main_name, const bool skip_currpar)
2163 {
2164         struct direntry *files;
2165         int nbr_files, nbr_entries = 0;
2166
2167         nbr_files = BLI_filelist_dir_contents(root, &files);
2168         if (files) {
2169                 int i = nbr_files;
2170                 while (i--) {
2171                         FileListInternEntry *entry;
2172
2173                         if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) {
2174                                 continue;
2175                         }
2176
2177                         entry = MEM_callocN(sizeof(*entry), __func__);
2178                         entry->relpath = MEM_dupallocN(files[i].relname);
2179                         entry->st = files[i].s;
2180
2181                         /* Set file type. */
2182                         if (S_ISDIR(files[i].s.st_mode)) {
2183                                 entry->typeflag = FILE_TYPE_DIR;
2184                         }
2185                         else if (do_lib && BLO_has_bfile_extension(entry->relpath)) {
2186                                 /* If we are considering .blend files as libs, promote them to directory status. */
2187                                 char name[FILE_MAX];
2188
2189                                 entry->typeflag = FILE_TYPE_BLENDER;
2190
2191                                 BLI_join_dirfile(name, sizeof(name), root, entry->relpath);
2192
2193                                 /* prevent current file being used as acceptable dir */
2194                                 if (BLI_path_cmp(main_name, name) != 0) {
2195                                         entry->typeflag |= FILE_TYPE_DIR;
2196                                 }
2197                         }
2198                         /* Otherwise, do not check extensions for directories! */
2199                         else if (!(entry->typeflag & FILE_TYPE_DIR)) {
2200                                 entry->typeflag = file_extension_type(root, entry->relpath);
2201                                 if (filter_glob[0] && BLI_testextensie_glob(entry->relpath, filter_glob)) {
2202                                         entry->typeflag |= FILE_TYPE_OPERATOR;
2203                                 }
2204                         }
2205
2206                         BLI_addtail(entries, entry);
2207                         nbr_entries++;
2208                 }
2209                 BLI_filelist_free(files, nbr_files);
2210         }
2211         return nbr_entries;
2212 }
2213
2214 static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar)
2215 {
2216         FileListInternEntry *entry;
2217         LinkNode *ln, *names;
2218         int i, nnames, idcode = 0, nbr_entries = 0;
2219         char dir[FILE_MAX_LIBEXTRA], *group;
2220         bool ok;
2221
2222         struct BlendHandle *libfiledata = NULL;
2223
2224         /* name test */
2225         ok = BLO_library_path_explode(root, dir, &group, NULL);
2226         if (!ok) {
2227                 return nbr_entries;
2228         }
2229
2230         /* there we go */
2231         libfiledata = BLO_blendhandle_from_file(dir, NULL);
2232         if (libfiledata == NULL) {
2233                 return nbr_entries;
2234         }
2235
2236         /* memory for strings is passed into filelist[i].entry->relpath and freed in filelist_entry_free. */
2237         if (group) {
2238                 idcode = groupname_to_code(group);
2239                 names = BLO_blendhandle_get_datablock_names(libfiledata, idcode, &nnames);
2240         }
2241         else {
2242                 names = BLO_blendhandle_get_linkable_groups(libfiledata);
2243                 nnames = BLI_linklist_count(names);
2244         }
2245
2246         BLO_blendhandle_close(libfiledata);
2247
2248         if (!skip_currpar) {
2249                 entry = MEM_callocN(sizeof(*entry), __func__);
2250                 entry->relpath = BLI_strdup(FILENAME_PARENT);
2251                 entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR);
2252                 BLI_addtail(entries, entry);
2253                 nbr_entries++;
2254         }
2255
2256         for (i = 0, ln = names; i < nnames; i++, ln = ln->next) {
2257                 const char *blockname = ln->link;
2258
2259                 entry = MEM_callocN(sizeof(*entry), __func__);
2260                 entry->relpath = BLI_strdup(blockname);
2261                 entry->typeflag |= FILE_TYPE_BLENDERLIB;
2262                 if (!(group && idcode)) {
2263                         entry->typeflag |= FILE_TYPE_DIR;
2264                         entry->blentype = groupname_to_code(blockname);
2265                 }
2266                 else {
2267                         entry->blentype = idcode;
2268                 }
2269                 BLI_addtail(entries, entry);
2270                 nbr_entries++;
2271         }
2272
2273         BLI_linklist_free(names, free);
2274
2275         return nbr_entries;
2276 }
2277
2278 #if 0
2279 /* Kept for reference here, in case we want to add back that feature later. We do not need it currently. */
2280 /* Code ***NOT*** updated for job stuff! */
2281 static void filelist_readjob_main_rec(struct FileList *filelist)
2282 {
2283         ID *id;
2284         FileDirEntry *files, *firstlib = NULL;
2285         ListBase *lb;
2286         int a, fake, idcode, ok, totlib, totbl;
2287         
2288         // filelist->type = FILE_MAIN; // XXX TODO: add modes to filebrowser
2289
2290         BLI_assert(filelist->filelist.entries == NULL);
2291
2292         if (filelist->filelist.root[0] == '/') filelist->filelist.root[0] = '\0';
2293
2294         if (filelist->filelist.root[0]) {
2295                 idcode = groupname_to_code(filelist->filelist.root);
2296                 if (idcode == 0) filelist->filelist.root[0] = '\0';
2297         }
2298
2299         if (filelist->dir[0] == 0) {
2300                 /* make directories */
2301 #ifdef WITH_FREESTYLE
2302                 filelist->filelist.nbr_entries = 24;
2303 #else
2304                 filelist->filelist.nbr_entries = 23;
2305 #endif
2306                 filelist_resize(filelist, filelist->filelist.nbr_entries);
2307
2308                 for (a = 0; a < filelist->filelist.nbr_entries; a++) {
2309                         filelist->filelist.entries[a].typeflag |= FILE_TYPE_DIR;
2310                 }
2311
2312                 filelist->filelist.entries[0].entry->relpath = BLI_strdup(FILENAME_PARENT);
2313                 filelist->filelist.entries[1].entry->relpath = BLI_strdup("Scene");
2314                 filelist->filelist.entries[2].entry->relpath = BLI_strdup("Object");
2315                 filelist->filelist.entries[3].entry->relpath = BLI_strdup("Mesh");
2316                 filelist->filelist.entries[4].entry->relpath = BLI_strdup("Curve");
2317                 filelist->filelist.entries[5].entry->relpath = BLI_strdup("Metaball");
2318                 filelist->filelist.entries[6].entry->relpath = BLI_strdup("Material");
2319                 filelist->filelist.entries[7].entry->relpath = BLI_strdup("Texture");
2320                 filelist->filelist.entries[8].entry->relpath = BLI_strdup("Image");
2321                 filelist->filelist.entries[9].entry->relpath = BLI_strdup("Ika");
2322                 filelist->filelist.entries[10].entry->relpath = BLI_strdup("Wave");
2323                 filelist->filelist.entries[11].entry->relpath = BLI_strdup("Lattice");
2324                 filelist->filelist.entries[12].entry->relpath = BLI_strdup("Lamp");
2325                 filelist->filelist.entries[13].entry->relpath = BLI_strdup("Camera");
2326                 filelist->filelist.entries[14].entry->relpath = BLI_strdup("Ipo");
2327                 filelist->filelist.entries[15].entry->relpath = BLI_strdup("World");
2328                 filelist->filelist.entries[16].entry->relpath = BLI_strdup("Screen");
2329                 filelist->filelist.entries[17].entry->relpath = BLI_strdup("VFont");
2330                 filelist->filelist.entries[18].entry->relpath = BLI_strdup("Text");
2331                 filelist->filelist.entries[19].entry->relpath = BLI_strdup("Armature");
2332                 filelist->filelist.entries[20].entry->relpath = BLI_strdup("Action");
2333                 filelist->filelist.entries[21].entry->relpath = BLI_strdup("NodeTree");
2334                 filelist->filelist.entries[22].entry->relpath = BLI_strdup("Speaker");
2335 #ifdef WITH_FREESTYLE
2336                 filelist->filelist.entries[23].entry->relpath = BLI_strdup("FreestyleLineStyle");
2337 #endif
2338         }
2339         else {
2340                 /* make files */
2341                 idcode = groupname_to_code(filelist->filelist.root);
2342
2343                 lb = which_libbase(G.main, idcode);
2344                 if (lb == NULL) return;
2345
2346                 filelist->filelist.nbr_entries = 0;
2347                 for (id = lb->first; id; id = id->next) {
2348                         if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') {
2349                                 filelist->filelist.nbr_entries++;
2350                         }
2351                 }
2352
2353                 /* XXX TODO: if databrowse F4 or append/link filelist->flags & FLF_HIDE_PARENT has to be set */
2354                 if (!(filelist->filter_data.flags & FLF_HIDE_PARENT))
2355                         filelist->filelist.nbr_entries++;
2356
2357                 if (filelist->filelist.nbr_entries > 0) {
2358                         filelist_resize(filelist, filelist->filelist.nbr_entries);
2359                 }
2360
2361                 files = filelist->filelist.entries;
2362                 
2363                 if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) {
2364                         files->entry->relpath = BLI_strdup(FILENAME_PARENT);
2365                         files->typeflag |= FILE_TYPE_DIR;
2366
2367                         files++;
2368                 }
2369
2370                 totlib = totbl = 0;
2371                 for (id = lb->first; id; id = id->next) {
2372                         ok = 1;
2373                         if (ok) {
2374                                 if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') {
2375                                         if (id->lib == NULL) {
2376                                                 files->entry->relpath = BLI_strdup(id->name + 2);
2377                                         }
2378                                         else {
2379                                                 char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3];
2380                                                 BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->name, id->name + 2);
2381                                                 files->entry->relpath = BLI_strdup(relname);
2382                                         }
2383 //                                      files->type |= S_IFREG;
2384 #if 0               /* XXX TODO show the selection status of the objects */
2385                                         if (!filelist->has_func) { /* F4 DATA BROWSE */
2386                                                 if (idcode == ID_OB) {
2387                                                         if ( ((Object *)id)->flag & SELECT) files->entry->selflag |= FILE_SEL_SELECTED;
2388                                                 }
2389                                                 else if (idcode == ID_SCE) {
2390                                                         if ( ((Scene *)id)->r.scemode & R_BG_RENDER) files->entry->selflag |= FILE_SEL_SELECTED;
2391                                                 }
2392                                         }
2393 #endif
2394 //                                      files->entry->nr = totbl + 1;
2395                                         files->entry->poin = id;
2396                                         fake = id->flag & LIB_FAKEUSER;
2397                                         if (idcode == ID_MA || idcode == ID_TE || idcode == ID_LA || idcode == ID_WO || idcode == ID_IM) {
2398                                                 files->typeflag |= FILE_TYPE_IMAGE;
2399                                         }
2400 //                                      if      (id->lib && fake) BLI_snprintf(files->extra, sizeof(files->entry->extra), "LF %d",    id->us);
2401 //                                      else if (id->lib)         BLI_snprintf(files->extra, sizeof(files->entry->extra), "L    %d",  id->us);
2402 //                                      else if (fake)            BLI_snprintf(files->extra, sizeof(files->entry->extra), "F    %d",  id->us);
2403 //                                      else                      BLI_snprintf(files->extra, sizeof(files->entry->extra), "      %d", id->us);
2404
2405                                         if (id->lib) {
2406                                                 if (totlib == 0) firstlib = files;
2407                                                 totlib++;
2408                                         }
2409
2410                                         files++;
2411                                 }
2412                                 totbl++;
2413                         }
2414                 }
2415
2416                 /* only qsort of library blocks */
2417                 if (totlib > 1) {
2418                         qsort(firstlib, totlib, sizeof(*files), compare_name);
2419                 }
2420         }
2421 }
2422 #endif
2423
2424 static void filelist_readjob_do(
2425         const bool do_lib,
2426         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2427 {
2428         ListBase entries = {0};
2429         BLI_Stack *todo_dirs;
2430         TodoDir *td_dir;
2431         char dir[FILE_MAX_LIBEXTRA];
2432         char filter_glob[64];  /* TODO should be define! */
2433         const char *root = filelist->filelist.root;
2434         const int max_recursion = filelist->max_recursion;
2435         int nbr_done_dirs = 0, nbr_todo_dirs = 1;
2436
2437 //      BLI_assert(filelist->filtered == NULL);
2438         BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == 0));
2439
2440         todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__);
2441         td_dir = BLI_stack_push_r(todo_dirs);
2442         td_dir->level = 1;
2443
2444         BLI_strncpy(dir, filelist->filelist.root, sizeof(dir));
2445         BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob));
2446
2447         BLI_cleanup_dir(main_name, dir);
2448         td_dir->dir = BLI_strdup(dir);
2449
2450         while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) {
2451                 FileListInternEntry *entry;
2452                 int nbr_entries = 0;
2453                 bool is_lib = do_lib;
2454
2455                 char *subdir;
2456                 char rel_subdir[FILE_MAX_LIBEXTRA];
2457                 int recursion_level;
2458                 bool skip_currpar;
2459
2460                 td_dir = BLI_stack_peek(todo_dirs);
2461                 subdir = td_dir->dir;
2462                 recursion_level = td_dir->level;
2463                 skip_currpar = (recursion_level > 1);
2464
2465                 BLI_stack_discard(todo_dirs);
2466
2467                 /* ARRRG! We have to be very careful *not to use* common BLI_path_util helpers over entry->relpath itself
2468                  * (nor any path containing it), since it may actually be a datablock name inside .blend file,
2469                  * which can have slashes and backslashes! See T46827.
2470                  * Note that in the end, this means we 'cache' valid relative subdir once here, this is actually better. */
2471                 BLI_strncpy(rel_subdir, subdir, sizeof(rel_subdir));
2472                 BLI_cleanup_dir(root, rel_subdir);
2473                 BLI_path_rel(rel_subdir, root);
2474
2475                 if (do_lib) {
2476                         nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar);
2477                 }
2478                 if (!nbr_entries) {
2479                         is_lib = false;
2480                         nbr_entries = filelist_readjob_list_dir(subdir, &entries, filter_glob, do_lib, main_name, skip_currpar);
2481                 }
2482
2483                 for (entry = entries.first; entry; entry = entry->next) {
2484                         BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath);
2485
2486                         /* Generate our entry uuid. Abusing uuid as an uint32, shall be more than enough here,
2487                          * things would crash way before we overflow that counter!
2488                          * Using an atomic operation to avoid having to lock thread...
2489                          * Note that we do not really need this here currently, since there is a single listing thread, but better
2490                          * remain consistent about threading! */
2491                         *((uint32_t *)entry->uuid) = atomic_add_uint32((uint32_t *)filelist->filelist_intern.curr_uuid, 1);
2492
2493                         /* Only thing we change in direntry here, so we need to free it first. */
2494                         MEM_freeN(entry->relpath);
2495                         entry->relpath = BLI_strdup(dir + 2);  /* + 2 to remove '//' added by BLI_path_rel to rel_subdir */
2496                         entry->name = BLI_strdup(fileentry_uiname(root, entry->relpath, entry->typeflag, dir));
2497
2498                         /* Here we decide whether current filedirentry is to be listed too, or not. */
2499                         if (max_recursion && (is_lib || (recursion_level <= max_recursion))) {
2500                                 if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) {
2501                                         /* Skip... */
2502                                 }
2503                                 else if (!is_lib && (recursion_level >= max_recursion) &&
2504                                          ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0))
2505                                 {
2506                                         /* Do not recurse in real directories in this case, only in .blend libs. */
2507                                 }
2508                                 else {
2509                                         /* We have a directory we want to list, add it to todo list! */
2510                                         BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath);
2511                                         BLI_cleanup_dir(main_name, dir);
2512                                         td_dir = BLI_stack_push_r(todo_dirs);
2513                                         td_dir->level = recursion_level + 1;
2514                                         td_dir->dir = BLI_strdup(dir);
2515                                         nbr_todo_dirs++;
2516                                 }
2517                         }
2518                 }
2519
2520                 if (nbr_entries) {
2521                         BLI_mutex_lock(lock);
2522
2523                         *do_update = true;
2524
2525                         BLI_movelisttolist(&filelist->filelist.entries, &entries);
2526                         filelist->filelist.nbr_entries += nbr_entries;
2527
2528                         BLI_mutex_unlock(lock);
2529                 }
2530
2531                 nbr_done_dirs++;
2532                 *progress = (float)nbr_done_dirs / (float)nbr_todo_dirs;
2533                 MEM_freeN(subdir);
2534         }
2535
2536         /* If we were interrupted by stop, stack may not be empty and we need to free pending dir paths. */
2537         while (!BLI_stack_is_empty(todo_dirs)) {
2538                 td_dir = BLI_stack_peek(todo_dirs);
2539                 MEM_freeN(td_dir->dir);
2540                 BLI_stack_discard(todo_dirs);
2541         }
2542         BLI_stack_free(todo_dirs);
2543 }
2544
2545 static void filelist_readjob_dir(
2546         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2547 {
2548         filelist_readjob_do(false, filelist, main_name, stop, do_update, progress, lock);
2549 }
2550
2551 static void filelist_readjob_lib(
2552         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2553 {
2554         filelist_readjob_do(true, filelist, main_name, stop, do_update, progress, lock);
2555 }
2556
2557 static void filelist_readjob_main(
2558         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2559 {
2560         /* TODO! */
2561         filelist_readjob_dir(filelist, main_name, stop, do_update, progress, lock);
2562 }
2563
2564
2565 typedef struct FileListReadJob {
2566         ThreadMutex lock;
2567         char main_name[FILE_MAX];
2568         struct FileList *filelist;
2569         struct FileList *tmp_filelist;  /* XXX We may use a simpler struct here... just a linked list and root path? */
2570 } FileListReadJob;
2571
2572 static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress)
2573 {
2574         FileListReadJob *flrj = flrjv;
2575
2576 //      printf("START filelist reading (%d files, main thread: %d)\n",
2577 //             flrj->filelist->filelist.nbr_entries, BLI_thread_is_main());
2578
2579         BLI_mutex_lock(&flrj->lock);
2580
2581         BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist);
2582
2583         flrj->tmp_filelist = MEM_dupallocN(flrj->filelist);
2584
2585         BLI_listbase_clear(&flrj->tmp_filelist->filelist.entries);
2586         flrj->tmp_filelist->filelist.nbr_entries = 0;
2587
2588         flrj->tmp_filelist->filelist_intern.filtered = NULL;
2589         BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries);
2590         memset(flrj->tmp_filelist->filelist_intern.curr_uuid, 0, sizeof(flrj->tmp_filelist->filelist_intern.curr_uuid));
2591
2592         flrj->tmp_filelist->libfiledata = NULL;
2593         memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache));
2594         flrj->tmp_filelist->selection_state = NULL;
2595
2596         BLI_mutex_unlock(&flrj->lock);
2597
2598         flrj->tmp_filelist->read_jobf(flrj->tmp_filelist, flrj->main_name, stop, do_update, progress, &flrj->lock);
2599 }
2600
2601 static void filelist_readjob_update(void *flrjv)
2602 {
2603         FileListReadJob *flrj = flrjv;
2604         FileListIntern *fl_intern = &flrj->filelist->filelist_intern;
2605         ListBase new_entries = {NULL};
2606         int nbr_entries, new_nbr_entries = 0;
2607
2608         BLI_movelisttolist(&new_entries, &fl_intern->entries);
2609         nbr_entries = flrj->filelist->filelist.nbr_entries;
2610
2611         BLI_mutex_lock(&flrj->lock);
2612
2613         if (flrj->tmp_filelist->filelist.nbr_entries) {
2614                 /* We just move everything out of 'thread context' into final list. */
2615                 new_nbr_entries = flrj->tmp_filelist->filelist.nbr_entries;
2616                 BLI_movelisttolist(&new_entries, &flrj->tmp_filelist->filelist.entries);
2617                 flrj->tmp_filelist->filelist.nbr_entries = 0;
2618         }
2619
2620         BLI_mutex_unlock(&flrj->lock);
2621
2622         if (new_nbr_entries) {
2623                 /* Do not clear selection cache, we can assume already 'selected' uuids are still valid! */
2624                 filelist_clear_ex(flrj->filelist, true, false);
2625
2626                 flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING);
2627         }
2628
2629         /* if no new_nbr_entries, this is NOP */
2630         BLI_movelisttolist(&fl_intern->entries, &new_entries);
2631         flrj->filelist->filelist.nbr_entries = nbr_entries + new_nbr_entries;
2632 }
2633
2634 static void filelist_readjob_endjob(void *flrjv)
2635 {
2636         FileListReadJob *flrj = flrjv;
2637
2638         /* In case there would be some dangling update... */
2639         filelist_readjob_update(flrjv);
2640
2641         flrj->filelist->flags &= ~FL_IS_PENDING;
2642         flrj->filelist->flags |= FL_IS_READY;
2643 }
2644
2645 static void filelist_readjob_free(void *flrjv)
2646 {
2647         FileListReadJob *flrj = flrjv;
2648
2649 //      printf("END filelist reading (%d files)\n", flrj->filelist->filelist.nbr_entries);
2650
2651         if (flrj->tmp_filelist) {
2652                 /* tmp_filelist shall never ever be filtered! */
2653                 BLI_assert(flrj->tmp_filelist->filelist.nbr_entries == 0);
2654                 BLI_assert(BLI_listbase_is_empty(&flrj->tmp_filelist->filelist.entries));
2655
2656                 filelist_freelib(flrj->tmp_filelist);
2657                 filelist_free(flrj->tmp_filelist);
2658                 MEM_freeN(flrj->tmp_filelist);
2659         }
2660
2661         BLI_mutex_end(&flrj->lock);
2662
2663         MEM_freeN(flrj);
2664 }
2665
2666 void filelist_readjob_start(FileList *filelist, const bContext *C)
2667 {
2668         wmJob *wm_job;
2669         FileListReadJob *flrj;
2670
2671         /* prepare job data */
2672         flrj = MEM_callocN(sizeof(*flrj), __func__);
2673         flrj->filelist = filelist;
2674         BLI_strncpy(flrj->main_name, G.main->name, sizeof(flrj->main_name));
2675
2676         filelist->flags &= ~(FL_FORCE_RESET | FL_IS_READY);
2677         filelist->flags |= FL_IS_PENDING;
2678
2679         BLI_mutex_init(&flrj->lock);
2680
2681         /* setup job */
2682         wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), CTX_wm_area(C), "Listing Dirs...",
2683                              WM_JOB_PROGRESS, WM_JOB_TYPE_FILESEL_READDIR);
2684         WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free);
2685         WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST);
2686         WM_jobs_callbacks(wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob);
2687
2688         /* start the job */
2689         WM_jobs_start(CTX_wm_manager(C), wm_job);
2690 }
2691
2692 void filelist_readjob_stop(wmWindowManager *wm, ScrArea *sa)
2693 {
2694         WM_jobs_kill_type(wm, sa, WM_JOB_TYPE_FILESEL_READDIR);
2695 }
2696
2697 int filelist_readjob_running(wmWindowManager *wm, ScrArea *sa)
2698 {
2699         return WM_jobs_test(wm, sa, WM_JOB_TYPE_FILESEL_READDIR);
2700 }