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