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