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) 2001-2002 by NaN Holding BV.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup spfile
22  */
23 
24 #include <math.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "MEM_guardedalloc.h"
30 
31 #include "BLI_blenlib.h"
32 #include "BLI_ghash.h"
33 #include "BLI_utildefines.h"
34 
35 #include "BLT_translation.h"
36 
37 #include "BKE_appdir.h"
38 
39 #include "ED_fileselect.h"
40 
41 #ifdef WIN32
42 /* Need to include windows.h so _WIN32_IE is defined. */
43 #  include <windows.h>
44 /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
45  * because 'near' is disabled through BLI_windstuff. */
46 #  include "BLI_winstuff.h"
47 #  include <shlobj.h>
48 #endif
49 
50 #include "UI_interface_icons.h"
51 #include "UI_resources.h"
52 #include "WM_api.h"
53 #include "WM_types.h"
54 
55 #ifdef __APPLE__
56 #  include <Carbon/Carbon.h>
57 #endif /* __APPLE__ */
58 
59 #ifdef __linux__
60 #  include "BLI_fileops_types.h"
61 #  include <mntent.h>
62 #endif
63 
64 #include "fsmenu.h" /* include ourselves */
65 
66 /* FSMENU HANDLING */
67 
68 typedef struct FSMenu {
69   FSMenuEntry *fsmenu_system;
70   FSMenuEntry *fsmenu_system_bookmarks;
71   FSMenuEntry *fsmenu_bookmarks;
72   FSMenuEntry *fsmenu_recent;
73   FSMenuEntry *fsmenu_other;
74 } FSMenu;
75 
76 static FSMenu *g_fsmenu = NULL;
77 
ED_fsmenu_get(void)78 FSMenu *ED_fsmenu_get(void)
79 {
80   if (!g_fsmenu) {
81     g_fsmenu = MEM_callocN(sizeof(struct FSMenu), "fsmenu");
82   }
83   return g_fsmenu;
84 }
85 
ED_fsmenu_get_category(struct FSMenu * fsmenu,FSMenuCategory category)86 struct FSMenuEntry *ED_fsmenu_get_category(struct FSMenu *fsmenu, FSMenuCategory category)
87 {
88   FSMenuEntry *fsm_head = NULL;
89 
90   switch (category) {
91     case FS_CATEGORY_SYSTEM:
92       fsm_head = fsmenu->fsmenu_system;
93       break;
94     case FS_CATEGORY_SYSTEM_BOOKMARKS:
95       fsm_head = fsmenu->fsmenu_system_bookmarks;
96       break;
97     case FS_CATEGORY_BOOKMARKS:
98       fsm_head = fsmenu->fsmenu_bookmarks;
99       break;
100     case FS_CATEGORY_RECENT:
101       fsm_head = fsmenu->fsmenu_recent;
102       break;
103     case FS_CATEGORY_OTHER:
104       fsm_head = fsmenu->fsmenu_other;
105       break;
106   }
107   return fsm_head;
108 }
109 
110 /* -------------------------------------------------------------------- */
111 /** \name XDG User Directory Support (Unix)
112  *
113  * Generic Unix, Use XDG when available, otherwise fallback to the home directory.
114  * \{ */
115 
116 /**
117  * Look for `user-dirs.dirs`, where localized or custom user folders are defined,
118  * and store their paths in a GHash.
119  */
fsmenu_xdg_user_dirs_parse(const char * home)120 static GHash *fsmenu_xdg_user_dirs_parse(const char *home)
121 {
122   /* Add to the default for variable, equals & quotes. */
123   char l[128 + FILE_MAXDIR];
124   FILE *fp;
125 
126   /* Check if the config file exists. */
127   {
128     char filepath[FILE_MAX];
129     const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
130     if (xdg_config_home != NULL) {
131       BLI_path_join(filepath, sizeof(filepath), xdg_config_home, "user-dirs.dirs", NULL);
132     }
133     else {
134       BLI_path_join(filepath, sizeof(filepath), home, ".config", "user-dirs.dirs", NULL);
135     }
136     fp = BLI_fopen(filepath, "r");
137     if (!fp) {
138       return NULL;
139     }
140   }
141   /* By default there are 8 paths. */
142   GHash *xdg_map = BLI_ghash_str_new_ex(__func__, 8);
143   while (fgets(l, sizeof(l), fp) != NULL) { /* read a line */
144 
145     /* Avoid inserting invalid values. */
146     if (STRPREFIX(l, "XDG_")) {
147       char *l_value = strchr(l, '=');
148       if (l_value != NULL) {
149         *l_value = '\0';
150         l_value++;
151 
152         BLI_str_rstrip(l_value);
153         const uint l_value_len = strlen(l_value);
154         if ((l_value[0] == '"') && (l_value_len > 0) && (l_value[l_value_len - 1] == '"')) {
155           l_value[l_value_len - 1] = '\0';
156           l_value++;
157 
158           char l_value_expanded[FILE_MAX];
159           char *l_value_final = l_value;
160 
161           /* This is currently the only variable used.
162            * Based on the 'user-dirs.dirs' man page,
163            * there is no need to resolve arbitrary environment variables. */
164           if (STRPREFIX(l_value, "$HOME" SEP_STR)) {
165             BLI_path_join(l_value_expanded, sizeof(l_value_expanded), home, l_value + 6, NULL);
166             l_value_final = l_value_expanded;
167           }
168 
169           BLI_ghash_insert(xdg_map, BLI_strdup(l), BLI_strdup(l_value_final));
170         }
171       }
172     }
173   }
174   return xdg_map;
175 }
176 
fsmenu_xdg_user_dirs_free(GHash * xdg_map)177 static void fsmenu_xdg_user_dirs_free(GHash *xdg_map)
178 {
179   if (xdg_map != NULL) {
180     BLI_ghash_free(xdg_map, MEM_freeN, MEM_freeN);
181   }
182 }
183 
184 /**
185  * Add fsmenu entry for system folders on linux.
186  * - Check if a path is stored in the GHash generated from user-dirs.dirs
187  * - If not, check for a default path in $HOME
188  *
189  * \param key: Use `user-dirs.dirs` format "XDG_EXAMPLE_DIR"
190  * \param default_path: Directory name to check in $HOME, also used for the menu entry name.
191  */
fsmenu_xdg_insert_entry(GHash * xdg_map,struct FSMenu * fsmenu,const char * key,const char * default_path,int icon,const char * home)192 static void fsmenu_xdg_insert_entry(GHash *xdg_map,
193                                     struct FSMenu *fsmenu,
194                                     const char *key,
195                                     const char *default_path,
196                                     int icon,
197                                     const char *home)
198 {
199   char xdg_path_buf[FILE_MAXDIR];
200   const char *xdg_path = xdg_map ? BLI_ghash_lookup(xdg_map, key) : NULL;
201   if (xdg_path == NULL) {
202     BLI_path_join(xdg_path_buf, sizeof(xdg_path_buf), home, default_path, NULL);
203     xdg_path = xdg_path_buf;
204   }
205   fsmenu_insert_entry(
206       fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, xdg_path, IFACE_(default_path), icon, FS_INSERT_LAST);
207 }
208 
209 /** \} */
210 
ED_fsmenu_set_category(struct FSMenu * fsmenu,FSMenuCategory category,FSMenuEntry * fsm_head)211 void ED_fsmenu_set_category(struct FSMenu *fsmenu, FSMenuCategory category, FSMenuEntry *fsm_head)
212 {
213   switch (category) {
214     case FS_CATEGORY_SYSTEM:
215       fsmenu->fsmenu_system = fsm_head;
216       break;
217     case FS_CATEGORY_SYSTEM_BOOKMARKS:
218       fsmenu->fsmenu_system_bookmarks = fsm_head;
219       break;
220     case FS_CATEGORY_BOOKMARKS:
221       fsmenu->fsmenu_bookmarks = fsm_head;
222       break;
223     case FS_CATEGORY_RECENT:
224       fsmenu->fsmenu_recent = fsm_head;
225       break;
226     case FS_CATEGORY_OTHER:
227       fsmenu->fsmenu_other = fsm_head;
228       break;
229   }
230 }
231 
ED_fsmenu_get_nentries(struct FSMenu * fsmenu,FSMenuCategory category)232 int ED_fsmenu_get_nentries(struct FSMenu *fsmenu, FSMenuCategory category)
233 {
234   FSMenuEntry *fsm_iter;
235   int count = 0;
236 
237   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter; fsm_iter = fsm_iter->next) {
238     count++;
239   }
240 
241   return count;
242 }
243 
ED_fsmenu_get_entry(struct FSMenu * fsmenu,FSMenuCategory category,int idx)244 FSMenuEntry *ED_fsmenu_get_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
245 {
246   FSMenuEntry *fsm_iter;
247 
248   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx;
249        fsm_iter = fsm_iter->next) {
250     idx--;
251   }
252 
253   return fsm_iter;
254 }
255 
ED_fsmenu_entry_get_path(struct FSMenuEntry * fsentry)256 char *ED_fsmenu_entry_get_path(struct FSMenuEntry *fsentry)
257 {
258   return fsentry->path;
259 }
260 
ED_fsmenu_entry_set_path(struct FSMenuEntry * fsentry,const char * path)261 void ED_fsmenu_entry_set_path(struct FSMenuEntry *fsentry, const char *path)
262 {
263   if ((!fsentry->path || !path || !STREQ(path, fsentry->path)) && (fsentry->path != path)) {
264     char tmp_name[FILE_MAXFILE];
265 
266     MEM_SAFE_FREE(fsentry->path);
267 
268     fsentry->path = (path && path[0]) ? BLI_strdup(path) : NULL;
269 
270     BLI_join_dirfile(tmp_name,
271                      sizeof(tmp_name),
272                      BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
273                      BLENDER_BOOKMARK_FILE);
274     fsmenu_write_file(ED_fsmenu_get(), tmp_name);
275   }
276 }
277 
ED_fsmenu_entry_get_icon(struct FSMenuEntry * fsentry)278 int ED_fsmenu_entry_get_icon(struct FSMenuEntry *fsentry)
279 {
280   return (fsentry->icon) ? fsentry->icon : ICON_FILE_FOLDER;
281 }
282 
ED_fsmenu_entry_set_icon(struct FSMenuEntry * fsentry,const int icon)283 void ED_fsmenu_entry_set_icon(struct FSMenuEntry *fsentry, const int icon)
284 {
285   fsentry->icon = icon;
286 }
287 
fsmenu_entry_generate_name(struct FSMenuEntry * fsentry,char * name,size_t name_size)288 static void fsmenu_entry_generate_name(struct FSMenuEntry *fsentry, char *name, size_t name_size)
289 {
290   int offset = 0;
291   int len = name_size;
292 
293   if (BLI_path_name_at_index(fsentry->path, -1, &offset, &len)) {
294     /* use as size */
295     len += 1;
296   }
297 
298   BLI_strncpy(name, &fsentry->path[offset], MIN2(len, name_size));
299   if (!name[0]) {
300     name[0] = '/';
301     name[1] = '\0';
302   }
303 }
304 
ED_fsmenu_entry_get_name(struct FSMenuEntry * fsentry)305 char *ED_fsmenu_entry_get_name(struct FSMenuEntry *fsentry)
306 {
307   if (fsentry->name[0]) {
308     return fsentry->name;
309   }
310 
311   /* Here we abuse fsm_iter->name, keeping first char NULL. */
312   char *name = fsentry->name + 1;
313   size_t name_size = sizeof(fsentry->name) - 1;
314 
315   fsmenu_entry_generate_name(fsentry, name, name_size);
316   return name;
317 }
318 
ED_fsmenu_entry_set_name(struct FSMenuEntry * fsentry,const char * name)319 void ED_fsmenu_entry_set_name(struct FSMenuEntry *fsentry, const char *name)
320 {
321   if (!STREQ(name, fsentry->name)) {
322     char tmp_name[FILE_MAXFILE];
323     size_t tmp_name_size = sizeof(tmp_name);
324 
325     fsmenu_entry_generate_name(fsentry, tmp_name, tmp_name_size);
326     if (!name[0] || STREQ(tmp_name, name)) {
327       /* reset name to default behavior. */
328       fsentry->name[0] = '\0';
329     }
330     else {
331       BLI_strncpy(fsentry->name, name, sizeof(fsentry->name));
332     }
333 
334     BLI_join_dirfile(tmp_name,
335                      sizeof(tmp_name),
336                      BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
337                      BLENDER_BOOKMARK_FILE);
338     fsmenu_write_file(ED_fsmenu_get(), tmp_name);
339   }
340 }
341 
fsmenu_entry_refresh_valid(struct FSMenuEntry * fsentry)342 void fsmenu_entry_refresh_valid(struct FSMenuEntry *fsentry)
343 {
344   if (fsentry->path && fsentry->path[0]) {
345 #ifdef WIN32
346     /* XXX Special case, always consider those as valid.
347      * Thanks to Windows, which can spend five seconds to perform a mere stat() call on those paths
348      * See T43684. */
349     const char *exceptions[] = {"A:\\", "B:\\", NULL};
350     const size_t exceptions_len[] = {strlen(exceptions[0]), strlen(exceptions[1]), 0};
351     int i;
352 
353     for (i = 0; exceptions[i]; i++) {
354       if (STRCASEEQLEN(fsentry->path, exceptions[i], exceptions_len[i])) {
355         fsentry->valid = true;
356         return;
357       }
358     }
359 #endif
360     fsentry->valid = BLI_is_dir(fsentry->path);
361   }
362   else {
363     fsentry->valid = false;
364   }
365 }
366 
fsmenu_can_save(struct FSMenu * fsmenu,FSMenuCategory category,int idx)367 short fsmenu_can_save(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
368 {
369   FSMenuEntry *fsm_iter;
370 
371   for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx;
372        fsm_iter = fsm_iter->next) {
373     idx--;
374   }
375 
376   return fsm_iter ? fsm_iter->save : 0;
377 }
378 
fsmenu_insert_entry(struct FSMenu * fsmenu,FSMenuCategory category,const char * path,const char * name,int icon,FSMenuInsert flag)379 void fsmenu_insert_entry(struct FSMenu *fsmenu,
380                          FSMenuCategory category,
381                          const char *path,
382                          const char *name,
383                          int icon,
384                          FSMenuInsert flag)
385 {
386   const uint path_len = strlen(path);
387   BLI_assert(path_len > 0);
388   if (path_len == 0) {
389     return;
390   }
391   const bool has_trailing_slash = (path[path_len - 1] == SEP);
392   FSMenuEntry *fsm_prev;
393   FSMenuEntry *fsm_iter;
394   FSMenuEntry *fsm_head;
395 
396   fsm_head = ED_fsmenu_get_category(fsmenu, category);
397   fsm_prev = fsm_head; /* this is odd and not really correct? */
398 
399   for (fsm_iter = fsm_head; fsm_iter; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
400     if (fsm_iter->path) {
401       /* Compare, with/without the trailing slash in 'path'. */
402       const int cmp_ret = BLI_path_ncmp(path, fsm_iter->path, path_len);
403       if (cmp_ret == 0 && STREQ(fsm_iter->path + path_len, has_trailing_slash ? "" : SEP_STR)) {
404         if (flag & FS_INSERT_FIRST) {
405           if (fsm_iter != fsm_head) {
406             fsm_prev->next = fsm_iter->next;
407             fsm_iter->next = fsm_head;
408             ED_fsmenu_set_category(fsmenu, category, fsm_iter);
409           }
410         }
411         return;
412       }
413       if ((flag & FS_INSERT_SORTED) && cmp_ret < 0) {
414         break;
415       }
416     }
417     else {
418       /* if we're bookmarking this, file should come
419        * before the last separator, only automatically added
420        * current dir go after the last separator. */
421       if (flag & FS_INSERT_SAVE) {
422         break;
423       }
424     }
425   }
426 
427   fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme");
428   if (has_trailing_slash) {
429     fsm_iter->path = BLI_strdup(path);
430   }
431   else {
432     fsm_iter->path = BLI_strdupn(path, path_len + 1);
433     fsm_iter->path[path_len] = SEP;
434     fsm_iter->path[path_len + 1] = '\0';
435   }
436   fsm_iter->save = (flag & FS_INSERT_SAVE) != 0;
437 
438   /* If entry is also in another list, use that icon and maybe name. */
439   /* On macOS we get icons and names for System Bookmarks from the FS_CATEGORY_OTHER list. */
440   if (ELEM(category, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT)) {
441 
442     const FSMenuCategory cats[] = {
443         FS_CATEGORY_OTHER,
444         FS_CATEGORY_SYSTEM,
445         FS_CATEGORY_SYSTEM_BOOKMARKS,
446         FS_CATEGORY_BOOKMARKS,
447     };
448     int i = ARRAY_SIZE(cats);
449     if (category == FS_CATEGORY_BOOKMARKS) {
450       i--;
451     }
452 
453     while (i--) {
454       FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, cats[i]);
455       for (; tfsm; tfsm = tfsm->next) {
456         if (STREQ(tfsm->path, fsm_iter->path)) {
457           icon = tfsm->icon;
458           if (tfsm->name[0] && (!name || !name[0])) {
459             name = tfsm->name;
460           }
461           break;
462         }
463       }
464       if (tfsm) {
465         break;
466       }
467     }
468   }
469 
470   if (name && name[0]) {
471     BLI_strncpy(fsm_iter->name, name, sizeof(fsm_iter->name));
472   }
473   else {
474     fsm_iter->name[0] = '\0';
475   }
476 
477   ED_fsmenu_entry_set_icon(fsm_iter, icon);
478 
479   fsmenu_entry_refresh_valid(fsm_iter);
480 
481   if (fsm_prev) {
482     if (flag & FS_INSERT_FIRST) {
483       fsm_iter->next = fsm_head;
484       ED_fsmenu_set_category(fsmenu, category, fsm_iter);
485     }
486     else {
487       fsm_iter->next = fsm_prev->next;
488       fsm_prev->next = fsm_iter;
489     }
490   }
491   else {
492     fsm_iter->next = fsm_head;
493     ED_fsmenu_set_category(fsmenu, category, fsm_iter);
494   }
495 }
496 
fsmenu_remove_entry(struct FSMenu * fsmenu,FSMenuCategory category,int idx)497 void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
498 {
499   FSMenuEntry *fsm_prev = NULL;
500   FSMenuEntry *fsm_iter;
501   FSMenuEntry *fsm_head;
502 
503   fsm_head = ED_fsmenu_get_category(fsmenu, category);
504 
505   for (fsm_iter = fsm_head; fsm_iter && idx; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
506     idx--;
507   }
508 
509   if (fsm_iter) {
510     /* you should only be able to remove entries that were
511      * not added by default, like windows drives.
512      * also separators (where path == NULL) shouldn't be removed */
513     if (fsm_iter->save && fsm_iter->path) {
514 
515       /* remove fsme from list */
516       if (fsm_prev) {
517         fsm_prev->next = fsm_iter->next;
518       }
519       else {
520         fsm_head = fsm_iter->next;
521         ED_fsmenu_set_category(fsmenu, category, fsm_head);
522       }
523       /* free entry */
524       MEM_freeN(fsm_iter->path);
525       MEM_freeN(fsm_iter);
526     }
527   }
528 }
529 
fsmenu_write_file(struct FSMenu * fsmenu,const char * filename)530 void fsmenu_write_file(struct FSMenu *fsmenu, const char *filename)
531 {
532   FSMenuEntry *fsm_iter = NULL;
533   char fsm_name[FILE_MAX];
534   int nwritten = 0;
535 
536   FILE *fp = BLI_fopen(filename, "w");
537   if (!fp) {
538     return;
539   }
540 
541   fprintf(fp, "[Bookmarks]\n");
542   for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter;
543        fsm_iter = fsm_iter->next) {
544     if (fsm_iter->path && fsm_iter->save) {
545       fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
546       if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
547         fprintf(fp, "!%s\n", fsm_iter->name);
548       }
549       fprintf(fp, "%s\n", fsm_iter->path);
550     }
551   }
552   fprintf(fp, "[Recent]\n");
553   for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT);
554        fsm_iter && (nwritten < FSMENU_RECENT_MAX);
555        fsm_iter = fsm_iter->next, nwritten++) {
556     if (fsm_iter->path && fsm_iter->save) {
557       fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
558       if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
559         fprintf(fp, "!%s\n", fsm_iter->name);
560       }
561       fprintf(fp, "%s\n", fsm_iter->path);
562     }
563   }
564   fclose(fp);
565 }
566 
fsmenu_read_bookmarks(struct FSMenu * fsmenu,const char * filename)567 void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filename)
568 {
569   char line[FILE_MAXDIR];
570   char name[FILE_MAXFILE];
571   FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
572   FILE *fp;
573 
574   fp = BLI_fopen(filename, "r");
575   if (!fp) {
576     return;
577   }
578 
579   name[0] = '\0';
580 
581   while (fgets(line, sizeof(line), fp) != NULL) { /* read a line */
582     if (STREQLEN(line, "[Bookmarks]", 11)) {
583       category = FS_CATEGORY_BOOKMARKS;
584     }
585     else if (STREQLEN(line, "[Recent]", 8)) {
586       category = FS_CATEGORY_RECENT;
587     }
588     else if (line[0] == '!') {
589       int len = strlen(line);
590       if (len > 0) {
591         if (line[len - 1] == '\n') {
592           line[len - 1] = '\0';
593         }
594         BLI_strncpy(name, line + 1, sizeof(name));
595       }
596     }
597     else {
598       int len = strlen(line);
599       if (len > 0) {
600         if (line[len - 1] == '\n') {
601           line[len - 1] = '\0';
602         }
603         /* don't do this because it can be slow on network drives,
604          * having a bookmark from a drive that's ejected or so isn't
605          * all _that_ bad */
606 #if 0
607         if (BLI_exists(line))
608 #endif
609         {
610           fsmenu_insert_entry(fsmenu, category, line, name, ICON_FILE_FOLDER, FS_INSERT_SAVE);
611         }
612       }
613       /* always reset name. */
614       name[0] = '\0';
615     }
616   }
617   fclose(fp);
618 }
619 
620 #ifdef WIN32
621 /* Add a Windows known folder path to the System list. */
fsmenu_add_windows_folder(struct FSMenu * fsmenu,FSMenuCategory category,REFKNOWNFOLDERID rfid,const char * name,const int icon,FSMenuInsert flag)622 static void fsmenu_add_windows_folder(struct FSMenu *fsmenu,
623                                       FSMenuCategory category,
624                                       REFKNOWNFOLDERID rfid,
625                                       const char *name,
626                                       const int icon,
627                                       FSMenuInsert flag)
628 {
629   LPWSTR pPath;
630   char line[FILE_MAXDIR];
631   if (SHGetKnownFolderPath(rfid, 0, NULL, &pPath) == S_OK) {
632     BLI_strncpy_wchar_as_utf8(line, pPath, FILE_MAXDIR);
633     CoTaskMemFree(pPath);
634     fsmenu_insert_entry(fsmenu, category, line, name, icon, flag);
635   }
636 }
637 #endif
638 
fsmenu_read_system(struct FSMenu * fsmenu,int read_bookmarks)639 void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
640 {
641   char line[FILE_MAXDIR];
642 #ifdef WIN32
643   /* Add the drive names to the listing */
644   {
645     wchar_t wline[FILE_MAXDIR];
646     __int64 tmp;
647     char tmps[4], *name;
648 
649     tmp = GetLogicalDrives();
650 
651     for (int i = 0; i < 26; i++) {
652       if ((tmp >> i) & 1) {
653         tmps[0] = 'A' + i;
654         tmps[1] = ':';
655         tmps[2] = '\\';
656         tmps[3] = '\0';
657         name = NULL;
658 
659         /* Flee from horrible win querying hover floppy drives! */
660         if (i > 1) {
661           /* Try to get a friendly drive description. */
662           SHFILEINFOW shFile = {0};
663           BLI_strncpy_wchar_from_utf8(wline, tmps, 4);
664           if (SHGetFileInfoW(wline, 0, &shFile, sizeof(SHFILEINFOW), SHGFI_DISPLAYNAME)) {
665             BLI_strncpy_wchar_as_utf8(line, shFile.szDisplayName, FILE_MAXDIR);
666             name = line;
667           }
668         }
669         if (name == NULL) {
670           name = tmps;
671         }
672 
673         int icon = ICON_DISK_DRIVE;
674         switch (GetDriveType(tmps)) {
675           case DRIVE_REMOVABLE:
676             icon = ICON_EXTERNAL_DRIVE;
677             break;
678           case DRIVE_CDROM:
679             icon = ICON_DISC;
680             break;
681           case DRIVE_FIXED:
682           case DRIVE_RAMDISK:
683             icon = ICON_DISK_DRIVE;
684             break;
685           case DRIVE_REMOTE:
686             icon = ICON_NETWORK_DRIVE;
687             break;
688         }
689 
690         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, name, icon, FS_INSERT_SORTED);
691       }
692     }
693 
694     /* Get Special Folder Locations. */
695     if (read_bookmarks) {
696 
697       /* These items are shown in System List. */
698       fsmenu_add_windows_folder(fsmenu,
699                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
700                                 &FOLDERID_Profile,
701                                 IFACE_("Home"),
702                                 ICON_HOME,
703                                 FS_INSERT_LAST);
704       fsmenu_add_windows_folder(fsmenu,
705                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
706                                 &FOLDERID_Desktop,
707                                 IFACE_("Desktop"),
708                                 ICON_DESKTOP,
709                                 FS_INSERT_LAST);
710       fsmenu_add_windows_folder(fsmenu,
711                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
712                                 &FOLDERID_Documents,
713                                 IFACE_("Documents"),
714                                 ICON_DOCUMENTS,
715                                 FS_INSERT_LAST);
716       fsmenu_add_windows_folder(fsmenu,
717                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
718                                 &FOLDERID_Downloads,
719                                 IFACE_("Downloads"),
720                                 ICON_IMPORT,
721                                 FS_INSERT_LAST);
722       fsmenu_add_windows_folder(fsmenu,
723                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
724                                 &FOLDERID_Music,
725                                 IFACE_("Music"),
726                                 ICON_FILE_SOUND,
727                                 FS_INSERT_LAST);
728       fsmenu_add_windows_folder(fsmenu,
729                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
730                                 &FOLDERID_Pictures,
731                                 IFACE_("Pictures"),
732                                 ICON_FILE_IMAGE,
733                                 FS_INSERT_LAST);
734       fsmenu_add_windows_folder(fsmenu,
735                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
736                                 &FOLDERID_Videos,
737                                 IFACE_("Videos"),
738                                 ICON_FILE_MOVIE,
739                                 FS_INSERT_LAST);
740       fsmenu_add_windows_folder(fsmenu,
741                                 FS_CATEGORY_SYSTEM_BOOKMARKS,
742                                 &FOLDERID_Fonts,
743                                 IFACE_("Fonts"),
744                                 ICON_FILE_FONT,
745                                 FS_INSERT_LAST);
746 
747       /* These items are just put in path cache for thumbnail views and if bookmarked. */
748 
749       fsmenu_add_windows_folder(
750           fsmenu, FS_CATEGORY_OTHER, &FOLDERID_UserProfiles, NULL, ICON_COMMUNITY, FS_INSERT_LAST);
751 
752       fsmenu_add_windows_folder(
753           fsmenu, FS_CATEGORY_OTHER, &FOLDERID_SkyDrive, NULL, ICON_URL, FS_INSERT_LAST);
754     }
755   }
756 #elif defined(__APPLE__)
757   {
758     /* We store some known macOS system paths and corresponding icons
759      * and names in the FS_CATEGORY_OTHER (not displayed directly) category. */
760     fsmenu_insert_entry(fsmenu,
761                         FS_CATEGORY_OTHER,
762                         "/Library/Fonts/",
763                         IFACE_("Fonts"),
764                         ICON_FILE_FONT,
765                         FS_INSERT_LAST);
766     fsmenu_insert_entry(fsmenu,
767                         FS_CATEGORY_OTHER,
768                         "/Applications/",
769                         IFACE_("Applications"),
770                         ICON_FILE_FOLDER,
771                         FS_INSERT_LAST);
772 
773     const char *home = BLI_getenv("HOME");
774 
775 #  define FS_MACOS_PATH(path, name, icon) \
776     BLI_snprintf(line, sizeof(line), path, home); \
777     fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, line, name, icon, FS_INSERT_LAST);
778 
779     FS_MACOS_PATH("%s/", NULL, ICON_HOME)
780     FS_MACOS_PATH("%s/Desktop/", IFACE_("Desktop"), ICON_DESKTOP)
781     FS_MACOS_PATH("%s/Documents/", IFACE_("Documents"), ICON_DOCUMENTS)
782     FS_MACOS_PATH("%s/Downloads/", IFACE_("Downloads"), ICON_IMPORT)
783     FS_MACOS_PATH("%s/Movies/", IFACE_("Movies"), ICON_FILE_MOVIE)
784     FS_MACOS_PATH("%s/Music/", IFACE_("Music"), ICON_FILE_SOUND)
785     FS_MACOS_PATH("%s/Pictures/", IFACE_("Pictures"), ICON_FILE_IMAGE)
786     FS_MACOS_PATH("%s/Library/Fonts/", IFACE_("Fonts"), ICON_FILE_FONT)
787 
788 #  undef FS_MACOS_PATH
789 
790     /* Get mounted volumes better method OSX 10.6 and higher, see:
791      * https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html
792      */
793 
794     /* We get all volumes sorted including network and do not relay
795      * on user-defined finder visibility, less confusing. */
796 
797     CFURLRef cfURL = NULL;
798     CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
799     CFURLEnumeratorRef volEnum = CFURLEnumeratorCreateForMountedVolumes(
800         NULL, kCFURLEnumeratorSkipInvisibles, NULL);
801 
802     while (result != kCFURLEnumeratorEnd) {
803       char defPath[FILE_MAX];
804 
805       result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, NULL);
806       if (result != kCFURLEnumeratorSuccess) {
807         continue;
808       }
809 
810       CFURLGetFileSystemRepresentation(cfURL, false, (UInt8 *)defPath, FILE_MAX);
811 
812       /* Get name of the volume. */
813       char name[FILE_MAXFILE] = "";
814       CFStringRef nameString = NULL;
815       CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeLocalizedNameKey, &nameString, NULL);
816       if (nameString != NULL) {
817         CFStringGetCString(nameString, name, sizeof(name), kCFStringEncodingUTF8);
818         CFRelease(nameString);
819       }
820 
821       /* Set icon for regular, removable or network drive. */
822       int icon = ICON_DISK_DRIVE;
823       CFBooleanRef localKey = NULL;
824       CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeIsLocalKey, &localKey, NULL);
825       if (localKey != NULL) {
826         if (!CFBooleanGetValue(localKey)) {
827           icon = ICON_NETWORK_DRIVE;
828         }
829         else {
830           CFBooleanRef ejectableKey = NULL;
831           CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeIsEjectableKey, &ejectableKey, NULL);
832           if (ejectableKey != NULL) {
833             if (CFBooleanGetValue(ejectableKey)) {
834               icon = ICON_EXTERNAL_DRIVE;
835             }
836             CFRelease(ejectableKey);
837           }
838         }
839         CFRelease(localKey);
840       }
841 
842       fsmenu_insert_entry(
843           fsmenu, FS_CATEGORY_SYSTEM, defPath, name[0] ? name : NULL, icon, FS_INSERT_SORTED);
844     }
845 
846     CFRelease(volEnum);
847 
848     /* kLSSharedFileListFavoriteItems is deprecated, but available till macOS 10.15.
849      * Will have to find a new method to sync the Finder Favorites with File Browser. */
850 #  pragma GCC diagnostic push
851 #  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
852     /* Finally get user favorite places */
853     if (read_bookmarks) {
854       UInt32 seed;
855       LSSharedFileListRef list = LSSharedFileListCreate(
856           NULL, kLSSharedFileListFavoriteItems, NULL);
857       CFArrayRef pathesArray = LSSharedFileListCopySnapshot(list, &seed);
858       CFIndex pathesCount = CFArrayGetCount(pathesArray);
859 
860       for (CFIndex i = 0; i < pathesCount; i++) {
861         LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(
862             pathesArray, i);
863 
864         CFURLRef cfURL = NULL;
865         OSErr err = LSSharedFileListItemResolve(itemRef,
866                                                 kLSSharedFileListNoUserInteraction |
867                                                     kLSSharedFileListDoNotMountVolumes,
868                                                 &cfURL,
869                                                 NULL);
870         if (err != noErr || !cfURL) {
871           continue;
872         }
873 
874         CFStringRef pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
875 
876         if (pathString == NULL ||
877             !CFStringGetCString(pathString, line, sizeof(line), kCFStringEncodingUTF8)) {
878           continue;
879         }
880 
881         /* Exclude "all my files" as it makes no sense in blender fileselector */
882         /* Exclude "airdrop" if wlan not active as it would show "" ) */
883         if (!strstr(line, "myDocuments.cannedSearch") && (*line != '\0')) {
884           fsmenu_insert_entry(
885               fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, ICON_FILE_FOLDER, FS_INSERT_LAST);
886         }
887 
888         CFRelease(pathString);
889         CFRelease(cfURL);
890       }
891 
892       CFRelease(pathesArray);
893       CFRelease(list);
894     }
895 #  pragma GCC diagnostic pop
896   }
897 #else
898   /* unix */
899   {
900     const char *home = BLI_getenv("HOME");
901 
902     if (read_bookmarks && home) {
903 
904       fsmenu_insert_entry(
905           fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, home, IFACE_("Home"), ICON_HOME, FS_INSERT_LAST);
906 
907       /* Follow the XDG spec, check if these are available. */
908       GHash *xdg_map = fsmenu_xdg_user_dirs_parse(home);
909 
910       struct {
911         const char *key;
912         const char *default_path;
913         BIFIconID icon;
914       } xdg_items[] = {
915           {"XDG_DESKTOP_DIR", "Desktop", ICON_DESKTOP},
916           {"XDG_DOCUMENTS_DIR", "Documents", ICON_DOCUMENTS},
917           {"XDG_DOWNLOAD_DIR", "Downloads", ICON_IMPORT},
918           {"XDG_VIDEOS_DIR", "Videos", ICON_FILE_MOVIE},
919           {"XDG_PICTURES_DIR", "Pictures", ICON_FILE_IMAGE},
920           {"XDG_MUSIC_DIR", "Music", ICON_FILE_SOUND},
921       };
922 
923       for (int i = 0; i < ARRAY_SIZE(xdg_items); i++) {
924         fsmenu_xdg_insert_entry(
925             xdg_map, fsmenu, xdg_items[i].key, xdg_items[i].default_path, xdg_items[i].icon, home);
926       }
927 
928       fsmenu_xdg_user_dirs_free(xdg_map);
929     }
930 
931     {
932       int found = 0;
933 #  ifdef __linux__
934       /* loop over mount points */
935       struct mntent *mnt;
936       FILE *fp;
937 
938       fp = setmntent(MOUNTED, "r");
939       if (fp == NULL) {
940         fprintf(stderr, "could not get a list of mounted file-systems\n");
941       }
942       else {
943         while ((mnt = getmntent(fp))) {
944           if (STRPREFIX(mnt->mnt_dir, "/boot")) {
945             /* Hide share not usable to the user. */
946             continue;
947           }
948           if (!STRPREFIX(mnt->mnt_fsname, "/dev")) {
949             continue;
950           }
951           if (STRPREFIX(mnt->mnt_fsname, "/dev/loop")) {
952             /* The dev/loop* entries are SNAPS used by desktop environment
953              * (Gnome) no need for them to show up in the list. */
954             continue;
955           }
956 
957           fsmenu_insert_entry(
958               fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, NULL, ICON_DISK_DRIVE, FS_INSERT_SORTED);
959 
960           found = 1;
961         }
962         if (endmntent(fp) == 0) {
963           fprintf(stderr, "could not close the list of mounted filesystems\n");
964         }
965       }
966       /* Check gvfs shares. */
967       const char *const xdg_runtime_dir = BLI_getenv("XDG_RUNTIME_DIR");
968       if (xdg_runtime_dir != NULL) {
969         struct direntry *dir;
970         char name[FILE_MAX];
971         BLI_join_dirfile(name, sizeof(name), xdg_runtime_dir, "gvfs/");
972         const uint dir_len = BLI_filelist_dir_contents(name, &dir);
973         for (uint i = 0; i < dir_len; i++) {
974           if ((dir[i].type & S_IFDIR)) {
975             const char *dirname = dir[i].relname;
976             if (dirname[0] != '.') {
977               /* Dir names contain a lot of unwanted text.
978                * Assuming every entry ends with the share name */
979               const char *label = strstr(dirname, "share=");
980               if (label != NULL) {
981                 /* Move pointer so "share=" is trimmed off
982                  * or use full dirname as label. */
983                 const char *label_test = label + 6;
984                 label = *label_test ? label_test : dirname;
985               }
986               BLI_snprintf(line, sizeof(line), "%s%s", name, dirname);
987               fsmenu_insert_entry(
988                   fsmenu, FS_CATEGORY_SYSTEM, line, label, ICON_NETWORK_DRIVE, FS_INSERT_SORTED);
989               found = 1;
990             }
991           }
992         }
993         BLI_filelist_free(dir, dir_len);
994       }
995 #  endif
996 
997       /* fallback */
998       if (!found) {
999         fsmenu_insert_entry(
1000             fsmenu, FS_CATEGORY_SYSTEM, "/", NULL, ICON_DISK_DRIVE, FS_INSERT_SORTED);
1001       }
1002     }
1003   }
1004 #endif
1005 
1006 #if defined(WIN32) || defined(__APPLE__)
1007   /* Quiet warnings. */
1008   UNUSED_VARS(fsmenu_xdg_insert_entry, fsmenu_xdg_user_dirs_parse, fsmenu_xdg_user_dirs_free);
1009 #endif
1010 
1011   /* For all platforms, we add some directories from User Preferences to
1012    * the FS_CATEGORY_OTHER category so that these directories
1013    * have the appropriate icons when they are added to the Bookmarks. */
1014 #define FS_UDIR_PATH(dir, icon) \
1015   if (BLI_strnlen(dir, 3) > 2) { \
1016     fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, dir, NULL, icon, FS_INSERT_LAST); \
1017   }
1018 
1019   FS_UDIR_PATH(U.fontdir, ICON_FILE_FONT)
1020   FS_UDIR_PATH(U.textudir, ICON_FILE_IMAGE)
1021   FS_UDIR_PATH(U.pythondir, ICON_FILE_SCRIPT)
1022   FS_UDIR_PATH(U.sounddir, ICON_FILE_SOUND)
1023   FS_UDIR_PATH(U.tempdir, ICON_TEMP)
1024 
1025 #undef FS_UDIR_PATH
1026 }
1027 
fsmenu_free_category(struct FSMenu * fsmenu,FSMenuCategory category)1028 static void fsmenu_free_category(struct FSMenu *fsmenu, FSMenuCategory category)
1029 {
1030   FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
1031 
1032   while (fsm_iter) {
1033     FSMenuEntry *fsm_next = fsm_iter->next;
1034 
1035     if (fsm_iter->path) {
1036       MEM_freeN(fsm_iter->path);
1037     }
1038     MEM_freeN(fsm_iter);
1039 
1040     fsm_iter = fsm_next;
1041   }
1042 }
1043 
fsmenu_refresh_system_category(struct FSMenu * fsmenu)1044 void fsmenu_refresh_system_category(struct FSMenu *fsmenu)
1045 {
1046   fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
1047   ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM, NULL);
1048 
1049   fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
1050   ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, NULL);
1051 
1052   /* Add all entries to system category */
1053   fsmenu_read_system(fsmenu, true);
1054 }
1055 
fsmenu_free_ex(FSMenu ** fsmenu)1056 static void fsmenu_free_ex(FSMenu **fsmenu)
1057 {
1058   if (*fsmenu != NULL) {
1059     fsmenu_free_category(*fsmenu, FS_CATEGORY_SYSTEM);
1060     fsmenu_free_category(*fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
1061     fsmenu_free_category(*fsmenu, FS_CATEGORY_BOOKMARKS);
1062     fsmenu_free_category(*fsmenu, FS_CATEGORY_RECENT);
1063     fsmenu_free_category(*fsmenu, FS_CATEGORY_OTHER);
1064     MEM_freeN(*fsmenu);
1065   }
1066 
1067   *fsmenu = NULL;
1068 }
1069 
fsmenu_free(void)1070 void fsmenu_free(void)
1071 {
1072   fsmenu_free_ex(&g_fsmenu);
1073 }
1074 
fsmenu_copy_category(struct FSMenu * fsmenu_dst,struct FSMenu * fsmenu_src,const FSMenuCategory category)1075 static void fsmenu_copy_category(struct FSMenu *fsmenu_dst,
1076                                  struct FSMenu *fsmenu_src,
1077                                  const FSMenuCategory category)
1078 {
1079   FSMenuEntry *fsm_dst_prev = NULL, *fsm_dst_head = NULL;
1080   FSMenuEntry *fsm_src_iter = ED_fsmenu_get_category(fsmenu_src, category);
1081 
1082   for (; fsm_src_iter != NULL; fsm_src_iter = fsm_src_iter->next) {
1083     FSMenuEntry *fsm_dst = MEM_dupallocN(fsm_src_iter);
1084     if (fsm_dst->path != NULL) {
1085       fsm_dst->path = MEM_dupallocN(fsm_dst->path);
1086     }
1087 
1088     if (fsm_dst_prev != NULL) {
1089       fsm_dst_prev->next = fsm_dst;
1090     }
1091     else {
1092       fsm_dst_head = fsm_dst;
1093     }
1094     fsm_dst_prev = fsm_dst;
1095   }
1096 
1097   ED_fsmenu_set_category(fsmenu_dst, category, fsm_dst_head);
1098 }
1099 
fsmenu_copy(FSMenu * fsmenu)1100 static FSMenu *fsmenu_copy(FSMenu *fsmenu)
1101 {
1102   FSMenu *fsmenu_copy = MEM_dupallocN(fsmenu);
1103 
1104   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_SYSTEM);
1105   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_SYSTEM_BOOKMARKS);
1106   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_BOOKMARKS);
1107   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_RECENT);
1108   fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_OTHER);
1109 
1110   return fsmenu_copy;
1111 }
1112 
fsmenu_get_active_indices(struct FSMenu * fsmenu,enum FSMenuCategory category,const char * dir)1113 int fsmenu_get_active_indices(struct FSMenu *fsmenu, enum FSMenuCategory category, const char *dir)
1114 {
1115   FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
1116   int i;
1117 
1118   for (i = 0; fsm_iter; fsm_iter = fsm_iter->next, i++) {
1119     if (BLI_path_cmp(dir, fsm_iter->path) == 0) {
1120       return i;
1121     }
1122   }
1123 
1124   return -1;
1125 }
1126 
1127 /* Thanks to some bookmarks sometimes being network drives that can have tens of seconds of delay
1128  * before being defined as unreachable by the OS, we need to validate the bookmarks in an async
1129  * job...
1130  */
fsmenu_bookmark_validate_job_startjob(void * fsmenuv,short * stop,short * do_update,float * UNUSED (progress))1131 static void fsmenu_bookmark_validate_job_startjob(
1132     void *fsmenuv,
1133     /* Cannot be const, this function implements wm_jobs_start_callback.
1134      * NOLINTNEXTLINE: readability-non-const-parameter. */
1135     short *stop,
1136     short *do_update,
1137     float *UNUSED(progress))
1138 {
1139   FSMenu *fsmenu = fsmenuv;
1140 
1141   int categories[] = {
1142       FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
1143 
1144   for (size_t i = ARRAY_SIZE(categories); i--;) {
1145     FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, categories[i]);
1146     for (; fsm_iter; fsm_iter = fsm_iter->next) {
1147       if (*stop) {
1148         return;
1149       }
1150       /* Note that we do not really need atomics primitives or thread locks here, since this only
1151        * sets one short, which is assumed to be 'atomic'-enough for us here. */
1152       fsmenu_entry_refresh_valid(fsm_iter);
1153       *do_update = true;
1154     }
1155   }
1156 }
1157 
fsmenu_bookmark_validate_job_update(void * fsmenuv)1158 static void fsmenu_bookmark_validate_job_update(void *fsmenuv)
1159 {
1160   FSMenu *fsmenu_job = fsmenuv;
1161 
1162   int categories[] = {
1163       FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
1164 
1165   for (size_t i = ARRAY_SIZE(categories); i--;) {
1166     FSMenuEntry *fsm_iter_src = ED_fsmenu_get_category(fsmenu_job, categories[i]);
1167     FSMenuEntry *fsm_iter_dst = ED_fsmenu_get_category(ED_fsmenu_get(), categories[i]);
1168     for (; fsm_iter_dst != NULL; fsm_iter_dst = fsm_iter_dst->next) {
1169       while (fsm_iter_src != NULL && !STREQ(fsm_iter_dst->path, fsm_iter_src->path)) {
1170         fsm_iter_src = fsm_iter_src->next;
1171       }
1172       if (fsm_iter_src == NULL) {
1173         return;
1174       }
1175       fsm_iter_dst->valid = fsm_iter_src->valid;
1176     }
1177   }
1178 }
1179 
fsmenu_bookmark_validate_job_end(void * fsmenuv)1180 static void fsmenu_bookmark_validate_job_end(void *fsmenuv)
1181 {
1182   /* In case there would be some dangling update... */
1183   fsmenu_bookmark_validate_job_update(fsmenuv);
1184 }
1185 
fsmenu_bookmark_validate_job_free(void * fsmenuv)1186 static void fsmenu_bookmark_validate_job_free(void *fsmenuv)
1187 {
1188   FSMenu *fsmenu = fsmenuv;
1189   fsmenu_free_ex(&fsmenu);
1190 }
1191 
fsmenu_bookmark_validate_job_start(wmWindowManager * wm)1192 static void fsmenu_bookmark_validate_job_start(wmWindowManager *wm)
1193 {
1194   wmJob *wm_job;
1195   FSMenu *fsmenu_job = fsmenu_copy(g_fsmenu);
1196 
1197   /* setup job */
1198   wm_job = WM_jobs_get(
1199       wm, wm->winactive, wm, "Validating Bookmarks...", 0, WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE);
1200   WM_jobs_customdata_set(wm_job, fsmenu_job, fsmenu_bookmark_validate_job_free);
1201   WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST);
1202   WM_jobs_callbacks(wm_job,
1203                     fsmenu_bookmark_validate_job_startjob,
1204                     NULL,
1205                     fsmenu_bookmark_validate_job_update,
1206                     fsmenu_bookmark_validate_job_end);
1207 
1208   /* start the job */
1209   WM_jobs_start(wm, wm_job);
1210 }
1211 
fsmenu_bookmark_validate_job_stop(wmWindowManager * wm)1212 static void fsmenu_bookmark_validate_job_stop(wmWindowManager *wm)
1213 {
1214   WM_jobs_kill_type(wm, wm, WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE);
1215 }
1216 
fsmenu_refresh_bookmarks_status(wmWindowManager * wm,FSMenu * fsmenu)1217 void fsmenu_refresh_bookmarks_status(wmWindowManager *wm, FSMenu *fsmenu)
1218 {
1219   BLI_assert(fsmenu == ED_fsmenu_get());
1220   UNUSED_VARS_NDEBUG(fsmenu);
1221 
1222   fsmenu_bookmark_validate_job_stop(wm);
1223   fsmenu_bookmark_validate_job_start(wm);
1224 }
1225