FileSpace cleanup: make ED_path_extension_type public.
[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_MAXDIR], *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_MAXDIR];
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_TEXT)
924                 return ICON_FILE_TEXT;
925         else if (typeflag & FILE_TYPE_BLENDERLIB) {
926                 const int ret = UI_idcode_icon_get(blentype);
927                 if (ret != ICON_NONE) {
928                         return ret;
929                 }
930         }
931         return is_main ? ICON_FILE_BLANK : ICON_NONE;
932 }
933
934 int filelist_geticon(struct FileList *filelist, const int index, const bool is_main)
935 {
936         FileDirEntry *file = filelist_geticon_get_file(filelist, index);
937
938         return filelist_geticon_ex(file->typeflag, file->blentype, file->relpath, is_main, false);
939 }
940
941 /* ********** Main ********** */
942
943 static void filelist_checkdir_dir(struct FileList *UNUSED(filelist), char *r_dir)
944 {
945         BLI_make_exist(r_dir);
946 }
947
948 static void filelist_checkdir_lib(struct FileList *UNUSED(filelist), char *r_dir)
949 {
950         char dir[FILE_MAXDIR];
951         if (!BLO_library_path_explode(r_dir, dir, NULL, NULL)) {
952                 /* if not a valid library, we need it to be a valid directory! */
953                 BLI_make_exist(r_dir);
954         }
955 }
956
957 static void filelist_checkdir_main(struct FileList *filelist, char *r_dir)
958 {
959         /* TODO */
960         filelist_checkdir_lib(filelist, r_dir);
961 }
962
963 static void filelist_entry_clear(FileDirEntry *entry)
964 {
965         if (entry->name) {
966                 MEM_freeN(entry->name);
967         }
968         if (entry->description) {
969                 MEM_freeN(entry->description);
970         }
971         if (entry->relpath) {
972                 MEM_freeN(entry->relpath);
973         }
974         if (entry->image) {
975                 IMB_freeImBuf(entry->image);
976         }
977         /* For now, consider FileDirEntryRevision::poin as not owned here, so no need to do anything about it */
978
979         if (!BLI_listbase_is_empty(&entry->variants)) {
980                 FileDirEntryVariant *var;
981
982                 for (var = entry->variants.first; var; var = var->next) {
983                         if (var->name) {
984                                 MEM_freeN(var->name);
985                         }
986                         if (var->description) {
987                                 MEM_freeN(var->description);
988                         }
989
990                         if (!BLI_listbase_is_empty(&var->revisions)) {
991                                 FileDirEntryRevision *rev;
992
993                                 for (rev = var->revisions.first; rev; rev = rev->next) {
994                                         if (rev->comment) {
995                                                 MEM_freeN(rev->comment);
996                                         }
997                                 }
998
999                                 BLI_freelistN(&var->revisions);
1000                         }
1001                 }
1002
1003                 /* TODO: tags! */
1004
1005                 BLI_freelistN(&entry->variants);
1006         }
1007         else if (entry->entry) {
1008                 MEM_freeN(entry->entry);
1009         }
1010 }
1011
1012 static void filelist_entry_free(FileDirEntry *entry)
1013 {
1014         filelist_entry_clear(entry);
1015         MEM_freeN(entry);
1016 }
1017
1018 static void filelist_direntryarr_free(FileDirEntryArr *array)
1019 {
1020 #if 0
1021         FileDirEntry *entry, *entry_next;
1022
1023         for (entry = array->entries.first; entry; entry = entry_next) {
1024                 entry_next = entry->next;
1025                 filelist_entry_free(entry);
1026         }
1027         BLI_listbase_clear(&array->entries);
1028 #else
1029         BLI_assert(BLI_listbase_is_empty(&array->entries));
1030 #endif
1031         array->nbr_entries = 0;
1032         array->nbr_entries_filtered = -1;
1033         array->entry_idx_start = -1;
1034         array->entry_idx_end = -1;
1035 }
1036
1037 static void filelist_intern_entry_free(FileListInternEntry *entry)
1038 {
1039         if (entry->relpath) {
1040                 MEM_freeN(entry->relpath);
1041         }
1042         if (entry->name) {
1043                 MEM_freeN(entry->name);
1044         }
1045         MEM_freeN(entry);
1046 }
1047
1048 static void filelist_intern_free(FileListIntern *filelist_intern)
1049 {
1050         FileListInternEntry *entry, *entry_next;
1051
1052         for (entry = filelist_intern->entries.first; entry; entry = entry_next) {
1053                 entry_next = entry->next;
1054                 filelist_intern_entry_free(entry);
1055         }
1056         BLI_listbase_clear(&filelist_intern->entries);
1057
1058         MEM_SAFE_FREE(filelist_intern->filtered);
1059 }
1060
1061 static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata, int UNUSED(threadid))
1062 {
1063         FileListEntryCache *cache = BLI_task_pool_userdata(pool);
1064         FileListEntryPreview *preview = taskdata;
1065
1066         ThumbSource source = 0;
1067
1068 //      printf("%s: Start (%d)...\n", __func__, threadid);
1069
1070 //      printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1071         BLI_assert(preview->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT |
1072                                      FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB));
1073
1074         if (preview->flags & FILE_TYPE_IMAGE) {
1075                 source = THB_SOURCE_IMAGE;
1076         }
1077         else if (preview->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)) {
1078                 source = THB_SOURCE_BLEND;
1079         }
1080         else if (preview->flags & FILE_TYPE_MOVIE) {
1081                 source = THB_SOURCE_MOVIE;
1082         }
1083         else if (preview->flags & FILE_TYPE_FTFONT) {
1084                 source = THB_SOURCE_FONT;
1085         }
1086
1087         IMB_thumb_path_lock(preview->path);
1088         preview->img = IMB_thumb_manage(preview->path, THB_LARGE, source);
1089         IMB_thumb_path_unlock(preview->path);
1090
1091         preview->flags = 0;  /* Used to tell free func to not free anything! */
1092         BLI_thread_queue_push(cache->previews_done, preview);
1093
1094 //      printf("%s: End (%d)...\n", __func__, threadid);
1095 }
1096
1097 static void filelist_cache_preview_freef(TaskPool * __restrict UNUSED(pool), void *taskdata, int UNUSED(threadid))
1098 {
1099         FileListEntryPreview *preview = taskdata;
1100
1101         /* If preview->flag is empty, it means that preview has already been generated and added to done queue,
1102          * we do not own it anymore. */
1103         if (preview->flags) {
1104                 if (preview->img) {
1105                         IMB_freeImBuf(preview->img);
1106                 }
1107                 MEM_freeN(preview);
1108         }
1109 }
1110
1111 static void filelist_cache_preview_ensure_running(FileListEntryCache *cache)
1112 {
1113         if (!cache->previews_pool) {
1114                 TaskScheduler *scheduler = BLI_task_scheduler_get();
1115
1116                 cache->previews_pool = BLI_task_pool_create_background(scheduler, cache);
1117                 cache->previews_done = BLI_thread_queue_init();
1118
1119                 IMB_thumb_locks_acquire();
1120         }
1121 }
1122
1123 static void filelist_cache_previews_clear(FileListEntryCache *cache)
1124 {
1125         FileListEntryPreview *preview;
1126
1127         if (cache->previews_pool) {
1128                 BLI_task_pool_cancel(cache->previews_pool);
1129
1130                 while ((preview = BLI_thread_queue_pop_timeout(cache->previews_done, 0))) {
1131 //                      printf("%s: DONE %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1132                         if (preview->img) {
1133                                 IMB_freeImBuf(preview->img);
1134                         }
1135                         MEM_freeN(preview);
1136                 }
1137         }
1138 }
1139
1140 static void filelist_cache_previews_free(FileListEntryCache *cache)
1141 {
1142         if (cache->previews_pool) {
1143                 BLI_thread_queue_nowait(cache->previews_done);
1144
1145                 filelist_cache_previews_clear(cache);
1146
1147                 BLI_thread_queue_free(cache->previews_done);
1148                 BLI_task_pool_free(cache->previews_pool);
1149                 cache->previews_pool = NULL;
1150                 cache->previews_done = NULL;
1151
1152                 IMB_thumb_locks_release();
1153         }
1154
1155         cache->flags &= ~FLC_PREVIEWS_ACTIVE;
1156 }
1157
1158 static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry, const int index)
1159 {
1160         FileListEntryCache *cache = &filelist->filelist_cache;
1161
1162         BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE);
1163
1164         if (!entry->image &&
1165             !(entry->flags & FILE_ENTRY_INVALID_PREVIEW) &&
1166             (entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT |
1167                                 FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)))
1168         {
1169                 FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__);
1170                 BLI_join_dirfile(preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath);
1171                 preview->index = index;
1172                 preview->flags = entry->typeflag;
1173                 preview->img = NULL;
1174 //              printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1175
1176                 filelist_cache_preview_ensure_running(cache);
1177                 BLI_task_pool_push_ex(cache->previews_pool, filelist_cache_preview_runf, preview,
1178                                       true, filelist_cache_preview_freef, TASK_PRIORITY_LOW);
1179         }
1180 }
1181
1182 static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size)
1183 {
1184         BLI_listbase_clear(&cache->cached_entries);
1185
1186         cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0;
1187         cache->block_entries = MEM_mallocN(sizeof(*cache->block_entries) * cache_size, __func__);
1188
1189         cache->misc_entries = BLI_ghash_ptr_new_ex(__func__, cache_size);
1190         cache->misc_entries_indices = MEM_mallocN(sizeof(*cache->misc_entries_indices) * cache_size, __func__);
1191         copy_vn_i(cache->misc_entries_indices, cache_size, -1);
1192         cache->misc_cursor = 0;
1193
1194         /* XXX This assumes uint is 32 bits and uuid is 128 bits (char[16]), be careful! */
1195         cache->uuids = BLI_ghash_new_ex(
1196                            BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__, cache_size * 2);
1197
1198         cache->size = cache_size;
1199         cache->flags = FLC_IS_INIT;
1200 }
1201
1202 static void filelist_cache_free(FileListEntryCache *cache)
1203 {
1204         FileDirEntry *entry, *entry_next;
1205
1206         if (!(cache->flags & FLC_IS_INIT)) {
1207                 return;
1208         }
1209
1210         filelist_cache_previews_free(cache);
1211
1212         MEM_freeN(cache->block_entries);
1213
1214         BLI_ghash_free(cache->misc_entries, NULL, NULL);
1215         MEM_freeN(cache->misc_entries_indices);
1216
1217         BLI_ghash_free(cache->uuids, NULL, NULL);
1218
1219         for (entry = cache->cached_entries.first; entry; entry = entry_next) {
1220                 entry_next = entry->next;
1221                 filelist_entry_free(entry);
1222         }
1223         BLI_listbase_clear(&cache->cached_entries);
1224 }
1225
1226 static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size)
1227 {
1228         FileDirEntry *entry, *entry_next;
1229
1230         if (!(cache->flags & FLC_IS_INIT)) {
1231                 return;
1232         }
1233
1234         filelist_cache_previews_clear(cache);
1235
1236         cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0;
1237         if (new_size != cache->size) {
1238                 cache->block_entries = MEM_reallocN(cache->block_entries, sizeof(*cache->block_entries) * new_size);
1239         }
1240
1241         BLI_ghash_clear_ex(cache->misc_entries, NULL, NULL, new_size);
1242         if (new_size != cache->size) {
1243                 cache->misc_entries_indices = MEM_reallocN(cache->misc_entries_indices,
1244                                                            sizeof(*cache->misc_entries_indices) * new_size);
1245         }
1246         copy_vn_i(cache->misc_entries_indices, new_size, -1);
1247
1248         BLI_ghash_clear_ex(cache->uuids, NULL, NULL, new_size * 2);
1249
1250         cache->size = new_size;
1251
1252         for (entry = cache->cached_entries.first; entry; entry = entry_next) {
1253                 entry_next = entry->next;
1254                 filelist_entry_free(entry);
1255         }
1256         BLI_listbase_clear(&cache->cached_entries);
1257 }
1258
1259 FileList *filelist_new(short type)
1260 {
1261         FileList *p = MEM_callocN(sizeof(*p), __func__);
1262
1263         filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT);
1264
1265         p->selection_state = BLI_ghash_new(BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__);
1266
1267         switch (type) {
1268                 case FILE_MAIN:
1269                         p->checkdirf = filelist_checkdir_main;
1270                         p->read_jobf = filelist_readjob_main;
1271                         p->filterf = is_filtered_main;
1272                         break;
1273                 case FILE_LOADLIB:
1274                         p->checkdirf = filelist_checkdir_lib;
1275                         p->read_jobf = filelist_readjob_lib;
1276                         p->filterf = is_filtered_lib;
1277                         break;
1278                 default:
1279                         p->checkdirf = filelist_checkdir_dir;
1280                         p->read_jobf = filelist_readjob_dir;
1281                         p->filterf = is_filtered_file;
1282                         break;
1283         }
1284         return p;
1285 }
1286
1287 void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection)
1288 {
1289         if (!filelist) {
1290                 return;
1291         }
1292
1293         filelist_filter_clear(filelist);
1294
1295         if (do_cache) {
1296                 filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size);
1297         }
1298
1299         filelist_intern_free(&filelist->filelist_intern);
1300
1301         filelist_direntryarr_free(&filelist->filelist);
1302
1303         if (do_selection && filelist->selection_state) {
1304                 BLI_ghash_clear(filelist->selection_state, MEM_freeN, NULL);
1305         }
1306 }
1307
1308 void filelist_clear(struct FileList *filelist)
1309 {
1310         filelist_clear_ex(filelist, true, true);
1311 }
1312
1313 void filelist_free(struct FileList *filelist)
1314 {
1315         if (!filelist) {
1316                 printf("Attempting to delete empty filelist.\n");
1317                 return;
1318         }
1319         
1320         filelist_clear_ex(filelist, false, false);  /* No need to clear cache & selection_state, we free them anyway. */
1321         filelist_cache_free(&filelist->filelist_cache);
1322
1323         if (filelist->selection_state) {
1324                 BLI_ghash_free(filelist->selection_state, MEM_freeN, NULL);
1325                 filelist->selection_state = NULL;
1326         }
1327
1328         memset(&filelist->filter_data, 0, sizeof(filelist->filter_data));
1329
1330         filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING);
1331         filelist->sort = FILE_SORT_NONE;
1332 }
1333
1334 void filelist_freelib(struct FileList *filelist)
1335 {
1336         if (filelist->libfiledata)
1337                 BLO_blendhandle_close(filelist->libfiledata);
1338         filelist->libfiledata = NULL;
1339 }
1340
1341 BlendHandle *filelist_lib(struct FileList *filelist)
1342 {
1343         return filelist->libfiledata;
1344 }
1345
1346 static const char *fileentry_uiname(const char *root, const char *relpath, const int typeflag, char *buff)
1347 {
1348         char *name = NULL;
1349
1350         if (typeflag & FILE_TYPE_BLENDERLIB) {
1351                 char abspath[FILE_MAX_LIBEXTRA];
1352                 char *group;
1353
1354                 BLI_join_dirfile(abspath, sizeof(abspath), root, relpath);
1355                 BLO_library_path_explode(abspath, buff, &group, &name);
1356                 if (!name) {
1357                         name = group;
1358                 }
1359         }
1360         /* Depending on platforms, 'my_file.blend/..' might be viewed as dir or not... */
1361         if (!name) {
1362                 if (typeflag & FILE_TYPE_DIR) {
1363                         name = (char *)relpath;
1364                 }
1365                 else {
1366                         name = (char *)BLI_path_basename(relpath);
1367                 }
1368         }
1369         BLI_assert(name);
1370
1371         return name;
1372 }
1373
1374 const char *filelist_dir(struct FileList *filelist)
1375 {
1376         return filelist->filelist.root;
1377 }
1378
1379 /**
1380  * May modify in place given r_dir, which is expected to be FILE_MAX_LIBEXTRA length.
1381  */
1382 void filelist_setdir(struct FileList *filelist, char *r_dir)
1383 {
1384         BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA);
1385
1386         BLI_cleanup_dir(G.main->name, r_dir);
1387         filelist->checkdirf(filelist, r_dir);
1388
1389         if (!STREQ(filelist->filelist.root, r_dir)) {
1390                 BLI_strncpy(filelist->filelist.root, r_dir, sizeof(filelist->filelist.root));
1391                 filelist->flags |= FL_FORCE_RESET;
1392         }
1393 }
1394
1395 void filelist_setrecursion(struct FileList *filelist, const int recursion_level)
1396 {
1397         if (filelist->max_recursion != recursion_level) {
1398                 filelist->max_recursion = recursion_level;
1399                 filelist->flags |= FL_FORCE_RESET;
1400         }
1401 }
1402
1403 bool filelist_force_reset(struct FileList *filelist)
1404 {
1405         return (filelist->flags & FL_FORCE_RESET) != 0;
1406 }
1407
1408 bool filelist_is_ready(struct FileList *filelist)
1409 {
1410         return (filelist->flags & FL_IS_READY) != 0;
1411 }
1412
1413 bool filelist_pending(struct FileList *filelist)
1414 {
1415         return (filelist->flags & FL_IS_PENDING) != 0;
1416 }
1417
1418 /**
1419  * Limited version of full update done by space_file's file_refresh(), to be used by operators and such.
1420  * Ensures given filelist is ready to be used (i.e. it is filtered and sorted), unless it is tagged for a full refresh.
1421  */
1422 int filelist_files_ensure(FileList *filelist)
1423 {
1424         if (!filelist_force_reset(filelist) || !filelist_empty(filelist)) {
1425                 filelist_sort(filelist);
1426                 filelist_filter(filelist);
1427         }
1428
1429         return filelist->filelist.nbr_entries_filtered;
1430 }
1431
1432 static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index)
1433 {
1434         FileListInternEntry *entry = filelist->filelist_intern.filtered[index];
1435         FileListEntryCache *cache = &filelist->filelist_cache;
1436         FileDirEntry *ret;
1437         FileDirEntryRevision *rev;
1438
1439         ret = MEM_callocN(sizeof(*ret), __func__);
1440         rev = MEM_callocN(sizeof(*rev), __func__);
1441
1442         rev->size = (uint64_t)entry->st.st_size;
1443
1444         rev->time = (int64_t)entry->st.st_mtime;
1445
1446         ret->entry = rev;
1447         ret->relpath = BLI_strdup(entry->relpath);
1448         ret->name = BLI_strdup(entry->name);
1449         ret->description = BLI_strdupcat(filelist->filelist.root, entry->relpath);
1450         memcpy(ret->uuid, entry->uuid, sizeof(ret->uuid));
1451         ret->blentype = entry->blentype;
1452         ret->typeflag = entry->typeflag;
1453
1454         BLI_addtail(&cache->cached_entries, ret);
1455         return ret;
1456 }
1457
1458 static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry)
1459 {
1460         BLI_remlink(&filelist->filelist_cache.cached_entries, entry);
1461         filelist_entry_free(entry);
1462 }
1463
1464 static FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request)
1465 {
1466         FileDirEntry *ret = NULL, *old;
1467         FileListEntryCache *cache = &filelist->filelist_cache;
1468         const size_t cache_size = cache->size;
1469         int old_index;
1470
1471         if ((index < 0) || (index >= filelist->filelist.nbr_entries_filtered)) {
1472                 return ret;
1473         }
1474
1475         if (index >= cache->block_start_index && index < cache->block_end_index) {
1476                 const int idx = (index - cache->block_start_index + cache->block_cursor) % cache_size;
1477                 return cache->block_entries[idx];
1478         }
1479
1480         if ((ret = BLI_ghash_lookup(cache->misc_entries, SET_INT_IN_POINTER(index)))) {
1481                 return ret;
1482         }
1483
1484         if (!use_request) {
1485                 return NULL;
1486         }
1487
1488 //      printf("requesting file %d (not yet cached)\n", index);
1489
1490         /* Else, we have to add new entry to 'misc' cache - and possibly make room for it first! */
1491         ret = filelist_file_create_entry(filelist, index);
1492         old_index = cache->misc_entries_indices[cache->misc_cursor];
1493         if ((old = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(old_index), NULL))) {
1494                 BLI_ghash_remove(cache->uuids, old->uuid, NULL, NULL);
1495                 filelist_file_release_entry(filelist, old);
1496         }
1497         BLI_ghash_insert(cache->misc_entries, SET_INT_IN_POINTER(index), ret);
1498         BLI_ghash_insert(cache->uuids, ret->uuid, ret);
1499
1500         cache->misc_entries_indices[cache->misc_cursor] = index;
1501         cache->misc_cursor = (cache->misc_cursor + 1) % cache_size;
1502
1503 #if 0  /* Actually no, only block cached entries should have preview imho. */
1504         if (cache->previews_pool) {
1505                 filelist_cache_previews_push(filelist, ret, index);
1506         }
1507 #endif
1508
1509         return ret;
1510 }
1511
1512 FileDirEntry *filelist_file(struct FileList *filelist, int index)
1513 {
1514         return filelist_file_ex(filelist, index, true);
1515 }
1516
1517 int filelist_file_findpath(struct FileList *filelist, const char *filename)
1518 {
1519         int fidx = -1;
1520         
1521         if (filelist->filelist.nbr_entries_filtered < 0) {
1522                 return fidx;
1523         }
1524
1525         /* XXX TODO Cache could probably use a ghash on paths too? Not really urgent though.
1526          *          This is only used to find again renamed entry, annoying but looks hairy to get rid of it currently. */
1527
1528         for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) {
1529                 FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx];
1530                 if (STREQ(entry->relpath, filename)) {
1531                         return fidx;
1532                 }
1533         }
1534
1535         return -1;
1536 }
1537
1538 FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4])
1539 {
1540         if (filelist->filelist.nbr_entries_filtered < 0) {
1541                 return NULL;
1542         }
1543
1544         if (filelist->filelist_cache.uuids) {
1545                 FileDirEntry *entry = BLI_ghash_lookup(filelist->filelist_cache.uuids, uuid);
1546                 if (entry) {
1547                         return entry;
1548                 }
1549         }
1550
1551         {
1552                 int fidx;
1553
1554                 for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) {
1555                         FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx];
1556                         if (memcmp(entry->uuid, uuid, sizeof(entry->uuid)) == 0) {
1557                                 return filelist_file(filelist, fidx);
1558                         }
1559                 }
1560         }
1561
1562         return NULL;
1563 }
1564
1565 void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size)
1566 {
1567         /* Always keep it power of 2, in [256, 8192] range for now, cache being app. twice bigger than requested window. */
1568         size_t size = 256;
1569         window_size *= 2;
1570
1571         while (size < window_size && size < 8192) {
1572                 size *= 2;
1573         }
1574
1575         if (size != filelist->filelist_cache.size) {
1576                 filelist_cache_clear(&filelist->filelist_cache, size);
1577         }
1578 }
1579
1580 /* Helpers, low-level, they assume cursor + size <= cache_size */
1581 static bool filelist_file_cache_block_create(FileList *filelist, const int start_index, const int size, int cursor)
1582 {
1583         FileListEntryCache *cache = &filelist->filelist_cache;
1584
1585         {
1586                 int i, idx;
1587
1588                 for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) {
1589                         FileDirEntry *entry;
1590
1591                         /* That entry might have already been requested and stored in misc cache... */
1592                         if ((entry = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(idx), NULL)) == NULL) {
1593                                 entry = filelist_file_create_entry(filelist, idx);
1594                                 BLI_ghash_insert(cache->uuids, entry->uuid, entry);
1595                         }
1596                         cache->block_entries[cursor] = entry;
1597                 }
1598                 return true;
1599         }
1600
1601         return false;
1602 }
1603
1604 static void filelist_file_cache_block_release(struct FileList *filelist, const int size, int cursor)
1605 {
1606         FileListEntryCache *cache = &filelist->filelist_cache;
1607
1608         {
1609                 int i;
1610
1611                 for (i = 0; i < size; i++, cursor++) {
1612                         FileDirEntry *entry = cache->block_entries[cursor];
1613 //                      printf("%s: release cacheidx %d (%%p %%s)\n", __func__, cursor/*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/);
1614                         BLI_ghash_remove(cache->uuids, entry->uuid, NULL, NULL);
1615                         filelist_file_release_entry(filelist, entry);
1616 #ifndef NDEBUG
1617                         cache->block_entries[cursor] = NULL;
1618 #endif
1619                 }
1620         }
1621 }
1622
1623 /* Load in cache all entries "around" given index (as much as block cache may hold). */
1624 bool filelist_file_cache_block(struct FileList *filelist, const int index)
1625 {
1626         FileListEntryCache *cache = &filelist->filelist_cache;
1627         const size_t cache_size = cache->size;
1628
1629         const int nbr_entries = filelist->filelist.nbr_entries_filtered;
1630         int start_index = max_ii(0, index - (cache_size / 2));
1631         int end_index = min_ii(nbr_entries, index + (cache_size / 2));
1632         int i;
1633
1634         if ((index < 0) || (index >= nbr_entries)) {
1635 //              printf("Wrong index %d ([%d:%d])", index, 0, nbr_entries);
1636                 return false;
1637         }
1638
1639         /* Maximize cached range! */
1640         if ((end_index - start_index) < cache_size) {
1641                 if (start_index == 0) {
1642                         end_index = min_ii(nbr_entries, start_index + cache_size);
1643                 }
1644                 else if (end_index == nbr_entries) {
1645                         start_index = max_ii(0, end_index - cache_size);
1646                 }
1647         }
1648
1649         BLI_assert((end_index - start_index) <= cache_size) ;
1650
1651 //      printf("%s: [%d:%d] around index %d (current cache: [%d:%d])\n", __func__,
1652 //             start_index, end_index, index, cache->block_start_index, cache->block_end_index);
1653
1654         /* If we have something to (re)cache... */
1655         if ((start_index != cache->block_start_index) || (end_index != cache->block_end_index)) {
1656                 if ((start_index >= cache->block_end_index) || (end_index <= cache->block_start_index)) {
1657                         int size1 = cache->block_end_index - cache->block_start_index;
1658                         int size2 = 0;
1659                         int idx1 = cache->block_cursor, idx2 = 0;
1660
1661 //                      printf("Full Recaching!\n");
1662
1663                         if (cache->flags & FLC_PREVIEWS_ACTIVE) {
1664                                 filelist_cache_previews_clear(cache);
1665                         }
1666
1667                         if (idx1 + size1 > cache_size) {
1668                                 size2 = idx1 + size1 - cache_size;
1669                                 size1 -= size2;
1670                                 filelist_file_cache_block_release(filelist, size2, idx2);
1671                         }
1672                         filelist_file_cache_block_release(filelist, size1, idx1);
1673
1674                         cache->block_start_index = cache->block_end_index = cache->block_cursor = 0;
1675
1676                         /* New cached block does not overlap existing one, simple. */
1677                         if (!filelist_file_cache_block_create(filelist, start_index, end_index - start_index, 0)) {
1678                                 return false;
1679                         }
1680
1681                         cache->block_start_index = start_index;
1682                         cache->block_end_index = end_index;
1683                 }
1684                 else {
1685 //                      printf("Partial Recaching!\n");
1686
1687                         /* At this point, we know we keep part of currently cached entries, so update previews if needed,
1688                          * and remove everything from working queue - we'll add all newly needed entries at the end. */
1689                         if (cache->flags & FLC_PREVIEWS_ACTIVE) {
1690                                 filelist_cache_previews_update(filelist);
1691                                 filelist_cache_previews_clear(cache);
1692                         }
1693
1694 //                      printf("\tpreview cleaned up...\n");
1695
1696                         if (start_index > cache->block_start_index) {
1697                                 int size1 = start_index - cache->block_start_index;
1698                                 int size2 = 0;
1699                                 int idx1 = cache->block_cursor, idx2 = 0;
1700
1701 //                              printf("\tcache releasing: [%d:%d] (%d, %d)\n", cache->block_start_index, cache->block_start_index + size1, cache->block_cursor, size1);
1702
1703                                 if (idx1 + size1 > cache_size) {
1704                                         size2 = idx1 + size1 - cache_size;
1705                                         size1 -= size2;
1706                                         filelist_file_cache_block_release(filelist, size2, idx2);
1707                                 }
1708                                 filelist_file_cache_block_release(filelist, size1, idx1);
1709
1710                                 cache->block_cursor = (idx1 + size1 + size2) % cache_size;
1711                                 cache->block_start_index = start_index;
1712                         }
1713                         if (end_index < cache->block_end_index) {
1714                                 int size1 = cache->block_end_index - end_index;
1715                                 int size2 = 0;
1716                                 int idx1, idx2 = 0;
1717
1718 //                              printf("\tcache releasing: [%d:%d] (%d)\n", cache->block_end_index - size1, cache->block_end_index, cache->block_cursor);
1719
1720                                 idx1 = (cache->block_cursor + end_index - cache->block_start_index) % cache_size;
1721                                 if (idx1 + size1 > cache_size) {
1722                                         size2 = idx1 + size1 - cache_size;
1723                                         size1 -= size2;
1724                                         filelist_file_cache_block_release(filelist, size2, idx2);
1725                                 }
1726                                 filelist_file_cache_block_release(filelist, size1, idx1);
1727
1728                                 cache->block_end_index = end_index;
1729                         }
1730
1731 //                      printf("\tcache cleaned up...\n");
1732
1733                         if (start_index < cache->block_start_index) {
1734                                 /* Add (request) needed entries before already cached ones. */
1735                                 /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */
1736                                 int size1 = cache->block_start_index - start_index;
1737                                 int size2 = 0;
1738                                 int idx1, idx2;
1739
1740                                 if (size1 > cache->block_cursor) {
1741                                         size2 = size1;
1742                                         size1 -= cache->block_cursor;
1743                                         size2 -= size1;
1744                                         idx2 = 0;
1745                                         idx1 = cache_size - size1;
1746                                 }
1747                                 else {
1748                                         idx1 = cache->block_cursor - size1;
1749                                 }
1750
1751                                 if (size2) {
1752                                         if (!filelist_file_cache_block_create(filelist, start_index + size1, size2, idx2)) {
1753                                                 return false;
1754                                         }
1755                                 }
1756                                 if (!filelist_file_cache_block_create(filelist, start_index, size1, idx1)) {
1757                                         return false;
1758                                 }
1759
1760                                 cache->block_cursor = idx1;
1761                                 cache->block_start_index = start_index;
1762                         }
1763 //                      printf("\tstart-extended...\n");
1764                         if (end_index > cache->block_end_index) {
1765                                 /* Add (request) needed entries after already cached ones. */
1766                                 /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */
1767                                 int size1 = end_index - cache->block_end_index;
1768                                 int size2 = 0;
1769                                 int idx1, idx2;
1770
1771                                 idx1 = (cache->block_cursor + end_index - cache->block_start_index - size1) % cache_size;
1772                                 if ((idx1 + size1) > cache_size) {
1773                                         size2 = size1;
1774                                         size1 = cache_size - idx1;
1775                                         size2 -= size1;
1776                                         idx2 = 0;
1777                                 }
1778
1779                                 if (size2) {
1780                                         if (!filelist_file_cache_block_create(filelist, end_index - size2, size2, idx2)) {
1781                                                 return false;
1782                                         }
1783                                 }
1784                                 if (!filelist_file_cache_block_create(filelist, end_index - size1 - size2, size1, idx1)) {
1785                                         return false;
1786                                 }
1787
1788                                 cache->block_end_index = end_index;
1789                         }
1790
1791 //                      printf("\tend-extended...\n");
1792                 }
1793         }
1794         else if ((cache->block_center_index != index) && (cache->flags & FLC_PREVIEWS_ACTIVE)) {
1795                 /* We try to always preview visible entries first, so 'restart' preview background task. */
1796                 filelist_cache_previews_update(filelist);
1797                 filelist_cache_previews_clear(cache);
1798         }
1799
1800 //      printf("Re-queueing previews...\n");
1801
1802         /* Note we try to preview first images around given index - i.e. assumed visible ones. */
1803         if (cache->flags & FLC_PREVIEWS_ACTIVE) {
1804                 for (i = 0; ((index + i) < end_index) || ((index - i) >= start_index); i++) {
1805                         if ((index - i) >= start_index) {
1806                                 const int idx = (cache->block_cursor + (index - start_index) - i) % cache_size;
1807                                 filelist_cache_previews_push(filelist, cache->block_entries[idx], index - i);
1808                         }
1809                         if ((index + i) < end_index) {
1810                                 const int idx = (cache->block_cursor + (index - start_index) + i) % cache_size;
1811                                 filelist_cache_previews_push(filelist, cache->block_entries[idx], index + i);
1812                         }
1813                 }
1814         }
1815
1816         cache->block_center_index = index;
1817
1818 //      printf("%s Finished!\n", __func__);
1819
1820         return true;
1821 }
1822
1823 void filelist_cache_previews_set(FileList *filelist, const bool use_previews)
1824 {
1825         FileListEntryCache *cache = &filelist->filelist_cache;
1826
1827         if (use_previews == ((cache->flags & FLC_PREVIEWS_ACTIVE) != 0)) {
1828                 return;
1829         }
1830         /* Do not start preview work while listing, gives nasty flickering! */
1831         else if (use_previews && (filelist->flags & FL_IS_READY)) {
1832                 cache->flags |= FLC_PREVIEWS_ACTIVE;
1833
1834                 BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL));
1835
1836 //              printf("%s: Init Previews...\n", __func__);
1837
1838                 /* No need to populate preview queue here, filelist_file_cache_block() handles this. */
1839         }
1840         else {
1841 //              printf("%s: Clear Previews...\n", __func__);
1842
1843                 filelist_cache_previews_free(cache);
1844         }
1845 }
1846
1847 bool filelist_cache_previews_update(FileList *filelist)
1848 {
1849         FileListEntryCache *cache = &filelist->filelist_cache;
1850         TaskPool *pool = cache->previews_pool;
1851         bool changed = false;
1852
1853         if (!pool) {
1854                 return changed;
1855         }
1856
1857 //      printf("%s: Update Previews...\n", __func__);
1858
1859         while (!BLI_thread_queue_is_empty(cache->previews_done)) {
1860                 FileListEntryPreview *preview = BLI_thread_queue_pop(cache->previews_done);
1861                 FileDirEntry *entry;
1862
1863                 /* Paranoid (should never happen currently since we consume this queue from a single thread), but... */
1864                 if (!preview) {
1865                         continue;
1866                 }
1867                 /* entry might have been removed from cache in the mean time, we do not want to cache it again here. */
1868                 entry = filelist_file_ex(filelist, preview->index, false);
1869
1870 //              printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img);
1871
1872                 if (preview->img) {
1873                         /* Due to asynchronous process, a preview for a given image may be generated several times, i.e.
1874                          * entry->image may already be set at this point. */
1875                         if (entry && !entry->image) {
1876                                 entry->image = preview->img;
1877                                 changed = true;
1878                         }
1879                         else {
1880                                 IMB_freeImBuf(preview->img);
1881                         }
1882                 }
1883                 else if (entry) {
1884                         /* We want to avoid re-processing this entry continuously!
1885                          * Note that, since entries only live in cache, preview will be retried quite often anyway. */
1886                         entry->flags |= FILE_ENTRY_INVALID_PREVIEW;
1887                 }
1888
1889                 MEM_freeN(preview);
1890         }
1891
1892         return changed;
1893 }
1894
1895 bool filelist_cache_previews_running(FileList *filelist)
1896 {
1897         FileListEntryCache *cache = &filelist->filelist_cache;
1898
1899         return (cache->previews_pool != NULL);
1900 }
1901
1902 /* would recognize .blend as well */
1903 static bool file_is_blend_backup(const char *str)
1904 {
1905         const size_t a = strlen(str);
1906         size_t b = 7;
1907         bool retval = 0;
1908
1909         if (a == 0 || b >= a) {
1910                 /* pass */
1911         }
1912         else {
1913                 const char *loc;
1914                 
1915                 if (a > b + 1)
1916                         b++;
1917                 
1918                 /* allow .blend1 .blend2 .blend32 */
1919                 loc = BLI_strcasestr(str + a - b, ".blend");
1920                 
1921                 if (loc)
1922                         retval = 1;
1923         }
1924         
1925         return (retval);
1926 }
1927
1928 /* TODO: Maybe we should move this to BLI? On the other hand, it's using defines from spacefile area, so not sure... */
1929 int ED_path_extension_type(const char *path)
1930 {
1931         if (BLO_has_bfile_extension(path)) {
1932                 return FILE_TYPE_BLENDER;
1933         }
1934         else if (file_is_blend_backup(path)) {
1935                 return FILE_TYPE_BLENDER_BACKUP;
1936         }
1937         else if (BLI_testextensie(path, ".app")) {
1938                 return FILE_TYPE_APPLICATIONBUNDLE;
1939         }
1940         else if (BLI_testextensie(path, ".py")) {
1941                 return FILE_TYPE_PYSCRIPT;
1942         }
1943         else if (BLI_testextensie_n(path, ".txt", ".glsl", ".osl", ".data", NULL)) {
1944                 return FILE_TYPE_TEXT;
1945         }
1946         else if (BLI_testextensie_n(path, ".ttf", ".ttc", ".pfb", ".otf", ".otc", NULL)) {
1947                 return FILE_TYPE_FTFONT;
1948         }
1949         else if (BLI_testextensie(path, ".btx")) {
1950                 return FILE_TYPE_BTX;
1951         }
1952         else if (BLI_testextensie(path, ".dae")) {
1953                 return FILE_TYPE_COLLADA;
1954         }
1955         else if (BLI_testextensie_array(path, imb_ext_image) ||
1956                  (G.have_quicktime && BLI_testextensie_array(path, imb_ext_image_qt)))
1957         {
1958                 return FILE_TYPE_IMAGE;
1959         }
1960         else if (BLI_testextensie(path, ".ogg")) {
1961                 if (IMB_isanim(path)) {
1962                         return FILE_TYPE_MOVIE;
1963                 }
1964                 else {
1965                         return FILE_TYPE_SOUND;
1966                 }
1967         }
1968         else if (BLI_testextensie_array(path, imb_ext_movie)) {
1969                 return FILE_TYPE_MOVIE;
1970         }
1971         else if (BLI_testextensie_array(path, imb_ext_audio)) {
1972                 return FILE_TYPE_SOUND;
1973         }
1974         return 0;
1975 }
1976
1977 static int file_extension_type(const char *dir, const char *relpath)
1978 {
1979         char path[FILE_MAX];
1980         BLI_join_dirfile(path, sizeof(path), dir, relpath);
1981         return ED_path_extension_type(path);
1982 }
1983
1984 int ED_file_extension_icon(const char *path)
1985 {
1986         const int type = ED_path_extension_type(path);
1987         
1988         switch (type) {
1989                 case FILE_TYPE_BLENDER:
1990                         return ICON_FILE_BLEND;
1991                 case FILE_TYPE_BLENDER_BACKUP:
1992                         return ICON_FILE_BACKUP;
1993                 case FILE_TYPE_IMAGE:
1994                         return ICON_FILE_IMAGE;
1995                 case FILE_TYPE_MOVIE:
1996                         return ICON_FILE_MOVIE;
1997                 case FILE_TYPE_PYSCRIPT:
1998                         return ICON_FILE_SCRIPT;
1999                 case FILE_TYPE_SOUND:
2000                         return ICON_FILE_SOUND;
2001                 case FILE_TYPE_FTFONT:
2002                         return ICON_FILE_FONT;
2003                 case FILE_TYPE_BTX:
2004                         return ICON_FILE_BLANK;
2005                 case FILE_TYPE_COLLADA:
2006                         return ICON_FILE_BLANK;
2007                 case FILE_TYPE_TEXT:
2008                         return ICON_FILE_TEXT;
2009                 default:
2010                         return ICON_FILE_BLANK;
2011         }
2012 }
2013
2014 int filelist_empty(struct FileList *filelist)
2015 {
2016         return (filelist->filelist.nbr_entries == 0);
2017 }
2018
2019 unsigned int filelist_entry_select_set(
2020         const FileList *filelist, const FileDirEntry *entry, FileSelType select, unsigned int flag, FileCheckType check)
2021 {
2022         /* Default NULL pointer if not found is fine here! */
2023         void **es_p = BLI_ghash_lookup_p(filelist->selection_state, entry->uuid);
2024         unsigned int entry_flag = es_p ? GET_UINT_FROM_POINTER(*es_p) : 0;
2025         const unsigned int org_entry_flag = entry_flag;
2026
2027         BLI_assert(entry);
2028         BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL));
2029
2030         if (((check == CHECK_ALL)) ||
2031             ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) ||
2032             ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR)))
2033         {
2034                 switch (select) {
2035                         case FILE_SEL_REMOVE:
2036                                 entry_flag &= ~flag;
2037                                 break;
2038                         case FILE_SEL_ADD:
2039                                 entry_flag |= flag;
2040                                 break;
2041                         case FILE_SEL_TOGGLE:
2042                                 entry_flag ^= flag;
2043                                 break;
2044                 }
2045         }
2046
2047         if (entry_flag != org_entry_flag) {
2048                 if (es_p) {
2049                         if (entry_flag) {
2050                                 *es_p = SET_UINT_IN_POINTER(entry_flag);
2051                         }
2052                         else {
2053                                 BLI_ghash_remove(filelist->selection_state, entry->uuid, MEM_freeN, NULL);
2054                         }
2055                 }
2056                 else if (entry_flag) {
2057                         void *key = MEM_mallocN(sizeof(entry->uuid), __func__);
2058                         memcpy(key, entry->uuid, sizeof(entry->uuid));
2059                         BLI_ghash_insert(filelist->selection_state, key, SET_UINT_IN_POINTER(entry_flag));
2060                 }
2061         }
2062
2063         return entry_flag;
2064 }
2065
2066 void filelist_entry_select_index_set(FileList *filelist, const int index, FileSelType select, unsigned int flag, FileCheckType check)
2067 {
2068         FileDirEntry *entry = filelist_file(filelist, index);
2069
2070         if (entry) {
2071                 filelist_entry_select_set(filelist, entry, select, flag, check);
2072         }
2073 }
2074
2075 void filelist_entries_select_index_range_set(
2076         FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check)
2077 {
2078         /* select all valid files between first and last indicated */
2079         if ((sel->first >= 0) && (sel->first < filelist->filelist.nbr_entries_filtered) &&
2080             (sel->last >= 0) && (sel->last < filelist->filelist.nbr_entries_filtered))
2081         {
2082                 int current_file;
2083                 for (current_file = sel->first; current_file <= sel->last; current_file++) {
2084                         filelist_entry_select_index_set(filelist, current_file, select, flag, check);
2085                 }
2086         }
2087 }
2088
2089 unsigned int filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileCheckType check)
2090 {
2091         BLI_assert(entry);
2092         BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL));
2093
2094         if (((check == CHECK_ALL)) ||
2095             ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) ||
2096             ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR)))
2097         {
2098                 /* Default NULL pointer if not found is fine here! */
2099                 return GET_UINT_FROM_POINTER(BLI_ghash_lookup(filelist->selection_state, entry->uuid));
2100         }
2101
2102         return 0;
2103 }
2104
2105 unsigned int filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check)
2106 {
2107         FileDirEntry *entry = filelist_file(filelist, index);
2108
2109         if (entry) {
2110                 return filelist_entry_select_get(filelist, entry, check);
2111         }
2112
2113         return 0;
2114 }
2115
2116 bool filelist_islibrary(struct FileList *filelist, char *dir, char **group)
2117 {
2118         return BLO_library_path_explode(filelist->filelist.root, dir, group, NULL);
2119 }
2120
2121 static int groupname_to_code(const char *group)
2122 {
2123         char buf[BLO_GROUP_MAX];
2124         char *lslash;
2125
2126         BLI_assert(group);
2127
2128         BLI_strncpy(buf, group, sizeof(buf));
2129         lslash = (char *)BLI_last_slash(buf);
2130         if (lslash)
2131                 lslash[0] = '\0';
2132
2133         return buf[0] ? BKE_idcode_from_name(buf) : 0;
2134 }
2135
2136 static unsigned int groupname_to_filter_id(const char *group)
2137 {
2138         int id_code = groupname_to_code(group);
2139
2140         return BKE_idcode_to_idfilter(id_code);
2141 }
2142
2143 /**
2144  * From here, we are in 'Job Context', i.e. have to be careful about sharing stuff between background working thread
2145  * and main one (used by UI among other things).
2146  */
2147 typedef struct TodoDir {
2148         int level;
2149         char *dir;
2150 } TodoDir;
2151
2152 static int filelist_readjob_list_dir(
2153         const char *root, ListBase *entries, const char *filter_glob,
2154         const bool do_lib, const char *main_name, const bool skip_currpar)
2155 {
2156         struct direntry *files;
2157         int nbr_files, nbr_entries = 0;
2158
2159         nbr_files = BLI_filelist_dir_contents(root, &files);
2160         if (files) {
2161                 int i = nbr_files;
2162                 while (i--) {
2163                         FileListInternEntry *entry;
2164
2165                         if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) {
2166                                 continue;
2167                         }
2168
2169                         entry = MEM_callocN(sizeof(*entry), __func__);
2170                         entry->relpath = MEM_dupallocN(files[i].relname);
2171                         entry->st = files[i].s;
2172
2173                         /* Set file type. */
2174                         if (S_ISDIR(files[i].s.st_mode)) {
2175                                 entry->typeflag = FILE_TYPE_DIR;
2176                         }
2177                         else if (do_lib && BLO_has_bfile_extension(entry->relpath)) {
2178                                 /* If we are considering .blend files as libs, promote them to directory status. */
2179                                 char name[FILE_MAX];
2180
2181                                 entry->typeflag = FILE_TYPE_BLENDER;
2182
2183                                 BLI_join_dirfile(name, sizeof(name), root, entry->relpath);
2184
2185                                 /* prevent current file being used as acceptable dir */
2186                                 if (BLI_path_cmp(main_name, name) != 0) {
2187                                         entry->typeflag |= FILE_TYPE_DIR;
2188                                 }
2189                         }
2190                         /* Otherwise, do not check extensions for directories! */
2191                         else if (!(entry->typeflag & FILE_TYPE_DIR)) {
2192                                 entry->typeflag = file_extension_type(root, entry->relpath);
2193                                 if (filter_glob[0] && BLI_testextensie_glob(entry->relpath, filter_glob)) {
2194                                         entry->typeflag |= FILE_TYPE_OPERATOR;
2195                                 }
2196                         }
2197
2198                         BLI_addtail(entries, entry);
2199                         nbr_entries++;
2200                 }
2201                 BLI_filelist_free(files, nbr_files);
2202         }
2203         return nbr_entries;
2204 }
2205
2206 static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar)
2207 {
2208         FileListInternEntry *entry;
2209         LinkNode *ln, *names;
2210         int i, nnames, idcode = 0, nbr_entries = 0;
2211         char dir[FILE_MAX], *group;
2212         bool ok;
2213
2214         struct BlendHandle *libfiledata = NULL;
2215
2216         /* name test */
2217         ok = BLO_library_path_explode(root, dir, &group, NULL);
2218         if (!ok) {
2219                 return nbr_entries;
2220         }
2221
2222         /* there we go */
2223         libfiledata = BLO_blendhandle_from_file(dir, NULL);
2224         if (libfiledata == NULL) {
2225                 return nbr_entries;
2226         }
2227
2228         /* memory for strings is passed into filelist[i].entry->relpath and freed in filelist_entry_free. */
2229         if (group) {
2230                 idcode = groupname_to_code(group);
2231                 names = BLO_blendhandle_get_datablock_names(libfiledata, idcode, &nnames);
2232         }
2233         else {
2234                 names = BLO_blendhandle_get_linkable_groups(libfiledata);
2235                 nnames = BLI_linklist_count(names);
2236         }
2237
2238         BLO_blendhandle_close(libfiledata);
2239
2240         if (!skip_currpar) {
2241                 entry = MEM_callocN(sizeof(*entry), __func__);
2242                 entry->relpath = BLI_strdup(FILENAME_PARENT);
2243                 entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR);
2244                 BLI_addtail(entries, entry);
2245                 nbr_entries++;
2246         }
2247
2248         for (i = 0, ln = names; i < nnames; i++, ln = ln->next) {
2249                 const char *blockname = ln->link;
2250
2251                 entry = MEM_callocN(sizeof(*entry), __func__);
2252                 entry->relpath = BLI_strdup(blockname);
2253                 entry->typeflag |= FILE_TYPE_BLENDERLIB;
2254                 if (!(group && idcode)) {
2255                         entry->typeflag |= FILE_TYPE_DIR;
2256                         entry->blentype = groupname_to_code(blockname);
2257                 }
2258                 else {
2259                         entry->blentype = idcode;
2260                 }
2261                 BLI_addtail(entries, entry);
2262                 nbr_entries++;
2263         }
2264
2265         BLI_linklist_free(names, free);
2266
2267         return nbr_entries;
2268 }
2269
2270 #if 0
2271 /* Kept for reference here, in case we want to add back that feature later. We do not need it currently. */
2272 /* Code ***NOT*** updated for job stuff! */
2273 static void filelist_readjob_main_rec(struct FileList *filelist)
2274 {
2275         ID *id;
2276         FileDirEntry *files, *firstlib = NULL;
2277         ListBase *lb;
2278         int a, fake, idcode, ok, totlib, totbl;
2279         
2280         // filelist->type = FILE_MAIN; // XXX TODO: add modes to filebrowser
2281
2282         BLI_assert(filelist->filelist.entries == NULL);
2283
2284         if (filelist->filelist.root[0] == '/') filelist->filelist.root[0] = '\0';
2285
2286         if (filelist->filelist.root[0]) {
2287                 idcode = groupname_to_code(filelist->filelist.root);
2288                 if (idcode == 0) filelist->filelist.root[0] = '\0';
2289         }
2290
2291         if (filelist->dir[0] == 0) {
2292                 /* make directories */
2293 #ifdef WITH_FREESTYLE
2294                 filelist->filelist.nbr_entries = 24;
2295 #else
2296                 filelist->filelist.nbr_entries = 23;
2297 #endif
2298                 filelist_resize(filelist, filelist->filelist.nbr_entries);
2299
2300                 for (a = 0; a < filelist->filelist.nbr_entries; a++) {
2301                         filelist->filelist.entries[a].typeflag |= FILE_TYPE_DIR;
2302                 }
2303
2304                 filelist->filelist.entries[0].entry->relpath = BLI_strdup(FILENAME_PARENT);
2305                 filelist->filelist.entries[1].entry->relpath = BLI_strdup("Scene");
2306                 filelist->filelist.entries[2].entry->relpath = BLI_strdup("Object");
2307                 filelist->filelist.entries[3].entry->relpath = BLI_strdup("Mesh");
2308                 filelist->filelist.entries[4].entry->relpath = BLI_strdup("Curve");
2309                 filelist->filelist.entries[5].entry->relpath = BLI_strdup("Metaball");
2310                 filelist->filelist.entries[6].entry->relpath = BLI_strdup("Material");
2311                 filelist->filelist.entries[7].entry->relpath = BLI_strdup("Texture");
2312                 filelist->filelist.entries[8].entry->relpath = BLI_strdup("Image");
2313                 filelist->filelist.entries[9].entry->relpath = BLI_strdup("Ika");
2314                 filelist->filelist.entries[10].entry->relpath = BLI_strdup("Wave");
2315                 filelist->filelist.entries[11].entry->relpath = BLI_strdup("Lattice");
2316                 filelist->filelist.entries[12].entry->relpath = BLI_strdup("Lamp");
2317                 filelist->filelist.entries[13].entry->relpath = BLI_strdup("Camera");
2318                 filelist->filelist.entries[14].entry->relpath = BLI_strdup("Ipo");
2319                 filelist->filelist.entries[15].entry->relpath = BLI_strdup("World");
2320                 filelist->filelist.entries[16].entry->relpath = BLI_strdup("Screen");
2321                 filelist->filelist.entries[17].entry->relpath = BLI_strdup("VFont");
2322                 filelist->filelist.entries[18].entry->relpath = BLI_strdup("Text");
2323                 filelist->filelist.entries[19].entry->relpath = BLI_strdup("Armature");
2324                 filelist->filelist.entries[20].entry->relpath = BLI_strdup("Action");
2325                 filelist->filelist.entries[21].entry->relpath = BLI_strdup("NodeTree");
2326                 filelist->filelist.entries[22].entry->relpath = BLI_strdup("Speaker");
2327 #ifdef WITH_FREESTYLE
2328                 filelist->filelist.entries[23].entry->relpath = BLI_strdup("FreestyleLineStyle");
2329 #endif
2330         }
2331         else {
2332                 /* make files */
2333                 idcode = groupname_to_code(filelist->filelist.root);
2334
2335                 lb = which_libbase(G.main, idcode);
2336                 if (lb == NULL) return;
2337
2338                 filelist->filelist.nbr_entries = 0;
2339                 for (id = lb->first; id; id = id->next) {
2340                         if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') {
2341                                 filelist->filelist.nbr_entries++;
2342                         }
2343                 }
2344
2345                 /* XXX TODO: if databrowse F4 or append/link filelist->flags & FLF_HIDE_PARENT has to be set */
2346                 if (!(filelist->filter_data.flags & FLF_HIDE_PARENT))
2347                         filelist->filelist.nbr_entries++;
2348
2349                 if (filelist->filelist.nbr_entries > 0) {
2350                         filelist_resize(filelist, filelist->filelist.nbr_entries);
2351                 }
2352
2353                 files = filelist->filelist.entries;
2354                 
2355                 if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) {
2356                         files->entry->relpath = BLI_strdup(FILENAME_PARENT);
2357                         files->typeflag |= FILE_TYPE_DIR;
2358
2359                         files++;
2360                 }
2361
2362                 totlib = totbl = 0;
2363                 for (id = lb->first; id; id = id->next) {
2364                         ok = 1;
2365                         if (ok) {
2366                                 if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') {
2367                                         if (id->lib == NULL) {
2368                                                 files->entry->relpath = BLI_strdup(id->name + 2);
2369                                         }
2370                                         else {
2371                                                 char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3];
2372                                                 BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->name, id->name + 2);
2373                                                 files->entry->relpath = BLI_strdup(relname);
2374                                         }
2375 //                                      files->type |= S_IFREG;
2376 #if 0               /* XXX TODO show the selection status of the objects */
2377                                         if (!filelist->has_func) { /* F4 DATA BROWSE */
2378                                                 if (idcode == ID_OB) {
2379                                                         if ( ((Object *)id)->flag & SELECT) files->entry->selflag |= FILE_SEL_SELECTED;
2380                                                 }
2381                                                 else if (idcode == ID_SCE) {
2382                                                         if ( ((Scene *)id)->r.scemode & R_BG_RENDER) files->entry->selflag |= FILE_SEL_SELECTED;
2383                                                 }
2384                                         }
2385 #endif
2386 //                                      files->entry->nr = totbl + 1;
2387                                         files->entry->poin = id;
2388                                         fake = id->flag & LIB_FAKEUSER;
2389                                         if (idcode == ID_MA || idcode == ID_TE || idcode == ID_LA || idcode == ID_WO || idcode == ID_IM) {
2390                                                 files->typeflag |= FILE_TYPE_IMAGE;
2391                                         }
2392 //                                      if      (id->lib && fake) BLI_snprintf(files->extra, sizeof(files->entry->extra), "LF %d",    id->us);
2393 //                                      else if (id->lib)         BLI_snprintf(files->extra, sizeof(files->entry->extra), "L    %d",  id->us);
2394 //                                      else if (fake)            BLI_snprintf(files->extra, sizeof(files->entry->extra), "F    %d",  id->us);
2395 //                                      else                      BLI_snprintf(files->extra, sizeof(files->entry->extra), "      %d", id->us);
2396
2397                                         if (id->lib) {
2398                                                 if (totlib == 0) firstlib = files;
2399                                                 totlib++;
2400                                         }
2401
2402                                         files++;
2403                                 }
2404                                 totbl++;
2405                         }
2406                 }
2407
2408                 /* only qsort of library blocks */
2409                 if (totlib > 1) {
2410                         qsort(firstlib, totlib, sizeof(*files), compare_name);
2411                 }
2412         }
2413 }
2414 #endif
2415
2416 static void filelist_readjob_do(
2417         const bool do_lib,
2418         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2419 {
2420         ListBase entries = {0};
2421         BLI_Stack *todo_dirs;
2422         TodoDir *td_dir;
2423         char dir[FILE_MAX_LIBEXTRA];
2424         char filter_glob[64];  /* TODO should be define! */
2425         const char *root = filelist->filelist.root;
2426         const int max_recursion = filelist->max_recursion;
2427         int nbr_done_dirs = 0, nbr_todo_dirs = 1;
2428
2429 //      BLI_assert(filelist->filtered == NULL);
2430         BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == 0));
2431
2432         todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__);
2433         td_dir = BLI_stack_push_r(todo_dirs);
2434         td_dir->level = 1;
2435
2436         BLI_strncpy(dir, filelist->filelist.root, sizeof(dir));
2437         BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob));
2438
2439         BLI_cleanup_dir(main_name, dir);
2440         td_dir->dir = BLI_strdup(dir);
2441
2442         while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) {
2443                 FileListInternEntry *entry;
2444                 int nbr_entries = 0;
2445                 bool is_lib = do_lib;
2446
2447                 char *subdir;
2448                 char rel_subdir[FILE_MAX_LIBEXTRA];
2449                 int recursion_level;
2450                 bool skip_currpar;
2451
2452                 td_dir = BLI_stack_peek(todo_dirs);
2453                 subdir = td_dir->dir;
2454                 recursion_level = td_dir->level;
2455                 skip_currpar = (recursion_level > 1);
2456
2457                 BLI_stack_discard(todo_dirs);
2458
2459                 /* ARRRG! We have to be very careful *not to use* common BLI_path_util helpers over entry->relpath itself
2460                  * (nor any path containing it), since it may actually be a datablock name inside .blend file,
2461                  * which can have slashes and backslashes! See T46827.
2462                  * Note that in the end, this means we 'cache' valid relative subdir once here, this is actually better. */
2463                 BLI_strncpy(rel_subdir, subdir, sizeof(rel_subdir));
2464                 BLI_cleanup_dir(root, rel_subdir);
2465                 BLI_path_rel(rel_subdir, root);
2466
2467                 if (do_lib) {
2468                         nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar);
2469                 }
2470                 if (!nbr_entries) {
2471                         is_lib = false;
2472                         nbr_entries = filelist_readjob_list_dir(subdir, &entries, filter_glob, do_lib, main_name, skip_currpar);
2473                 }
2474
2475                 for (entry = entries.first; entry; entry = entry->next) {
2476                         BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath);
2477
2478                         /* Generate our entry uuid. Abusing uuid as an uint32, shall be more than enough here,
2479                          * things would crash way before we overflow that counter!
2480                          * Using an atomic operation to avoid having to lock thread...
2481                          * Note that we do not really need this here currently, since there is a single listing thread, but better
2482                          * remain consistent about threading! */
2483                         *((uint32_t *)entry->uuid) = atomic_add_uint32((uint32_t *)filelist->filelist_intern.curr_uuid, 1);
2484
2485                         /* Only thing we change in direntry here, so we need to free it first. */
2486                         MEM_freeN(entry->relpath);
2487                         entry->relpath = BLI_strdup(dir + 2);  /* + 2 to remove '//' added by BLI_path_rel to rel_subdir */
2488                         entry->name = BLI_strdup(fileentry_uiname(root, entry->relpath, entry->typeflag, dir));
2489
2490                         /* Here we decide whether current filedirentry is to be listed too, or not. */
2491                         if (max_recursion && (is_lib || (recursion_level <= max_recursion))) {
2492                                 if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) {
2493                                         /* Skip... */
2494                                 }
2495                                 else if (!is_lib && (recursion_level >= max_recursion) &&
2496                                          ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0))
2497                                 {
2498                                         /* Do not recurse in real directories in this case, only in .blend libs. */
2499                                 }
2500                                 else {
2501                                         /* We have a directory we want to list, add it to todo list! */
2502                                         BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath);
2503                                         BLI_cleanup_dir(main_name, dir);
2504                                         td_dir = BLI_stack_push_r(todo_dirs);
2505                                         td_dir->level = recursion_level + 1;
2506                                         td_dir->dir = BLI_strdup(dir);
2507                                         nbr_todo_dirs++;
2508                                 }
2509                         }
2510                 }
2511
2512                 if (nbr_entries) {
2513                         BLI_mutex_lock(lock);
2514
2515                         *do_update = true;
2516
2517                         BLI_movelisttolist(&filelist->filelist.entries, &entries);
2518                         filelist->filelist.nbr_entries += nbr_entries;
2519
2520                         BLI_mutex_unlock(lock);
2521                 }
2522
2523                 nbr_done_dirs++;
2524                 *progress = (float)nbr_done_dirs / (float)nbr_todo_dirs;
2525                 MEM_freeN(subdir);
2526         }
2527
2528         /* If we were interrupted by stop, stack may not be empty and we need to free pending dir paths. */
2529         while (!BLI_stack_is_empty(todo_dirs)) {
2530                 td_dir = BLI_stack_peek(todo_dirs);
2531                 MEM_freeN(td_dir->dir);
2532                 BLI_stack_discard(todo_dirs);
2533         }
2534         BLI_stack_free(todo_dirs);
2535 }
2536
2537 static void filelist_readjob_dir(
2538         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2539 {
2540         filelist_readjob_do(false, filelist, main_name, stop, do_update, progress, lock);
2541 }
2542
2543 static void filelist_readjob_lib(
2544         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2545 {
2546         filelist_readjob_do(true, filelist, main_name, stop, do_update, progress, lock);
2547 }
2548
2549 static void filelist_readjob_main(
2550         FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock)
2551 {
2552         /* TODO! */
2553         filelist_readjob_dir(filelist, main_name, stop, do_update, progress, lock);
2554 }
2555
2556
2557 typedef struct FileListReadJob {
2558         ThreadMutex lock;
2559         char main_name[FILE_MAX];
2560         struct FileList *filelist;
2561         struct FileList *tmp_filelist;  /* XXX We may use a simpler struct here... just a linked list and root path? */
2562 } FileListReadJob;
2563
2564 static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress)
2565 {
2566         FileListReadJob *flrj = flrjv;
2567
2568 //      printf("START filelist reading (%d files, main thread: %d)\n",
2569 //             flrj->filelist->filelist.nbr_entries, BLI_thread_is_main());
2570
2571         BLI_mutex_lock(&flrj->lock);
2572
2573         BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist);
2574
2575         flrj->tmp_filelist = MEM_dupallocN(flrj->filelist);
2576
2577         BLI_listbase_clear(&flrj->tmp_filelist->filelist.entries);
2578         flrj->tmp_filelist->filelist.nbr_entries = 0;
2579
2580         flrj->tmp_filelist->filelist_intern.filtered = NULL;
2581         BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries);
2582         memset(flrj->tmp_filelist->filelist_intern.curr_uuid, 0, sizeof(flrj->tmp_filelist->filelist_intern.curr_uuid));
2583
2584         flrj->tmp_filelist->libfiledata = NULL;
2585         memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache));
2586         flrj->tmp_filelist->selection_state = NULL;
2587
2588         BLI_mutex_unlock(&flrj->lock);
2589
2590         flrj->tmp_filelist->read_jobf(flrj->tmp_filelist, flrj->main_name, stop, do_update, progress, &flrj->lock);
2591 }
2592
2593 static void filelist_readjob_update(void *flrjv)
2594 {
2595         FileListReadJob *flrj = flrjv;
2596         FileListIntern *fl_intern = &flrj->filelist->filelist_intern;
2597         ListBase new_entries = {NULL};
2598         int nbr_entries, new_nbr_entries = 0;
2599
2600         BLI_movelisttolist(&new_entries, &fl_intern->entries);
2601         nbr_entries = flrj->filelist->filelist.nbr_entries;
2602
2603         BLI_mutex_lock(&flrj->lock);
2604
2605         if (flrj->tmp_filelist->filelist.nbr_entries) {
2606                 /* We just move everything out of 'thread context' into final list. */
2607                 new_nbr_entries = flrj->tmp_filelist->filelist.nbr_entries;
2608                 BLI_movelisttolist(&new_entries, &flrj->tmp_filelist->filelist.entries);
2609                 flrj->tmp_filelist->filelist.nbr_entries = 0;
2610         }
2611
2612         BLI_mutex_unlock(&flrj->lock);
2613
2614         if (new_nbr_entries) {
2615                 /* Do not clear selection cache, we can assume already 'selected' uuids are still valid! */
2616                 filelist_clear_ex(flrj->filelist, true, false);
2617
2618                 flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING);
2619         }
2620
2621         /* if no new_nbr_entries, this is NOP */
2622         BLI_movelisttolist(&fl_intern->entries, &new_entries);
2623         flrj->filelist->filelist.nbr_entries = nbr_entries + new_nbr_entries;
2624 }
2625
2626 static void filelist_readjob_endjob(void *flrjv)
2627 {
2628         FileListReadJob *flrj = flrjv;
2629
2630         /* In case there would be some dangling update... */
2631         filelist_readjob_update(flrjv);
2632
2633         flrj->filelist->flags &= ~FL_IS_PENDING;
2634         flrj->filelist->flags |= FL_IS_READY;
2635 }
2636
2637 static void filelist_readjob_free(void *flrjv)
2638 {
2639         FileListReadJob *flrj = flrjv;
2640
2641 //      printf("END filelist reading (%d files)\n", flrj->filelist->filelist.nbr_entries);
2642
2643         if (flrj->tmp_filelist) {
2644                 /* tmp_filelist shall never ever be filtered! */
2645                 BLI_assert(flrj->tmp_filelist->filelist.nbr_entries == 0);
2646                 BLI_assert(BLI_listbase_is_empty(&flrj->tmp_filelist->filelist.entries));
2647
2648                 filelist_freelib(flrj->tmp_filelist);
2649                 filelist_free(flrj->tmp_filelist);
2650                 MEM_freeN(flrj->tmp_filelist);
2651         }
2652
2653         BLI_mutex_end(&flrj->lock);
2654
2655         MEM_freeN(flrj);
2656 }
2657
2658 void filelist_readjob_start(FileList *filelist, const bContext *C)
2659 {
2660         wmJob *wm_job;
2661         FileListReadJob *flrj;
2662
2663         /* prepare job data */
2664         flrj = MEM_callocN(sizeof(*flrj), __func__);
2665         flrj->filelist = filelist;
2666         BLI_strncpy(flrj->main_name, G.main->name, sizeof(flrj->main_name));
2667
2668         filelist->flags &= ~(FL_FORCE_RESET | FL_IS_READY);
2669         filelist->flags |= FL_IS_PENDING;
2670
2671         BLI_mutex_init(&flrj->lock);
2672
2673         /* setup job */
2674         wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), CTX_wm_area(C), "Listing Dirs...",
2675                              WM_JOB_PROGRESS, WM_JOB_TYPE_FILESEL_READDIR);
2676         WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free);
2677         WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST);
2678         WM_jobs_callbacks(wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob);
2679
2680         /* start the job */
2681         WM_jobs_start(CTX_wm_manager(C), wm_job);
2682 }
2683
2684 void filelist_readjob_stop(wmWindowManager *wm, ScrArea *sa)
2685 {
2686         WM_jobs_kill_type(wm, sa, WM_JOB_TYPE_FILESEL_READDIR);
2687 }
2688
2689 int filelist_readjob_running(wmWindowManager *wm, ScrArea *sa)
2690 {
2691         return WM_jobs_test(wm, sa, WM_JOB_TYPE_FILESEL_READDIR);
2692 }