1 /*
2 * Copyright (C) 2002 - 2004 Red Hat, Inc.
3 * Copyright (C) 2012-2021 MATE Developers
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include <config.h>
22
23 #include "entry-directories.h"
24
25 #include <string.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <dirent.h>
29 #include <stdlib.h>
30
31 #include "menu-util.h"
32 #include "menu-monitor.h"
33
34 typedef struct CachedDir CachedDir;
35 typedef struct CachedDirMonitor CachedDirMonitor;
36
37 struct EntryDirectory {
38 CachedDir* dir;
39 char* legacy_prefix;
40
41 guint entry_type: 2;
42 guint is_legacy: 1;
43 guint refcount: 24;
44 };
45
46 struct EntryDirectoryList {
47 int refcount;
48 int length;
49 GList* dirs;
50 };
51
52 struct CachedDir {
53 CachedDir* parent;
54 char* name;
55
56 GSList* entries;
57 GSList* subdirs;
58
59 MenuMonitor* dir_monitor;
60 GSList* monitors;
61
62 guint have_read_entries: 1;
63 guint deleted: 1;
64
65 guint references;
66
67 GFunc notify;
68 gpointer notify_data;
69 };
70
71 struct CachedDirMonitor {
72 EntryDirectory* ed;
73 EntryDirectoryChangedFunc callback;
74 gpointer user_data;
75 };
76
77 static void cached_dir_add_reference (CachedDir *dir);
78 static void cached_dir_remove_reference (CachedDir *dir);
79 static void cached_dir_free (CachedDir *dir);
80 static gboolean cached_dir_load_entries_recursive (CachedDir *dir,
81 const char *dirname);
82 static void cached_dir_unref (CachedDir *dir);
83 static void cached_dir_unref_noparent (CachedDir *dir);
84 static CachedDir * cached_dir_add_subdir (CachedDir *dir,
85 const char *basename,
86 const char *path);
87 static gboolean cached_dir_remove_subdir (CachedDir *dir,
88 const char *basename);
89
90 static void handle_cached_dir_changed (MenuMonitor *monitor,
91 MenuMonitorEvent event,
92 const char *path,
93 CachedDir *dir);
94
95 /*
96 * Entry directory cache
97 */
98
99 static CachedDir* dir_cache = NULL;
100
101 static void
clear_cache(CachedDir * dir,gpointer * cache)102 clear_cache (CachedDir *dir,
103 gpointer *cache)
104 {
105 *cache = NULL;
106 }
107
108 static CachedDir *
cached_dir_new(const char * name)109 cached_dir_new (const char *name)
110 {
111 CachedDir* dir;
112
113 dir = g_new0 (CachedDir, 1);
114 dir->name = g_strdup (name);
115
116 return dir;
117 }
118
119 static CachedDir *
cached_dir_new_full(const char * name,GFunc notify,gpointer notify_data)120 cached_dir_new_full (const char *name,
121 GFunc notify,
122 gpointer notify_data)
123 {
124 CachedDir *dir;
125
126 dir = cached_dir_new (name);
127
128 dir->notify = notify;
129 dir->notify_data = notify_data;
130
131 return dir;
132 }
133
134 static void
cached_dir_free(CachedDir * dir)135 cached_dir_free (CachedDir *dir)
136 {
137 if (dir->dir_monitor)
138 {
139 menu_monitor_remove_notify (dir->dir_monitor,
140 (MenuMonitorNotifyFunc) handle_cached_dir_changed,
141 dir);
142 menu_monitor_unref (dir->dir_monitor);
143 dir->dir_monitor = NULL;
144 }
145
146 g_slist_foreach (dir->monitors, (GFunc) g_free, NULL);
147 g_slist_free (dir->monitors);
148 dir->monitors = NULL;
149
150 g_slist_foreach (dir->entries,
151 (GFunc) desktop_entry_unref,
152 NULL);
153 g_slist_free (dir->entries);
154 dir->entries = NULL;
155
156 g_slist_foreach (dir->subdirs,
157 (GFunc) cached_dir_unref_noparent,
158 NULL);
159 g_slist_free (dir->subdirs);
160 dir->subdirs = NULL;
161
162 g_free (dir->name);
163 g_free (dir);
164 }
165
166 static CachedDir *
cached_dir_ref(CachedDir * dir)167 cached_dir_ref (CachedDir *dir)
168 {
169 dir->references++;
170 return dir;
171 }
172
173 static void
cached_dir_unref(CachedDir * dir)174 cached_dir_unref (CachedDir *dir)
175 {
176 if (--dir->references == 0)
177 {
178 CachedDir *parent;
179
180 parent = dir->parent;
181
182 if (parent != NULL)
183 cached_dir_remove_subdir (parent, dir->name);
184
185 if (dir->notify)
186 dir->notify (dir, dir->notify_data);
187
188 cached_dir_free (dir);
189 }
190 }
191
192 static void
cached_dir_unref_noparent(CachedDir * dir)193 cached_dir_unref_noparent (CachedDir *dir)
194 {
195 if (--dir->references == 0)
196 {
197 if (dir->notify)
198 dir->notify (dir, dir->notify_data);
199
200 cached_dir_free (dir);
201 }
202 }
203
find_subdir(CachedDir * dir,const char * subdir)204 static inline CachedDir* find_subdir(CachedDir* dir, const char* subdir)
205 {
206 GSList *tmp;
207
208 tmp = dir->subdirs;
209 while (tmp != NULL)
210 {
211 CachedDir *sub = tmp->data;
212
213 if (strcmp (sub->name, subdir) == 0)
214 return sub;
215
216 tmp = tmp->next;
217 }
218
219 return NULL;
220 }
221
find_entry(CachedDir * dir,const char * basename)222 static DesktopEntry* find_entry(CachedDir* dir, const char* basename)
223 {
224 GSList *tmp;
225
226 tmp = dir->entries;
227 while (tmp != NULL)
228 {
229 if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
230 return tmp->data;
231
232 tmp = tmp->next;
233 }
234
235 return NULL;
236 }
237
cached_dir_find_relative_path(CachedDir * dir,const char * relative_path)238 static DesktopEntry* cached_dir_find_relative_path(CachedDir* dir, const char* relative_path)
239 {
240 DesktopEntry *retval = NULL;
241 char **split;
242 int i;
243
244 split = g_strsplit (relative_path, "/", -1);
245
246 i = 0;
247 while (split[i] != NULL)
248 {
249 if (split[i + 1] != NULL)
250 {
251 if ((dir = find_subdir (dir, split[i])) == NULL)
252 break;
253 }
254 else
255 {
256 retval = find_entry (dir, split[i]);
257 break;
258 }
259
260 ++i;
261 }
262
263 g_strfreev (split);
264
265 return retval;
266 }
267
cached_dir_lookup(const char * canonical)268 static CachedDir* cached_dir_lookup(const char* canonical)
269 {
270 CachedDir *dir;
271 char **split;
272 int i;
273
274 if (dir_cache == NULL)
275 dir_cache = cached_dir_new_full ("/",
276 (GFunc) clear_cache,
277 &dir_cache);
278 dir = dir_cache;
279
280 g_assert (canonical != NULL && canonical[0] == G_DIR_SEPARATOR);
281
282 menu_verbose ("Looking up cached dir \"%s\"\n", canonical);
283
284 split = g_strsplit (canonical + 1, "/", -1);
285
286 i = 0;
287 while (split[i] != NULL)
288 {
289 CachedDir *subdir;
290
291 subdir = cached_dir_add_subdir (dir, split[i], NULL);
292
293 dir = subdir;
294
295 ++i;
296 }
297
298 g_strfreev (split);
299
300 g_assert (dir != NULL);
301
302 return dir;
303 }
304
cached_dir_add_entry(CachedDir * dir,const char * basename,const char * path)305 static gboolean cached_dir_add_entry(CachedDir* dir, const char* basename, const char* path)
306 {
307 DesktopEntry *entry;
308
309 entry = desktop_entry_new (path);
310 if (entry == NULL)
311 return FALSE;
312
313 dir->entries = g_slist_prepend (dir->entries, entry);
314
315 return TRUE;
316 }
317
cached_dir_update_entry(CachedDir * dir,const char * basename,const char * path)318 static gboolean cached_dir_update_entry(CachedDir* dir, const char* basename, const char* path)
319 {
320 GSList *tmp;
321
322 tmp = dir->entries;
323 while (tmp != NULL)
324 {
325 if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
326 {
327 if (!desktop_entry_reload (tmp->data))
328 {
329 dir->entries = g_slist_delete_link (dir->entries, tmp);
330 }
331
332 return TRUE;
333 }
334
335 tmp = tmp->next;
336 }
337
338 return cached_dir_add_entry (dir, basename, path);
339 }
340
cached_dir_remove_entry(CachedDir * dir,const char * basename)341 static gboolean cached_dir_remove_entry(CachedDir* dir, const char* basename)
342 {
343 GSList *tmp;
344
345 tmp = dir->entries;
346 while (tmp != NULL)
347 {
348 if (strcmp (desktop_entry_get_basename (tmp->data), basename) == 0)
349 {
350 desktop_entry_unref (tmp->data);
351 dir->entries = g_slist_delete_link (dir->entries, tmp);
352 return TRUE;
353 }
354
355 tmp = tmp->next;
356 }
357
358 return FALSE;
359 }
360
361 static CachedDir *
cached_dir_add_subdir(CachedDir * dir,const char * basename,const char * path)362 cached_dir_add_subdir (CachedDir *dir,
363 const char *basename,
364 const char *path)
365 {
366 CachedDir *subdir;
367
368 subdir = find_subdir (dir, basename);
369
370 if (subdir != NULL)
371 {
372 subdir->deleted = FALSE;
373 return subdir;
374 }
375
376 subdir = cached_dir_new (basename);
377
378 if (path != NULL && !cached_dir_load_entries_recursive (subdir, path))
379 {
380 cached_dir_free (subdir);
381 return NULL;
382 }
383
384 menu_verbose ("Caching dir \"%s\"\n", basename);
385
386 subdir->parent = dir;
387 dir->subdirs = g_slist_prepend (dir->subdirs, cached_dir_ref (subdir));
388
389 return subdir;
390 }
391
cached_dir_remove_subdir(CachedDir * dir,const char * basename)392 static gboolean cached_dir_remove_subdir(CachedDir* dir, const char* basename)
393 {
394 CachedDir *subdir;
395
396 subdir = find_subdir (dir, basename);
397
398 if (subdir != NULL)
399 {
400 subdir->deleted = TRUE;
401
402 dir->subdirs = g_slist_remove (dir->subdirs, subdir);
403 cached_dir_unref (subdir);
404
405 return TRUE;
406 }
407
408 return FALSE;
409 }
410
411 static guint monitors_idle_handler = 0;
412 static GSList *pending_monitors_dirs = NULL;
413
414 static void
cached_dir_invoke_monitors(CachedDir * dir)415 cached_dir_invoke_monitors (CachedDir *dir)
416 {
417 GSList *tmp;
418
419 tmp = dir->monitors;
420 while (tmp != NULL)
421 {
422 CachedDirMonitor *monitor = tmp->data;
423 GSList *next = tmp->next;
424
425 monitor->callback (monitor->ed, monitor->user_data);
426
427 tmp = next;
428 }
429
430 /* we explicitly don't invoke monitors of the parent since an
431 * event has been queued for it too */
432 }
433
434 static gboolean
emit_monitors_in_idle(void)435 emit_monitors_in_idle (void)
436 {
437 GSList *monitors_to_emit;
438 GSList *tmp;
439
440 monitors_to_emit = pending_monitors_dirs;
441
442 pending_monitors_dirs = NULL;
443 monitors_idle_handler = 0;
444
445 tmp = monitors_to_emit;
446 while (tmp != NULL)
447 {
448 CachedDir *dir = tmp->data;
449
450 cached_dir_invoke_monitors (dir);
451 cached_dir_remove_reference (dir);
452
453 tmp = tmp->next;
454 }
455
456 g_slist_free (monitors_to_emit);
457
458 return FALSE;
459 }
460
461 static void
cached_dir_queue_monitor_event(CachedDir * dir)462 cached_dir_queue_monitor_event (CachedDir *dir)
463 {
464 GSList *tmp;
465
466 tmp = pending_monitors_dirs;
467 while (tmp != NULL)
468 {
469 CachedDir *d = tmp->data;
470 GSList *next = tmp->next;
471
472 if (dir->parent == d->parent &&
473 g_strcmp0 (dir->name, d->name) == 0)
474 break;
475
476 tmp = next;
477 }
478
479 /* not found, so let's queue it */
480 if (tmp == NULL)
481 {
482 cached_dir_add_reference (dir);
483 pending_monitors_dirs = g_slist_append (pending_monitors_dirs, dir);
484 }
485
486 if (dir->parent)
487 {
488 cached_dir_queue_monitor_event (dir->parent);
489 }
490
491 if (monitors_idle_handler == 0)
492 {
493 monitors_idle_handler = g_idle_add ((GSourceFunc) emit_monitors_in_idle, NULL);
494 }
495 }
496
handle_cached_dir_changed(MenuMonitor * monitor,MenuMonitorEvent event,const char * path,CachedDir * dir)497 static void handle_cached_dir_changed (MenuMonitor* monitor, MenuMonitorEvent event, const char* path, CachedDir* dir)
498 {
499 gboolean handled = FALSE;
500 char *basename;
501 char *dirname;
502
503 dirname = g_path_get_dirname (path);
504 basename = g_path_get_basename (path);
505
506 dir = cached_dir_lookup (dirname);
507 cached_dir_add_reference (dir);
508
509 if (g_str_has_suffix (basename, ".desktop") ||
510 g_str_has_suffix (basename, ".directory"))
511 {
512 switch (event)
513 {
514 case MENU_MONITOR_EVENT_CREATED:
515 case MENU_MONITOR_EVENT_CHANGED:
516 handled = cached_dir_update_entry (dir, basename, path);
517 break;
518
519 case MENU_MONITOR_EVENT_DELETED:
520 handled = cached_dir_remove_entry (dir, basename);
521 break;
522
523 default:
524 g_assert_not_reached ();
525 break;
526 }
527 }
528 else if (g_file_test (path, G_FILE_TEST_IS_DIR)) /* Try recursing */
529 {
530 switch (event)
531 {
532 case MENU_MONITOR_EVENT_CREATED:
533 handled = cached_dir_add_subdir (dir, basename, path) != NULL;
534 break;
535
536 case MENU_MONITOR_EVENT_CHANGED:
537 break;
538
539 case MENU_MONITOR_EVENT_DELETED:
540 handled = cached_dir_remove_subdir (dir, basename);
541 break;
542
543 default:
544 g_assert_not_reached ();
545 break;
546 }
547 }
548
549 g_free (basename);
550 g_free (dirname);
551
552 if (handled)
553 {
554 menu_verbose ("'%s' notified of '%s' %s - invalidating cache\n",
555 dir->name,
556 path,
557 event == MENU_MONITOR_EVENT_CREATED ? ("created") :
558 event == MENU_MONITOR_EVENT_DELETED ? ("deleted") : ("changed"));
559
560 /* CHANGED events don't change the set of desktop entries */
561 if (event == MENU_MONITOR_EVENT_CREATED || event == MENU_MONITOR_EVENT_DELETED)
562 {
563 _entry_directory_list_empty_desktop_cache ();
564 }
565
566 cached_dir_queue_monitor_event (dir);
567 }
568
569 cached_dir_remove_reference (dir);
570 }
571
cached_dir_ensure_monitor(CachedDir * dir,const char * dirname)572 static void cached_dir_ensure_monitor(CachedDir* dir, const char* dirname)
573 {
574 if (dir->dir_monitor == NULL)
575 {
576 dir->dir_monitor = menu_get_directory_monitor (dirname);
577 menu_monitor_add_notify (dir->dir_monitor,
578 (MenuMonitorNotifyFunc) handle_cached_dir_changed,
579 dir);
580 }
581 }
582
cached_dir_load_entries_recursive(CachedDir * dir,const char * dirname)583 static gboolean cached_dir_load_entries_recursive(CachedDir* dir, const char* dirname)
584 {
585 DIR *dp;
586 struct dirent *dent;
587 GString *fullpath;
588 gsize fullpath_len;
589
590 g_assert (dir != NULL);
591
592 if (dir->have_read_entries)
593 return TRUE;
594
595 menu_verbose ("Attempting to read entries from %s (full path %s)\n",
596 dir->name, dirname);
597
598 dp = opendir (dirname);
599 if (dp == NULL)
600 {
601 menu_verbose ("Unable to list directory \"%s\"\n",
602 dirname);
603 return FALSE;
604 }
605
606 cached_dir_ensure_monitor (dir, dirname);
607
608 fullpath = g_string_new (dirname);
609 if (fullpath->str[fullpath->len - 1] != G_DIR_SEPARATOR)
610 g_string_append_c (fullpath, G_DIR_SEPARATOR);
611
612 fullpath_len = fullpath->len;
613
614 while ((dent = readdir (dp)) != NULL)
615 {
616 /* ignore . and .. */
617 if (dent->d_name[0] == '.' &&
618 (dent->d_name[1] == '\0' ||
619 (dent->d_name[1] == '.' &&
620 dent->d_name[2] == '\0')))
621 continue;
622
623 g_string_append (fullpath, dent->d_name);
624
625 if (g_str_has_suffix (dent->d_name, ".desktop") ||
626 g_str_has_suffix (dent->d_name, ".directory"))
627 {
628 cached_dir_add_entry (dir, dent->d_name, fullpath->str);
629 }
630 else /* Try recursing */
631 {
632 cached_dir_add_subdir (dir, dent->d_name, fullpath->str);
633 }
634
635 g_string_truncate (fullpath, fullpath_len);
636 }
637
638 closedir (dp);
639
640 g_string_free (fullpath, TRUE);
641
642 dir->have_read_entries = TRUE;
643
644 return TRUE;
645 }
646
cached_dir_add_monitor(CachedDir * dir,EntryDirectory * ed,EntryDirectoryChangedFunc callback,gpointer user_data)647 static void cached_dir_add_monitor(CachedDir* dir, EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
648 {
649 CachedDirMonitor *monitor;
650 GSList *tmp;
651
652 tmp = dir->monitors;
653 while (tmp != NULL)
654 {
655 monitor = tmp->data;
656
657 if (monitor->ed == ed &&
658 monitor->callback == callback &&
659 monitor->user_data == user_data)
660 break;
661
662 tmp = tmp->next;
663 }
664
665 if (tmp == NULL)
666 {
667 monitor = g_new0 (CachedDirMonitor, 1);
668 monitor->ed = ed;
669 monitor->callback = callback;
670 monitor->user_data = user_data;
671
672 dir->monitors = g_slist_append (dir->monitors, monitor);
673 }
674 }
675
cached_dir_remove_monitor(CachedDir * dir,EntryDirectory * ed,EntryDirectoryChangedFunc callback,gpointer user_data)676 static void cached_dir_remove_monitor(CachedDir* dir, EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
677 {
678 GSList *tmp;
679
680 tmp = dir->monitors;
681 while (tmp != NULL)
682 {
683 CachedDirMonitor *monitor = tmp->data;
684 GSList *next = tmp->next;
685
686 if (monitor->ed == ed &&
687 monitor->callback == callback &&
688 monitor->user_data == user_data)
689 {
690 dir->monitors = g_slist_delete_link (dir->monitors, tmp);
691 g_free (monitor);
692 }
693
694 tmp = next;
695 }
696 }
697
cached_dir_add_reference(CachedDir * dir)698 static void cached_dir_add_reference(CachedDir* dir)
699 {
700 cached_dir_ref (dir);
701
702 if (dir->parent != NULL)
703 {
704 cached_dir_add_reference (dir->parent);
705 }
706 }
707
cached_dir_remove_reference(CachedDir * dir)708 static void cached_dir_remove_reference(CachedDir* dir)
709 {
710 CachedDir *parent;
711
712 parent = dir->parent;
713
714 cached_dir_unref (dir);
715
716 if (parent != NULL)
717 {
718 cached_dir_remove_reference (parent);
719 }
720 }
721
722 /*
723 * Entry directories
724 */
725
entry_directory_new_full(DesktopEntryType entry_type,const char * path,gboolean is_legacy,const char * legacy_prefix)726 static EntryDirectory* entry_directory_new_full(DesktopEntryType entry_type, const char* path, gboolean is_legacy, const char* legacy_prefix)
727 {
728 EntryDirectory *ed;
729 char *canonical;
730
731 menu_verbose ("Loading entry directory \"%s\" (legacy %s)\n",
732 path,
733 is_legacy ? "<yes>" : "<no>");
734
735 canonical = realpath (path, NULL);
736 if (canonical == NULL)
737 {
738 menu_verbose ("Failed to canonicalize \"%s\": %s\n",
739 path, g_strerror (errno));
740 return NULL;
741 }
742
743 ed = g_new0 (EntryDirectory, 1);
744
745 ed->dir = cached_dir_lookup (canonical);
746 g_assert (ed->dir != NULL);
747
748 cached_dir_add_reference (ed->dir);
749 cached_dir_load_entries_recursive (ed->dir, canonical);
750
751 ed->legacy_prefix = g_strdup (legacy_prefix);
752 ed->entry_type = entry_type;
753 ed->is_legacy = is_legacy != FALSE;
754 ed->refcount = 1;
755
756 g_free (canonical);
757
758 return ed;
759 }
760
entry_directory_new(DesktopEntryType entry_type,const char * path)761 EntryDirectory* entry_directory_new(DesktopEntryType entry_type, const char* path)
762 {
763 return entry_directory_new_full (entry_type, path, FALSE, NULL);
764 }
765
entry_directory_new_legacy(DesktopEntryType entry_type,const char * path,const char * legacy_prefix)766 EntryDirectory* entry_directory_new_legacy(DesktopEntryType entry_type, const char* path, const char* legacy_prefix)
767 {
768 return entry_directory_new_full(entry_type, path, TRUE, legacy_prefix);
769 }
770
entry_directory_ref(EntryDirectory * ed)771 EntryDirectory* entry_directory_ref(EntryDirectory* ed)
772 {
773 g_return_val_if_fail(ed != NULL, NULL);
774 g_return_val_if_fail(ed->refcount > 0, NULL);
775
776 ed->refcount++;
777
778 return ed;
779 }
780
entry_directory_unref(EntryDirectory * ed)781 void entry_directory_unref(EntryDirectory* ed)
782 {
783 g_return_if_fail (ed != NULL);
784 g_return_if_fail (ed->refcount > 0);
785
786 if (--ed->refcount == 0)
787 {
788 cached_dir_remove_reference (ed->dir);
789
790 ed->dir = NULL;
791 ed->entry_type = DESKTOP_ENTRY_INVALID;
792 ed->is_legacy = FALSE;
793
794 g_free (ed->legacy_prefix);
795 ed->legacy_prefix = NULL;
796
797 g_free (ed);
798 }
799 }
800
entry_directory_add_monitor(EntryDirectory * ed,EntryDirectoryChangedFunc callback,gpointer user_data)801 static void entry_directory_add_monitor(EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
802 {
803 cached_dir_add_monitor (ed->dir, ed, callback, user_data);
804 }
805
entry_directory_remove_monitor(EntryDirectory * ed,EntryDirectoryChangedFunc callback,gpointer user_data)806 static void entry_directory_remove_monitor(EntryDirectory* ed, EntryDirectoryChangedFunc callback, gpointer user_data)
807 {
808 cached_dir_remove_monitor (ed->dir, ed, callback, user_data);
809 }
810
entry_directory_get_directory(EntryDirectory * ed,const char * relative_path)811 static DesktopEntry* entry_directory_get_directory(EntryDirectory* ed, const char* relative_path)
812 {
813 DesktopEntry *entry;
814
815 if (ed->entry_type != DESKTOP_ENTRY_DIRECTORY)
816 return NULL;
817
818 entry = cached_dir_find_relative_path (ed->dir, relative_path);
819 if (entry == NULL || desktop_entry_get_type (entry) != DESKTOP_ENTRY_DIRECTORY)
820 return NULL;
821
822 return desktop_entry_ref (entry);
823 }
824
get_desktop_file_id_from_path(EntryDirectory * ed,DesktopEntryType entry_type,const char * relative_path)825 static char* get_desktop_file_id_from_path(EntryDirectory* ed, DesktopEntryType entry_type, const char* relative_path)
826 {
827 char *retval;
828
829 retval = NULL;
830
831 if (entry_type == DESKTOP_ENTRY_DESKTOP)
832 {
833 if (!ed->is_legacy)
834 {
835 retval = g_strdelimit (g_strdup (relative_path), "/", '-');
836 }
837 else
838 {
839 char *basename;
840
841 basename = g_path_get_basename (relative_path);
842
843 if (ed->legacy_prefix)
844 {
845 retval = g_strjoin ("-", ed->legacy_prefix, basename, NULL);
846 g_free (basename);
847 }
848 else
849 {
850 retval = basename;
851 }
852 }
853 }
854 else
855 {
856 retval = g_strdup (relative_path);
857 }
858
859 return retval;
860 }
861
862 typedef gboolean (*EntryDirectoryForeachFunc) (EntryDirectory* ed, DesktopEntry* entry, const char* file_id, DesktopEntrySet* set, gpointer user_data);
863
entry_directory_foreach_recursive(EntryDirectory * ed,CachedDir * cd,GString * relative_path,EntryDirectoryForeachFunc func,DesktopEntrySet * set,gpointer user_data)864 static gboolean entry_directory_foreach_recursive(EntryDirectory* ed, CachedDir* cd, GString* relative_path, EntryDirectoryForeachFunc func, DesktopEntrySet* set, gpointer user_data)
865 {
866 GSList *tmp;
867 gsize relative_path_len;
868
869 if (cd->deleted)
870 return TRUE;
871
872 relative_path_len = relative_path->len;
873
874 tmp = cd->entries;
875 while (tmp != NULL)
876 {
877 DesktopEntry *entry = tmp->data;
878
879 if (desktop_entry_get_type (entry) == ed->entry_type)
880 {
881 gboolean ret;
882 char *file_id;
883
884 g_string_append (relative_path,
885 desktop_entry_get_basename (entry));
886
887 file_id = get_desktop_file_id_from_path (ed,
888 ed->entry_type,
889 relative_path->str);
890
891 ret = func (ed, entry, file_id, set, user_data);
892
893 g_free (file_id);
894
895 g_string_truncate (relative_path, relative_path_len);
896
897 if (!ret)
898 return FALSE;
899 }
900
901 tmp = tmp->next;
902 }
903
904 tmp = cd->subdirs;
905 while (tmp != NULL)
906 {
907 CachedDir *subdir = tmp->data;
908
909 g_string_append (relative_path, subdir->name);
910 g_string_append_c (relative_path, G_DIR_SEPARATOR);
911
912 if (!entry_directory_foreach_recursive (ed,
913 subdir,
914 relative_path,
915 func,
916 set,
917 user_data))
918 return FALSE;
919
920 g_string_truncate (relative_path, relative_path_len);
921
922 tmp = tmp->next;
923 }
924
925 return TRUE;
926 }
927
entry_directory_foreach(EntryDirectory * ed,EntryDirectoryForeachFunc func,DesktopEntrySet * set,gpointer user_data)928 static void entry_directory_foreach(EntryDirectory* ed, EntryDirectoryForeachFunc func, DesktopEntrySet* set, gpointer user_data)
929 {
930 GString *path;
931
932 path = g_string_new (NULL);
933
934 entry_directory_foreach_recursive (ed,
935 ed->dir,
936 path,
937 func,
938 set,
939 user_data);
940
941 g_string_free (path, TRUE);
942 }
943
entry_directory_get_flat_contents(EntryDirectory * ed,DesktopEntrySet * desktop_entries,DesktopEntrySet * directory_entries,GSList ** subdirs)944 void entry_directory_get_flat_contents(EntryDirectory* ed, DesktopEntrySet* desktop_entries, DesktopEntrySet* directory_entries, GSList** subdirs)
945 {
946 GSList *tmp;
947
948 if (subdirs)
949 *subdirs = NULL;
950
951 tmp = ed->dir->entries;
952 while (tmp != NULL)
953 {
954 DesktopEntry *entry = tmp->data;
955 const char *basename;
956
957 basename = desktop_entry_get_basename (entry);
958
959 if (desktop_entries &&
960 desktop_entry_get_type (entry) == DESKTOP_ENTRY_DESKTOP)
961 {
962 char *file_id;
963
964 file_id = get_desktop_file_id_from_path (ed,
965 DESKTOP_ENTRY_DESKTOP,
966 basename);
967
968 desktop_entry_set_add_entry (desktop_entries,
969 entry,
970 file_id);
971
972 g_free (file_id);
973 }
974
975 if (directory_entries &&
976 desktop_entry_get_type (entry) == DESKTOP_ENTRY_DIRECTORY)
977 {
978 desktop_entry_set_add_entry (directory_entries,
979 entry,
980 basename);
981 }
982
983 tmp = tmp->next;
984 }
985
986 if (subdirs)
987 {
988 tmp = ed->dir->subdirs;
989 while (tmp != NULL)
990 {
991 CachedDir *cd = tmp->data;
992
993 if (!cd->deleted)
994 {
995 *subdirs = g_slist_prepend (*subdirs, g_strdup (cd->name));
996 }
997
998 tmp = tmp->next;
999 }
1000 }
1001
1002 if (subdirs)
1003 *subdirs = g_slist_reverse (*subdirs);
1004 }
1005
1006 /*
1007 * Entry directory lists
1008 */
1009
entry_directory_list_new(void)1010 EntryDirectoryList* entry_directory_list_new(void)
1011 {
1012 EntryDirectoryList *list;
1013
1014 list = g_new0 (EntryDirectoryList, 1);
1015
1016 list->refcount = 1;
1017 list->dirs = NULL;
1018 list->length = 0;
1019
1020 return list;
1021 }
1022
entry_directory_list_ref(EntryDirectoryList * list)1023 EntryDirectoryList* entry_directory_list_ref(EntryDirectoryList* list)
1024 {
1025 g_return_val_if_fail (list != NULL, NULL);
1026 g_return_val_if_fail (list->refcount > 0, NULL);
1027
1028 list->refcount += 1;
1029
1030 return list;
1031 }
1032
entry_directory_list_unref(EntryDirectoryList * list)1033 void entry_directory_list_unref(EntryDirectoryList* list)
1034 {
1035 g_return_if_fail (list != NULL);
1036 g_return_if_fail (list->refcount > 0);
1037
1038 list->refcount -= 1;
1039 if (list->refcount == 0)
1040 {
1041 g_list_foreach (list->dirs, (GFunc) entry_directory_unref, NULL);
1042 g_list_free (list->dirs);
1043 list->dirs = NULL;
1044 list->length = 0;
1045 g_free (list);
1046 }
1047 }
1048
entry_directory_list_prepend(EntryDirectoryList * list,EntryDirectory * ed)1049 void entry_directory_list_prepend(EntryDirectoryList* list, EntryDirectory* ed)
1050 {
1051 list->length += 1;
1052 list->dirs = g_list_prepend (list->dirs,
1053 entry_directory_ref (ed));
1054 }
1055
entry_directory_list_get_length(EntryDirectoryList * list)1056 int entry_directory_list_get_length(EntryDirectoryList* list)
1057 {
1058 return list->length;
1059 }
1060
entry_directory_list_append_list(EntryDirectoryList * list,EntryDirectoryList * to_append)1061 void entry_directory_list_append_list(EntryDirectoryList* list, EntryDirectoryList* to_append)
1062 {
1063 GList *tmp;
1064 GList *new_dirs = NULL;
1065
1066 if (to_append->length == 0)
1067 return;
1068
1069 tmp = to_append->dirs;
1070 while (tmp != NULL)
1071 {
1072 list->length += 1;
1073 new_dirs = g_list_prepend (new_dirs,
1074 entry_directory_ref (tmp->data));
1075
1076 tmp = tmp->next;
1077 }
1078
1079 new_dirs = g_list_reverse (new_dirs);
1080 list->dirs = g_list_concat (list->dirs, new_dirs);
1081 }
1082
entry_directory_list_get_directory(EntryDirectoryList * list,const char * relative_path)1083 DesktopEntry* entry_directory_list_get_directory(EntryDirectoryList *list, const char* relative_path)
1084 {
1085 DesktopEntry *retval = NULL;
1086 GList *tmp;
1087
1088 tmp = list->dirs;
1089 while (tmp != NULL)
1090 {
1091 if ((retval = entry_directory_get_directory (tmp->data, relative_path)) != NULL)
1092 break;
1093
1094 tmp = tmp->next;
1095 }
1096
1097 return retval;
1098 }
1099
_entry_directory_list_compare(const EntryDirectoryList * a,const EntryDirectoryList * b)1100 gboolean _entry_directory_list_compare(const EntryDirectoryList* a, const EntryDirectoryList* b)
1101 {
1102 GList *al, *bl;
1103
1104 if (a == NULL && b == NULL)
1105 return TRUE;
1106
1107 if ((a == NULL || b == NULL))
1108 return FALSE;
1109
1110 if (a->length != b->length)
1111 return FALSE;
1112
1113 al = a->dirs; bl = b->dirs;
1114 while (al && bl && al->data == bl->data)
1115 {
1116 al = al->next;
1117 bl = bl->next;
1118 }
1119
1120 return (al == NULL && bl == NULL);
1121 }
1122
get_all_func(EntryDirectory * ed,DesktopEntry * entry,const char * file_id,DesktopEntrySet * set,gpointer user_data)1123 static gboolean get_all_func(EntryDirectory* ed, DesktopEntry* entry, const char* file_id, DesktopEntrySet* set, gpointer user_data)
1124 {
1125 if (ed->is_legacy && !desktop_entry_has_categories (entry))
1126 {
1127 entry = desktop_entry_copy (entry);
1128 desktop_entry_add_legacy_category (entry);
1129 }
1130 else
1131 {
1132 entry = desktop_entry_ref (entry);
1133 }
1134
1135 desktop_entry_set_add_entry (set, entry, file_id);
1136 desktop_entry_unref (entry);
1137
1138 return TRUE;
1139 }
1140
1141 static DesktopEntrySet* entry_directory_last_set = NULL;
1142 static EntryDirectoryList* entry_directory_last_list = NULL;
1143
_entry_directory_list_empty_desktop_cache(void)1144 void _entry_directory_list_empty_desktop_cache(void)
1145 {
1146 if (entry_directory_last_set != NULL)
1147 desktop_entry_set_unref (entry_directory_last_set);
1148 entry_directory_last_set = NULL;
1149
1150 if (entry_directory_last_list != NULL)
1151 entry_directory_list_unref (entry_directory_last_list);
1152 entry_directory_last_list = NULL;
1153 }
1154
_entry_directory_list_get_all_desktops(EntryDirectoryList * list)1155 DesktopEntrySet* _entry_directory_list_get_all_desktops(EntryDirectoryList* list)
1156 {
1157 GList *tmp;
1158 DesktopEntrySet *set;
1159
1160 /* The only tricky thing here is that desktop files later
1161 * in the search list with the same relative path
1162 * are "hidden" by desktop files earlier in the path,
1163 * so we have to do the earlier files first causing
1164 * the later files to replace the earlier files
1165 * in the DesktopEntrySet
1166 *
1167 * We go from the end of the list so we can just
1168 * g_hash_table_replace and not have to do two
1169 * hash lookups (check for existing entry, then insert new
1170 * entry)
1171 */
1172
1173 /* This method is -extremely- slow, so we have a simple
1174 one-entry cache here */
1175 if (_entry_directory_list_compare (list, entry_directory_last_list))
1176 {
1177 menu_verbose (" Hit desktop list (%p) cache\n", list);
1178 return desktop_entry_set_ref (entry_directory_last_set);
1179 }
1180
1181 if (entry_directory_last_set != NULL)
1182 desktop_entry_set_unref (entry_directory_last_set);
1183 if (entry_directory_last_list != NULL)
1184 entry_directory_list_unref (entry_directory_last_list);
1185
1186 set = desktop_entry_set_new ();
1187 menu_verbose (" Storing all of list %p in set %p\n",
1188 list, set);
1189
1190 tmp = g_list_last (list->dirs);
1191 while (tmp != NULL)
1192 {
1193 entry_directory_foreach (tmp->data, get_all_func, set, NULL);
1194
1195 tmp = tmp->prev;
1196 }
1197
1198 entry_directory_last_list = entry_directory_list_ref (list);
1199 entry_directory_last_set = desktop_entry_set_ref (set);
1200
1201 return set;
1202 }
1203
entry_directory_list_add_monitors(EntryDirectoryList * list,EntryDirectoryChangedFunc callback,gpointer user_data)1204 void entry_directory_list_add_monitors(EntryDirectoryList* list, EntryDirectoryChangedFunc callback, gpointer user_data)
1205 {
1206 GList *tmp;
1207
1208 tmp = list->dirs;
1209 while (tmp != NULL)
1210 {
1211 entry_directory_add_monitor (tmp->data, callback, user_data);
1212 tmp = tmp->next;
1213 }
1214 }
1215
entry_directory_list_remove_monitors(EntryDirectoryList * list,EntryDirectoryChangedFunc callback,gpointer user_data)1216 void entry_directory_list_remove_monitors(EntryDirectoryList* list, EntryDirectoryChangedFunc callback, gpointer user_data)
1217 {
1218 GList *tmp;
1219
1220 tmp = list->dirs;
1221 while (tmp != NULL)
1222 {
1223 entry_directory_remove_monitor (tmp->data, callback, user_data);
1224 tmp = tmp->next;
1225 }
1226 }
1227