1 /*
2  *      menu-cache.c
3  *
4  *      Copyright 2008 PCMan <pcman.tw@gmail.com>
5  *      Copyright 2009 Jürgen Hötzel <juergen@archlinux.org>
6  *      Copyright 2012-2017 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
7  *
8  *      This library is free software; you can redistribute it and/or
9  *      modify it under the terms of the GNU Lesser General Public
10  *      License as published by the Free Software Foundation; either
11  *      version 2.1 of the License, or (at your option) any later version.
12  *
13  *      This library is distributed in the hope that it will be useful,
14  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *      Lesser General Public License for more details.
17  *
18  *      You should have received a copy of the GNU Lesser General Public
19  *      License along with this library; if not, write to the Free Software
20  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include "version.h"
28 
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <time.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #include <sys/fcntl.h>
39 #include <errno.h>
40 #include <sys/wait.h>
41 
42 #include <gio/gio.h>
43 
44 #include "menu-cache.h"
45 
46 #ifdef G_ENABLE_DEBUG
47 #define DEBUG(...)  g_debug(__VA_ARGS__)
48 #else
49 #define DEBUG(...)
50 #endif
51 
52 #if GLIB_CHECK_VERSION(2, 32, 0)
53 static GRecMutex _cache_lock;
54 #  define MENU_CACHE_LOCK       g_rec_mutex_lock(&_cache_lock)
55 #  define MENU_CACHE_UNLOCK     g_rec_mutex_unlock(&_cache_lock)
56 /* for sync lookup */
57 static GMutex sync_run_mutex;
58 static GCond sync_run_cond;
59 #define SET_CACHE_READY(_cache_) do { \
60     g_mutex_lock(&sync_run_mutex); \
61     _cache_->ready = TRUE; \
62     g_cond_broadcast(&sync_run_cond); \
63     g_mutex_unlock(&sync_run_mutex); } while(0)
64 #else
65 /* before 2.32 GLib had another entity for statically allocated mutexes */
66 static GStaticRecMutex _cache_lock = G_STATIC_REC_MUTEX_INIT;
67 #  define MENU_CACHE_LOCK       g_static_rec_mutex_lock(&_cache_lock)
68 #  define MENU_CACHE_UNLOCK     g_static_rec_mutex_unlock(&_cache_lock)
69 /* for sync lookup */
70 static GMutex *sync_run_mutex = NULL;
71 static GCond *sync_run_cond = NULL;
72 #define SET_CACHE_READY(_cache_) do { \
73     g_mutex_lock(sync_run_mutex); \
74     _cache_->ready = TRUE; \
75     if(sync_run_cond) g_cond_broadcast(sync_run_cond); \
76     g_mutex_unlock(sync_run_mutex); } while(0)
77 #endif
78 
79 typedef struct
80 {
81     char *dir;
82     gint n_ref;
83 } MenuCacheFileDir;
84 
85 struct _MenuCacheItem
86 {
87     guint n_ref;
88     MenuCacheType type;
89     char* id;
90     char* name;
91     char* comment;
92     char* icon;
93     MenuCacheFileDir* file_dir;
94     char* file_name;
95     MenuCacheDir* parent;
96 };
97 
98 struct _MenuCacheDir
99 {
100     MenuCacheItem item;
101     GSList* children;
102     guint32 flags;
103 };
104 
105 struct _MenuCacheApp
106 {
107     MenuCacheItem item;
108     char* generic_name;
109     char* exec;
110     char* working_dir;
111     guint32 show_in_flags;
112     guint32 flags;
113     char* try_exec;
114     const char **categories;
115     char* keywords;
116 };
117 
118 struct _MenuCache
119 {
120     guint n_ref;
121     MenuCacheDir* root_dir;
122     char* menu_name;
123     char* reg; /* includes md5 sum */
124     char* md5; /* link inside of reg */
125     char* cache_file;
126     char** known_des;
127     GSList* notifiers;
128     GThread* thr;
129     GCancellable* cancellable;
130     guint version;
131     guint reload_id;
132     gboolean ready : 1; /* used for sync access */
133 };
134 
135 static int server_fd = -1;
136 G_LOCK_DEFINE(connect); /* for server_fd */
137 
138 static GHashTable* hash = NULL;
139 
140 /* Don't call this API directly. Use menu_cache_lookup instead. */
141 static MenuCache* menu_cache_new( const char* cache_file );
142 
143 static gboolean connect_server(GCancellable* cancellable);
144 static gboolean register_menu_to_server(MenuCache* cache);
145 static void unregister_menu_from_server( MenuCache* cache );
146 
147 /* keep them for backward compatibility */
148 #ifdef G_DISABLE_DEPRECATED
149 MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache );
150 MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item );
151 MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path );
152 GSList* menu_cache_dir_get_children( MenuCacheDir* dir );
153 #endif
154 
menu_cache_init(int flags)155 void menu_cache_init(int flags)
156 {
157 #if !GLIB_CHECK_VERSION(2, 36, 0)
158     g_type_init();
159 #endif
160 }
161 
162 static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache,
163                                 MenuCacheFileDir** all_used_files, int n_all_used_files);
164 
165 /* functions read_dir(), read_app(), and read_item() should be called for
166    items that aren't accessible yet, therefore no lock is required */
read_dir(GDataInputStream * f,MenuCacheDir * dir,MenuCache * cache,MenuCacheFileDir ** all_used_files,int n_all_used_files)167 static void read_dir(GDataInputStream* f, MenuCacheDir* dir, MenuCache* cache,
168                      MenuCacheFileDir** all_used_files, int n_all_used_files)
169 {
170     MenuCacheItem* item;
171     char *line;
172     gsize len;
173 
174     /* nodisplay flag */
175     if (cache->version >= 2)
176     {
177         line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
178         if (G_UNLIKELY(line == NULL))
179             return;
180         dir->flags = (guint32)atoi(line);
181         g_free(line);
182     }
183 
184     /* load child items in the dir */
185     while( (item = read_item( f, cache, all_used_files, n_all_used_files )) )
186     {
187         /* menu_cache_ref shouldn't be called here for dir.
188          * Otherwise, circular reference will happen. */
189         item->parent = dir;
190         dir->children = g_slist_prepend( dir->children, item );
191     }
192 
193     dir->children = g_slist_reverse( dir->children );
194 
195     /* set flag by children if working with old cache generator */
196     if (cache->version == 1)
197     {
198         if (dir->children == NULL)
199             dir->flags = FLAG_IS_NODISPLAY;
200         else if ((line = menu_cache_item_get_file_path(MENU_CACHE_ITEM(dir))) != NULL)
201         {
202             GKeyFile *kf = g_key_file_new();
203             if (g_key_file_load_from_file(kf, line, G_KEY_FILE_NONE, NULL) &&
204                 g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP,
205                                        G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL))
206                 dir->flags = FLAG_IS_NODISPLAY;
207             g_key_file_free(kf);
208             g_free(line);
209         }
210     }
211 }
212 
_unescape_lf(char * str)213 static char *_unescape_lf(char *str)
214 {
215     char *c, *p = str;
216     gsize len = 0;
217 
218     while ((c = strchr(p, '\\')) != NULL)
219     {
220         if (p != &str[len])
221             memmove(&str[len], p, c - p);
222         len += (c - p);
223         if (c[1] == 'n')
224         {
225             str[len++] = '\n';
226             c++;
227         }
228         else if (c != &str[len])
229             str[len++] = *c;
230         p = &c[1];
231     }
232     if (p != &str[len])
233         memmove(&str[len], p, strlen(p) + 1);
234     return str;
235 }
236 
read_app(GDataInputStream * f,MenuCacheApp * app,MenuCache * cache)237 static void read_app(GDataInputStream* f, MenuCacheApp* app, MenuCache* cache)
238 {
239     char *line;
240     gsize len;
241     GString *str;
242 
243     /* generic name */
244     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
245     if(G_UNLIKELY(line == NULL))
246         return;
247     if(G_LIKELY(len > 0))
248         app->generic_name = _unescape_lf(line);
249     else
250         g_free(line);
251 
252     /* exec */
253     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
254     if(G_UNLIKELY(line == NULL))
255         return;
256     if(G_LIKELY(len > 0))
257         app->exec = _unescape_lf(line);
258     else
259         g_free(line);
260 
261     /* terminal / startup notify */
262     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
263     if(G_UNLIKELY(line == NULL))
264         return;
265     app->flags = (guint32)atoi(line);
266     g_free(line);
267 
268     /* ShowIn flags */
269     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
270     if(G_UNLIKELY(line == NULL))
271         return;
272     app->show_in_flags = (guint32)atol(line);
273     g_free(line);
274 
275     if (cache->version < 2)
276         return;
277 
278     /* TryExec */
279     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
280     if (G_UNLIKELY(line == NULL))
281         return;
282     if (G_LIKELY(len > 0))
283         app->try_exec = g_strchomp(line);
284     else
285         g_free(line);
286 
287     /* Path */
288     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
289     if (G_UNLIKELY(line == NULL))
290         return;
291     if (G_LIKELY(len > 0))
292         app->working_dir = line;
293     else
294         g_free(line);
295 
296     /* Categories */
297     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
298     if (G_UNLIKELY(line == NULL))
299         return;
300     if (G_LIKELY(len > 0))
301     {
302         const char **x;
303 
304         /* split and intern all the strings so categories can be processed
305            later for search doing g_quark_try_string()+g_quark_to_string() */
306         app->categories = x = (const char **)g_strsplit(line, ";", 0);
307         while (*x != NULL)
308         {
309             char *cat = (char *)*x;
310             *x = g_intern_string(cat);
311             g_free(cat);
312             x++;
313         }
314     }
315     g_free(line);
316 
317     /* Keywords */
318     str = g_string_new(MENU_CACHE_ITEM(app)->name);
319     if (G_LIKELY(app->exec != NULL))
320     {
321         char *sp = strchr(app->exec, ' ');
322         char *bn = strrchr(app->exec, G_DIR_SEPARATOR);
323 
324         g_string_append_c(str, ',');
325         if (bn == NULL && sp == NULL)
326             g_string_append(str, app->exec);
327         else if (bn == NULL || (sp != NULL && sp < bn))
328             g_string_append_len(str, app->exec, sp - app->exec);
329         else if (sp == NULL)
330             g_string_append(str, &bn[1]);
331         else
332             g_string_append_len(str, &bn[1], sp - &bn[1]);
333     }
334     if (app->generic_name != NULL)
335     {
336         g_string_append_c(str, ',');
337         g_string_append(str, app->generic_name);
338     }
339     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
340     if (G_UNLIKELY(line == NULL))
341         return;
342     if (len > 0)
343     {
344         g_string_append_c(str, ',');
345         g_string_append(str, line);
346     }
347     app->keywords = g_utf8_casefold(str->str, str->len);
348     g_string_free(str, TRUE);
349     g_free(line);
350 }
351 
read_item(GDataInputStream * f,MenuCache * cache,MenuCacheFileDir ** all_used_files,int n_all_used_files)352 static MenuCacheItem* read_item(GDataInputStream* f, MenuCache* cache,
353                                 MenuCacheFileDir** all_used_files, int n_all_used_files)
354 {
355     MenuCacheItem* item;
356     char *line;
357     gsize len;
358     gint idx;
359 
360     /* desktop/menu id */
361     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
362     if(G_UNLIKELY(line == NULL))
363         return NULL;
364 
365     if( G_LIKELY(len >= 1) )
366     {
367         if( line[0] == '+' ) /* menu dir */
368         {
369             item = (MenuCacheItem*)g_slice_new0( MenuCacheDir );
370             item->n_ref = 1;
371             item->type = MENU_CACHE_TYPE_DIR;
372         }
373         else if( line[0] == '-' ) /* menu item */
374         {
375             item = (MenuCacheItem*)g_slice_new0( MenuCacheApp );
376             item->n_ref = 1;
377             if( G_LIKELY( len > 1 ) ) /* application item */
378                 item->type = MENU_CACHE_TYPE_APP;
379             else /* separator */
380             {
381                 item->type = MENU_CACHE_TYPE_SEP;
382                 return item;
383             }
384         }
385         else
386             return NULL;
387 
388         item->id = g_strndup( line + 1, len - 1 );
389         g_free(line);
390     }
391     else
392     {
393         g_free(line);
394         return NULL;
395     }
396 
397     /* name */
398     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
399     if(G_UNLIKELY(line == NULL))
400         goto _fail;
401     if(G_LIKELY(len > 0))
402         item->name = _unescape_lf(line);
403     else
404         g_free(line);
405 
406     /* comment */
407     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
408     if(G_UNLIKELY(line == NULL))
409         goto _fail;
410     if(G_LIKELY(len > 0))
411         item->comment = _unescape_lf(line);
412     else
413         g_free(line);
414 
415     /* icon */
416     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
417     if(G_UNLIKELY(line == NULL))
418         goto _fail;
419     if(G_LIKELY(len > 0))
420         item->icon = line;
421     else
422         g_free(line);
423 
424     /* file dir/basename */
425 
426     /* file name */
427     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
428     if(G_UNLIKELY(line == NULL))
429         goto _fail;
430     if(G_LIKELY(len > 0))
431         item->file_name = line;
432     else if( item->type == MENU_CACHE_TYPE_APP )
433     {
434         /* When file name is the same as desktop_id, which is
435          * quite common in desktop files, we use this trick to
436          * save memory usage. */
437         item->file_name = item->id;
438         g_free(line);
439     }
440     else
441         g_free(line);
442 
443     /* desktop file dir */
444     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
445     if(G_UNLIKELY(line == NULL))
446     {
447 _fail:
448         g_free(item->id);
449         g_free(item->name);
450         g_free(item->comment);
451         g_free(item->icon);
452         if(item->file_name && item->file_name != item->id)
453             g_free(item->file_name);
454         if(item->type == MENU_CACHE_TYPE_DIR)
455             g_slice_free(MenuCacheDir, MENU_CACHE_DIR(item));
456         else
457             g_slice_free(MenuCacheApp, MENU_CACHE_APP(item));
458         return NULL;
459     }
460     idx = atoi( line );
461     g_free(line);
462     if( G_LIKELY( idx >=0 && idx < n_all_used_files ) )
463     {
464         item->file_dir = all_used_files[ idx ];
465         g_atomic_int_inc(&item->file_dir->n_ref);
466     }
467 
468     if( item->type == MENU_CACHE_TYPE_DIR )
469         read_dir( f, MENU_CACHE_DIR(item), cache, all_used_files, n_all_used_files );
470     else if( item->type == MENU_CACHE_TYPE_APP )
471         read_app( f, MENU_CACHE_APP(item), cache );
472 
473     return item;
474 }
475 
menu_cache_file_dir_unref(MenuCacheFileDir * file_dir)476 static void menu_cache_file_dir_unref(MenuCacheFileDir *file_dir)
477 {
478     if (file_dir && g_atomic_int_dec_and_test(&file_dir->n_ref))
479     {
480         g_free(file_dir->dir);
481         g_free(file_dir);
482     }
483 }
484 
read_all_used_files(GDataInputStream * f,MenuCache * cache,MenuCacheFileDir *** all_used_files)485 static gint read_all_used_files(GDataInputStream* f, MenuCache* cache,
486                                 MenuCacheFileDir*** all_used_files)
487 {
488     char *line;
489     gsize len;
490     int i, n;
491     MenuCacheFileDir** dirs;
492 
493     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
494     if(G_UNLIKELY(line == NULL))
495         return -1;
496 
497     n = atoi( line );
498     g_free(line);
499     if (G_UNLIKELY(n <= 0))
500         return n;
501 
502     dirs = g_new0( MenuCacheFileDir *, n );
503 
504     for( i = 0; i < n; ++i )
505     {
506         line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
507         if(G_UNLIKELY(line == NULL))
508         {
509             while (i-- > 0)
510                 menu_cache_file_dir_unref(dirs[i]);
511             g_free(dirs);
512             return -1;
513         }
514         dirs[i] = g_new(MenuCacheFileDir, 1);
515         dirs[i]->n_ref = 1;
516         dirs[i]->dir = line; /* don't include \n */
517     }
518     *all_used_files = dirs;
519     return n;
520 }
521 
read_all_known_des(GDataInputStream * f,MenuCache * cache)522 static gboolean read_all_known_des(GDataInputStream* f, MenuCache* cache)
523 {
524     char *line;
525     gsize len;
526     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
527     if(G_UNLIKELY(line == NULL))
528         return FALSE;
529     cache->known_des = g_strsplit_set( line, ";\n", 0 );
530     g_free(line);
531     return TRUE;
532 }
533 
menu_cache_new(const char * cache_file)534 static MenuCache* menu_cache_new( const char* cache_file )
535 {
536     MenuCache* cache;
537     cache = g_slice_new0( MenuCache );
538     cache->cache_file = g_strdup( cache_file );
539     cache->n_ref = 1;
540     return cache;
541 }
542 
543 /**
544  * menu_cache_ref
545  * @cache: a menu cache descriptor
546  *
547  * Increases reference counter on @cache.
548  *
549  * Returns: @cache.
550  *
551  * Since: 0.1.0
552  */
menu_cache_ref(MenuCache * cache)553 MenuCache* menu_cache_ref(MenuCache* cache)
554 {
555     g_atomic_int_inc( &cache->n_ref );
556     return cache;
557 }
558 
559 /**
560  * menu_cache_unref
561  * @cache: a menu cache descriptor
562  *
563  * Descreases reference counter on @cache. When reference count becomes 0
564  * then resources associated with @cache will be freed.
565  *
566  * Since: 0.1.0
567  */
menu_cache_unref(MenuCache * cache)568 void menu_cache_unref(MenuCache* cache)
569 {
570     /* DEBUG("cache_unref: %d", cache->n_ref); */
571     /* we need a lock here unfortunately because item in hash isn't protected
572        by reference therefore another thread may get access to it right now */
573     MENU_CACHE_LOCK;
574     if( g_atomic_int_dec_and_test(&cache->n_ref) )
575     {
576         /* g_assert(cache->reload_id != 0); */
577         unregister_menu_from_server( cache );
578         /* DEBUG("unregister to server"); */
579         g_hash_table_remove( hash, cache->menu_name );
580         if( g_hash_table_size(hash) == 0 )
581         {
582             /* DEBUG("destroy hash"); */
583             g_hash_table_destroy(hash);
584 
585             /* DEBUG("disconnect from server"); */
586             G_LOCK(connect);
587             shutdown(server_fd, SHUT_RDWR); /* the IO thread will terminate itself */
588             server_fd = -1;
589             G_UNLOCK(connect);
590             hash = NULL;
591         }
592         MENU_CACHE_UNLOCK;
593 
594         if(G_LIKELY(cache->thr))
595         {
596             g_cancellable_cancel(cache->cancellable);
597             g_thread_join(cache->thr);
598         }
599         g_object_unref(cache->cancellable);
600         if( G_LIKELY(cache->root_dir) )
601         {
602             /* DEBUG("unref root dir"); */
603             menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) );
604             /* DEBUG("unref root dir finished"); */
605         }
606         g_free( cache->cache_file );
607         g_free( cache->menu_name );
608         g_free(cache->reg);
609         /* g_free( cache->menu_file_path ); */
610         g_strfreev(cache->known_des);
611         g_slist_free(cache->notifiers);
612         g_slice_free( MenuCache, cache );
613     }
614     else
615         MENU_CACHE_UNLOCK;
616 }
617 
618 /**
619  * menu_cache_get_root_dir
620  * @cache: a menu cache instance
621  *
622  * Since: 0.1.0
623  *
624  * Deprecated: 0.3.4: Use menu_cache_dup_root_dir() instead.
625  */
menu_cache_get_root_dir(MenuCache * cache)626 MenuCacheDir* menu_cache_get_root_dir( MenuCache* cache )
627 {
628     MenuCacheDir* dir = menu_cache_dup_root_dir(cache);
629     /* NOTE: this is very ugly hack but cache->root_dir may be changed by
630        cache reload in server-io thread, so we should keep it alive :( */
631     if(dir)
632         g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir);
633     return dir;
634 }
635 
636 /**
637  * menu_cache_dup_root_dir
638  * @cache: a menu cache instance
639  *
640  * Retrieves root directory for @cache. Returned data should be freed
641  * with menu_cache_item_unref() after usage.
642  *
643  * Returns: (transfer full): root item or %NULL in case of error.
644  *
645  * Since: 0.3.4
646  */
menu_cache_dup_root_dir(MenuCache * cache)647 MenuCacheDir* menu_cache_dup_root_dir( MenuCache* cache )
648 {
649     MenuCacheDir* dir;
650     MENU_CACHE_LOCK;
651     dir = cache->root_dir;
652     if(G_LIKELY(dir))
653         menu_cache_item_ref(MENU_CACHE_ITEM(dir));
654     MENU_CACHE_UNLOCK;
655     return dir;
656 }
657 
658 /**
659  * menu_cache_item_ref
660  * @item: a menu cache item
661  *
662  * Increases reference counter on @item.
663  *
664  * Returns: @item.
665  *
666  * Since: 0.1.0
667  */
menu_cache_item_ref(MenuCacheItem * item)668 MenuCacheItem* menu_cache_item_ref(MenuCacheItem* item)
669 {
670     g_atomic_int_inc( &item->n_ref );
671     /* DEBUG("item_ref %s: %d -> %d", item->id, item->n_ref-1, item->n_ref); */
672     return item;
673 }
674 
menu_cache_reload_idle(gpointer cache)675 static gboolean menu_cache_reload_idle(gpointer cache)
676 {
677     /* do reload once */
678     if (!g_source_is_destroyed(g_main_current_source()))
679         menu_cache_reload(cache);
680     return FALSE;
681 }
682 
683 typedef struct _CacheReloadNotifier
684 {
685     MenuCacheReloadNotify func;
686     gpointer user_data;
687 }CacheReloadNotifier;
688 
689 struct _MenuCacheNotifyId
690 {
691     GSList l;
692 };
693 
694 /**
695  * menu_cache_add_reload_notify
696  * @cache: a menu cache instance
697  * @func: callback to call when menu cache is reloaded
698  * @user_data: user data provided for @func
699  *
700  * Adds a @func to list of callbacks that are called each time menu cache
701  * is loaded.
702  *
703  * Returns: an ID of added callback.
704  *
705  * Since: 0.1.0
706  */
menu_cache_add_reload_notify(MenuCache * cache,MenuCacheReloadNotify func,gpointer user_data)707 MenuCacheNotifyId menu_cache_add_reload_notify(MenuCache* cache, MenuCacheReloadNotify func, gpointer user_data)
708 {
709     GSList* l = g_slist_alloc();
710     CacheReloadNotifier* n = g_slice_new(CacheReloadNotifier);
711     gboolean is_first;
712     n->func = func;
713     n->user_data = user_data;
714     l->data = n;
715     MENU_CACHE_LOCK;
716     is_first = (cache->root_dir == NULL && cache->notifiers == NULL);
717     cache->notifiers = g_slist_concat( cache->notifiers, l );
718     /* reload existing file first so it will be ready right away */
719     if(is_first && cache->reload_id == 0)
720         cache->reload_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE,
721                                            menu_cache_reload_idle,
722                                            menu_cache_ref(cache),
723                                            (GDestroyNotify)menu_cache_unref);
724     MENU_CACHE_UNLOCK;
725     return (MenuCacheNotifyId)l;
726 }
727 
728 /**
729  * menu_cache_remove_reload_notify
730  * @cache: a menu cache instance
731  * @notify_id: an ID of callback
732  *
733  * Removes @notify_id from list of callbacks added for @cache by previous
734  * call to menu_cache_add_reload_notify().
735  *
736  * Since: 0.1.0
737  */
menu_cache_remove_reload_notify(MenuCache * cache,MenuCacheNotifyId notify_id)738 void menu_cache_remove_reload_notify(MenuCache* cache, MenuCacheNotifyId notify_id)
739 {
740     MENU_CACHE_LOCK;
741     g_slice_free( CacheReloadNotifier, ((GSList*)notify_id)->data );
742     cache->notifiers = g_slist_delete_link( cache->notifiers, (GSList*)notify_id );
743     MENU_CACHE_UNLOCK;
744 }
745 
reload_notify(gpointer data)746 static gboolean reload_notify(gpointer data)
747 {
748     MenuCache* cache = (MenuCache*)data;
749     GSList* l;
750     MENU_CACHE_LOCK;
751     /* we have it referenced and there is no source removal so no check */
752     for( l = cache->notifiers; l; l = l->next )
753     {
754         CacheReloadNotifier* n = (CacheReloadNotifier*)l->data;
755         if(n->func)
756             n->func( cache, n->user_data );
757     }
758     MENU_CACHE_UNLOCK;
759     return FALSE;
760 }
761 
762 /**
763  * menu_cache_reload
764  * @cache: a menu cache instance
765  *
766  * Reloads menu cache from file generated by menu-cached.
767  *
768  * Returns: %TRUE if reload was successful.
769  *
770  * Since: 0.1.0
771  */
menu_cache_reload(MenuCache * cache)772 gboolean menu_cache_reload( MenuCache* cache )
773 {
774     char* line;
775     gsize len;
776     GFile* file;
777     GFileInputStream* istr = NULL;
778     GDataInputStream* f;
779     MenuCacheFileDir** all_used_files;
780     int i, n;
781     int ver_maj, ver_min;
782 
783     MENU_CACHE_LOCK;
784     if (cache->reload_id)
785         g_source_remove(cache->reload_id);
786     cache->reload_id = 0;
787     MENU_CACHE_UNLOCK;
788     file = g_file_new_for_path(cache->cache_file);
789     if(!file)
790         return FALSE;
791     istr = g_file_read(file, cache->cancellable, NULL);
792     g_object_unref(file);
793     if(!istr)
794         return FALSE;
795     f = g_data_input_stream_new(G_INPUT_STREAM(istr));
796     g_object_unref(istr);
797     if( ! f )
798         return FALSE;
799 
800     /* the first line is version number */
801     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
802     if(G_LIKELY(line))
803     {
804         len = sscanf(line, "%d.%d", &ver_maj, &ver_min);
805         g_free(line);
806         if(len < 2)
807             goto _fail;
808         if( ver_maj != VER_MAJOR ||
809             ver_min > VER_MINOR || ver_min < VER_MINOR_SUPPORTED )
810             goto _fail;
811     }
812     else
813         goto _fail;
814 
815     g_debug("menu cache: got file version 1.%d", ver_min);
816     /* the second line is menu name */
817     line = g_data_input_stream_read_line(f, &len, cache->cancellable, NULL);
818     if(G_UNLIKELY(line == NULL))
819         goto _fail;
820     g_free(line);
821 
822     /* FIXME: this may lock other threads for some time */
823     MENU_CACHE_LOCK;
824     if(cache->notifiers == NULL)
825     {
826         /* nobody aware of reloads, stupid clients may think root is forever */
827         MENU_CACHE_UNLOCK;
828         goto _fail;
829     }
830 
831     /* get all used files */
832     n = read_all_used_files( f, cache, &all_used_files );
833     if (n <= 0)
834     {
835         MENU_CACHE_UNLOCK;
836         goto _fail;
837     }
838 
839     /* read known DEs */
840     g_strfreev( cache->known_des );
841     if( ! read_all_known_des( f, cache ) )
842     {
843         cache->known_des = NULL;
844         MENU_CACHE_UNLOCK;
845         for (i = 0; i < n; i++)
846             menu_cache_file_dir_unref(all_used_files[i]);
847         g_free(all_used_files);
848 _fail:
849         g_object_unref(f);
850         return FALSE;
851     }
852     cache->version = ver_min;
853 
854     if(cache->root_dir)
855         menu_cache_item_unref( MENU_CACHE_ITEM(cache->root_dir) );
856 
857     cache->root_dir = (MenuCacheDir*)read_item( f, cache, all_used_files, n );
858     g_object_unref(f);
859 
860     g_idle_add_full(G_PRIORITY_HIGH_IDLE, reload_notify, menu_cache_ref(cache),
861                     (GDestroyNotify)menu_cache_unref);
862     MENU_CACHE_UNLOCK;
863 
864     for (i = 0; i < n; i++)
865         menu_cache_file_dir_unref(all_used_files[i]);
866     g_free(all_used_files);
867 
868     return TRUE;
869 }
870 
871 /**
872  * menu_cache_item_unref
873  * @item: a menu cache item
874  *
875  * Decreases reference counter on @item. When reference count becomes 0
876  * then resources associated with @item will be freed.
877  *
878  * Returns: %FALSE (since 0.5.0)
879  *
880  * Since: 0.1.0
881  */
menu_cache_item_unref(MenuCacheItem * item)882 gboolean menu_cache_item_unref(MenuCacheItem* item)
883 {
884     /* DEBUG("item_unref(%s): %d", item->id, item->n_ref); */
885     /* We need a lock here unfortunately since another thread may have access
886        to it via some child->parent which isn't protected by reference */
887     MENU_CACHE_LOCK; /* lock may be recursive here */
888     if( g_atomic_int_dec_and_test( &item->n_ref ) )
889     {
890         /* DEBUG("free item: %s", item->id); */
891         g_free( item->id );
892         g_free( item->name );
893         g_free( item->comment );
894         g_free( item->icon );
895 
896         menu_cache_file_dir_unref(item->file_dir);
897 
898         if( item->file_name && item->file_name != item->id )
899             g_free( item->file_name );
900 
901         if( item->parent )
902         {
903             /* DEBUG("remove %s from parent %s", item->id, MENU_CACHE_ITEM(item->parent)->id); */
904             /* remove ourselve from the parent node. */
905             item->parent->children = g_slist_remove(item->parent->children, item);
906         }
907 
908         if( item->type == MENU_CACHE_TYPE_DIR )
909         {
910             MenuCacheDir* dir = MENU_CACHE_DIR(item);
911             GSList* l;
912             for(l = dir->children; l; )
913             {
914                 MenuCacheItem* child = MENU_CACHE_ITEM(l->data);
915                 /* remove ourselve from the children. */
916                 child->parent = NULL;
917                 l = l->next;
918                 menu_cache_item_unref(child);
919             }
920             g_slist_free( dir->children );
921             g_slice_free( MenuCacheDir, dir );
922         }
923         else
924         {
925             MenuCacheApp* app = MENU_CACHE_APP(item);
926             g_free( app->exec );
927             g_free(app->try_exec);
928             g_free(app->working_dir);
929             g_free(app->categories);
930             g_free(app->keywords);
931             g_slice_free( MenuCacheApp, app );
932         }
933     }
934     MENU_CACHE_UNLOCK;
935     return FALSE;
936 }
937 
938 /**
939  * menu_cache_item_get_type
940  * @item: a menu cache item
941  *
942  * Checks type of @item.
943  *
944  * Returns: type of @item.
945  *
946  * Since: 0.1.0
947  */
menu_cache_item_get_type(MenuCacheItem * item)948 MenuCacheType menu_cache_item_get_type( MenuCacheItem* item )
949 {
950     return item->type;
951 }
952 
953 /**
954  * menu_cache_item_get_id
955  * @item: a menu cache item
956  *
957  * Retrieves ID (short name such as 'application.desktop') of @item.
958  * Returned data are owned by menu cache and should be not freed by caller.
959  *
960  * Returns: (transfer none): item ID.
961  *
962  * Since: 0.1.0
963  */
menu_cache_item_get_id(MenuCacheItem * item)964 const char* menu_cache_item_get_id( MenuCacheItem* item )
965 {
966     return item->id;
967 }
968 
969 /**
970  * menu_cache_item_get_name
971  * @item: a menu cache item
972  *
973  * Retrieves display name of @item. Returned data are owned by menu
974  * cache and should be not freed by caller.
975  *
976  * Returns: (transfer none): @item display name or %NULL.
977  *
978  * Since: 0.1.0
979  */
menu_cache_item_get_name(MenuCacheItem * item)980 const char* menu_cache_item_get_name( MenuCacheItem* item )
981 {
982     return item->name;
983 }
984 
985 /**
986  * menu_cache_item_get_comment
987  * @item: a menu cache item
988  *
989  * Retrieves comment of @item. The comment can be used to show tooltip
990  * on @item. Returned data are owned by menu cache and should be not
991  * freed by caller.
992  *
993  * Returns: (transfer none): @item comment or %NULL.
994  *
995  * Since: 0.1.0
996  */
menu_cache_item_get_comment(MenuCacheItem * item)997 const char* menu_cache_item_get_comment( MenuCacheItem* item )
998 {
999     return item->comment;
1000 }
1001 
1002 /**
1003  * menu_cache_item_get_icon
1004  * @item: a menu cache item
1005  *
1006  * Retrieves name of icon of @item. Returned data are owned by menu
1007  * cache and should be not freed by caller.
1008  *
1009  * Returns: (transfer none): @item icon name or %NULL.
1010  *
1011  * Since: 0.1.0
1012  */
menu_cache_item_get_icon(MenuCacheItem * item)1013 const char* menu_cache_item_get_icon( MenuCacheItem* item )
1014 {
1015     return item->icon;
1016 }
1017 
1018 /**
1019  * menu_cache_item_get_file_basename
1020  * @item: a menu cache item
1021  *
1022  * Retrieves basename of @item. This API can return %NULL if @item is a
1023  * directory and have no directory desktop entry file. Returned data are
1024  * owned by menu cache and should be not freed by caller.
1025  *
1026  * Returns: (transfer none): @item file basename or %NULL.
1027  *
1028  * Since: 0.2.0
1029  */
menu_cache_item_get_file_basename(MenuCacheItem * item)1030 const char* menu_cache_item_get_file_basename( MenuCacheItem* item )
1031 {
1032     return item->file_name;
1033 }
1034 
1035 /**
1036  * menu_cache_item_get_file_dirname
1037  * @item: a menu cache item
1038  *
1039  * Retrieves path to directory where @item desktop enrty file is located.
1040  * This API can return %NULL if @item is a directory and have no
1041  * desktop entry file. Returned data are owned by menu cache and should
1042  * be not freed by caller.
1043  *
1044  * Returns: (transfer none): @item file parent directory path or %NULL.
1045  *
1046  * Since: 0.2.0
1047  */
menu_cache_item_get_file_dirname(MenuCacheItem * item)1048 const char* menu_cache_item_get_file_dirname( MenuCacheItem* item )
1049 {
1050     return item->file_dir ? item->file_dir->dir + 1 : NULL;
1051 }
1052 
1053 /**
1054  * menu_cache_item_get_file_path
1055  * @item: a menu cache item
1056  *
1057  * Retrieves path to @item desktop enrty file. This API can return %NULL
1058  * if @item is a directory and have no desktop entry file. Returned data
1059  * should be freed with g_free() after usage.
1060  *
1061  * Returns: (transfer full): @item file path or %NULL.
1062  *
1063  * Since: 0.2.0
1064  */
menu_cache_item_get_file_path(MenuCacheItem * item)1065 char* menu_cache_item_get_file_path( MenuCacheItem* item )
1066 {
1067     if( ! item->file_name || ! item->file_dir )
1068         return NULL;
1069     return g_build_filename( item->file_dir->dir + 1, item->file_name, NULL );
1070 }
1071 
1072 /**
1073  * menu_cache_item_get_parent
1074  * @item: a menu cache item
1075  *
1076  * Since: 0.1.0
1077  *
1078  * Deprecated: 0.3.4: Use menu_cache_item_dup_parent() instead.
1079  */
menu_cache_item_get_parent(MenuCacheItem * item)1080 MenuCacheDir* menu_cache_item_get_parent( MenuCacheItem* item )
1081 {
1082     MenuCacheDir* dir = menu_cache_item_dup_parent(item);
1083     /* NOTE: this is very ugly hack but parent may be changed by item freeing
1084        so we should keep it alive :( */
1085     if(dir)
1086         g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref, dir);
1087     return dir;
1088 }
1089 
1090 /**
1091  * menu_cache_item_dup_parent
1092  * @item: a menu item
1093  *
1094  * Retrieves parent (directory) for @item. Returned data should be freed
1095  * with menu_cache_item_unref() after usage.
1096  *
1097  * Returns: (transfer full): parent item or %NULL in case of error.
1098  *
1099  * Since: 0.3.4
1100  */
menu_cache_item_dup_parent(MenuCacheItem * item)1101 MenuCacheDir* menu_cache_item_dup_parent( MenuCacheItem* item )
1102 {
1103     MenuCacheDir* dir;
1104     MENU_CACHE_LOCK;
1105     dir = item->parent;
1106     if(G_LIKELY(dir))
1107         menu_cache_item_ref(MENU_CACHE_ITEM(dir));
1108     MENU_CACHE_UNLOCK;
1109     return dir;
1110 }
1111 
1112 /**
1113  * menu_cache_dir_get_children
1114  * @dir: a menu cache item
1115  *
1116  * Retrieves list of items contained in @dir. Returned data are owned by
1117  * menu cache and should be not freed by caller.
1118  * This API is thread unsafe and should be never called from outside of
1119  * default main loop.
1120  *
1121  * Returns: (transfer none) (element-type MenuCacheItem): list of items.
1122  *
1123  * Since: 0.1.0
1124  *
1125  * Deprecated: 0.4.0: Use menu_cache_dir_list_children() instead.
1126  */
menu_cache_dir_get_children(MenuCacheDir * dir)1127 GSList* menu_cache_dir_get_children( MenuCacheDir* dir )
1128 {
1129     /* NOTE: this is very ugly hack but dir may be freed by cache reload
1130        in server-io thread, so we should keep it alive :( */
1131     g_timeout_add_seconds(10, (GSourceFunc)menu_cache_item_unref,
1132                           menu_cache_item_ref(MENU_CACHE_ITEM(dir)));
1133     return dir->children;
1134 }
1135 
1136 /**
1137  * menu_cache_dir_list_children
1138  * @dir: a menu cache item
1139  *
1140  * Retrieves list of items contained in @dir. Returned data should be
1141  * freed with g_slist_free_full(list, menu_cache_item_unref) after usage.
1142  *
1143  * Returns: (transfer full) (element-type MenuCacheItem): list of items.
1144  *
1145  * Since: 0.4.0
1146  */
menu_cache_dir_list_children(MenuCacheDir * dir)1147 GSList* menu_cache_dir_list_children(MenuCacheDir* dir)
1148 {
1149     GSList *children, *l;
1150 
1151     if(MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR)
1152         return NULL;
1153     MENU_CACHE_LOCK;
1154     children = g_slist_copy(dir->children);
1155     for(l = children; l; l = l->next)
1156         menu_cache_item_ref(l->data);
1157     MENU_CACHE_UNLOCK;
1158     return children;
1159 }
1160 
1161 /**
1162  * menu_cache_find_child_by_id
1163  * @dir: a menu cache item
1164  * @id: a string to find
1165  *
1166  * Checks if @dir has a child with given @id. Returned data should be
1167  * freed with menu_cache_item_unref() when no longer needed.
1168  *
1169  * Returns: (transfer full): found item or %NULL.
1170  *
1171  * Since: 0.5.0
1172  */
menu_cache_find_child_by_id(MenuCacheDir * dir,const char * id)1173 MenuCacheItem *menu_cache_find_child_by_id(MenuCacheDir *dir, const char *id)
1174 {
1175     GSList *child;
1176     MenuCacheItem *item = NULL;
1177 
1178     if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || id == NULL)
1179         return NULL;
1180     MENU_CACHE_LOCK;
1181     for (child = dir->children; child; child = child->next)
1182         if (g_strcmp0(MENU_CACHE_ITEM(child->data)->id, id) == 0)
1183         {
1184             item = menu_cache_item_ref(child->data);
1185             break;
1186         }
1187     MENU_CACHE_UNLOCK;
1188     return item;
1189 }
1190 
1191 /**
1192  * menu_cache_find_child_by_name
1193  * @dir: a menu cache item
1194  * @name: a string to find
1195  *
1196  * Checks if @dir has a child with given @name. Returned data should be
1197  * freed with menu_cache_item_unref() when no longer needed.
1198  *
1199  * Returns: (transfer full): found item or %NULL.
1200  *
1201  * Since: 0.5.0
1202  */
menu_cache_find_child_by_name(MenuCacheDir * dir,const char * name)1203 MenuCacheItem *menu_cache_find_child_by_name(MenuCacheDir *dir, const char *name)
1204 {
1205     GSList *child;
1206     MenuCacheItem *item = NULL;
1207 
1208     if (MENU_CACHE_ITEM(dir)->type != MENU_CACHE_TYPE_DIR || name == NULL)
1209         return NULL;
1210     MENU_CACHE_LOCK;
1211     for (child = dir->children; child; child = child->next)
1212         if (g_strcmp0(MENU_CACHE_ITEM(child->data)->name, name) == 0)
1213         {
1214             item = menu_cache_item_ref(child->data);
1215             break;
1216         }
1217     MENU_CACHE_UNLOCK;
1218     return item;
1219 }
1220 
1221 /**
1222  * menu_cache_dir_is_visible
1223  * @dir: a menu cache item
1224  *
1225  * Checks if @dir should be visible.
1226  *
1227  * Returns: %TRUE if @dir is visible.
1228  *
1229  * Since: 0.5.0
1230  */
menu_cache_dir_is_visible(MenuCacheDir * dir)1231 gboolean menu_cache_dir_is_visible(MenuCacheDir *dir)
1232 {
1233     return ((dir->flags & FLAG_IS_NODISPLAY) == 0);
1234 }
1235 
1236 /**
1237  * menu_cache_app_get_generic_name
1238  * @app: a menu cache item
1239  *
1240  * Retrieves generic name for @app. Returned data are owned by menu
1241  * cache and should not be freed by caller.
1242  *
1243  * Returns: (transfer none): app's generic name or %NULL.
1244  *
1245  * Since: 1.0.3
1246  */
menu_cache_app_get_generic_name(MenuCacheApp * app)1247 const char* menu_cache_app_get_generic_name( MenuCacheApp* app )
1248 {
1249 	return app->generic_name;
1250 }
1251 
1252 /**
1253  * menu_cache_app_get_exec
1254  * @app: a menu cache item
1255  *
1256  * Retrieves execution string for @app. Returned data are owned by menu
1257  * cache and should be not freed by caller.
1258  *
1259  * Returns: (transfer none): item execution string or %NULL.
1260  *
1261  * Since: 0.1.0
1262  */
menu_cache_app_get_exec(MenuCacheApp * app)1263 const char* menu_cache_app_get_exec( MenuCacheApp* app )
1264 {
1265     return app->exec;
1266 }
1267 
1268 /**
1269  * menu_cache_app_get_working_dir
1270  * @app: a menu cache item
1271  *
1272  * Retrieves working directory for @app. Returned data are owned by menu
1273  * cache and should be not freed by caller.
1274  *
1275  * Returns: (transfer none): item working directory or %NULL.
1276  *
1277  * Since: 0.1.0
1278  */
menu_cache_app_get_working_dir(MenuCacheApp * app)1279 const char* menu_cache_app_get_working_dir( MenuCacheApp* app )
1280 {
1281     return app->working_dir;
1282 }
1283 
1284 /**
1285  * menu_cache_app_get_categories
1286  * @app: a menu cache item
1287  *
1288  * Retrieves list of categories for @app. Returned data are owned by menu
1289  * cache and should be not freed by caller.
1290  *
1291  * Returns: (transfer none): list of categories or %NULL.
1292  *
1293  * Since: 1.0.0
1294  */
menu_cache_app_get_categories(MenuCacheApp * app)1295 const char * const * menu_cache_app_get_categories(MenuCacheApp* app)
1296 {
1297     return app->categories;
1298 }
1299 
1300 /**
1301  * menu_cache_app_get_use_terminal
1302  * @app: a menu cache item
1303  *
1304  * Checks if @app should be ran in terminal.
1305  *
1306  * Returns: %TRUE if @app requires terminal to run.
1307  *
1308  * Since: 0.1.0
1309  */
menu_cache_app_get_use_terminal(MenuCacheApp * app)1310 gboolean menu_cache_app_get_use_terminal( MenuCacheApp* app )
1311 {
1312     return ( (app->flags & FLAG_USE_TERMINAL) != 0 );
1313 }
1314 
1315 /**
1316  * menu_cache_app_get_use_sn
1317  * @app: a menu cache item
1318  *
1319  * Checks if @app wants startup notification.
1320  *
1321  * Returns: %TRUE if @app wants startup notification.
1322  *
1323  * Since: 0.1.0
1324  */
menu_cache_app_get_use_sn(MenuCacheApp * app)1325 gboolean menu_cache_app_get_use_sn( MenuCacheApp* app )
1326 {
1327     return ( (app->flags & FLAG_USE_SN) != 0 );
1328 }
1329 
1330 /**
1331  * menu_cache_app_get_show_flags
1332  * @app: a menu cache item
1333  *
1334  * Retrieves list of desktop environments where @app should be visible.
1335  *
1336  * Returns: bit mask of DE.
1337  *
1338  * Since: 0.2.0
1339  */
menu_cache_app_get_show_flags(MenuCacheApp * app)1340 guint32 menu_cache_app_get_show_flags( MenuCacheApp* app )
1341 {
1342     return app->show_in_flags;
1343 }
1344 
_can_be_exec(MenuCacheApp * app)1345 static gboolean _can_be_exec(MenuCacheApp *app)
1346 {
1347     char *path;
1348 
1349     if (app->try_exec == NULL)
1350         return TRUE;
1351     path = g_find_program_in_path(app->try_exec);
1352     g_free(path);
1353     return (path != NULL);
1354 }
1355 
1356 /**
1357  * menu_cache_app_get_is_visible
1358  * @app: a menu cache item
1359  * @de_flags: bit mask of DE to test
1360  *
1361  * Checks if @app should be visible in any of desktop environments
1362  * @de_flags.
1363  *
1364  * Returns: %TRUE if @app is visible.
1365  *
1366  * Since: 0.2.0
1367  */
menu_cache_app_get_is_visible(MenuCacheApp * app,guint32 de_flags)1368 gboolean menu_cache_app_get_is_visible( MenuCacheApp* app, guint32 de_flags )
1369 {
1370     if(app->flags & FLAG_IS_NODISPLAY)
1371         return FALSE;
1372     return (!app->show_in_flags || (app->show_in_flags & de_flags)) &&
1373            _can_be_exec(app);
1374 }
1375 
1376 /*
1377 MenuCacheApp* menu_cache_find_app_by_exec( const char* exec )
1378 {
1379     return NULL;
1380 }
1381 */
1382 
1383 /**
1384  * menu_cache_get_dir_from_path
1385  * @cache: a menu cache instance
1386  * @path: item path
1387  *
1388  * Since: 0.1.0
1389  *
1390  * Deprecated: 0.3.4: Use menu_cache_item_from_path() instead.
1391  */
menu_cache_get_dir_from_path(MenuCache * cache,const char * path)1392 MenuCacheDir* menu_cache_get_dir_from_path( MenuCache* cache, const char* path )
1393 {
1394     char** names = g_strsplit( path + 1, "/", -1 );
1395     int i = 0;
1396     MenuCacheDir* dir = NULL;
1397 
1398     if( !names )
1399         return NULL;
1400 
1401     if( G_UNLIKELY(!names[0]) )
1402     {
1403         g_strfreev(names);
1404         return NULL;
1405     }
1406     /* the topmost dir of the path should be the root menu dir. */
1407     MENU_CACHE_LOCK;
1408     dir = cache->root_dir;
1409     if (G_UNLIKELY(dir == NULL) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id))
1410     {
1411         MENU_CACHE_UNLOCK;
1412         return NULL;
1413     }
1414 
1415     for( ++i; names[i]; ++i )
1416     {
1417         GSList* l;
1418         for( l = dir->children; l; l = l->next )
1419         {
1420             MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
1421             if( item->type == MENU_CACHE_TYPE_DIR && 0 == strcmp( item->id, names[i] ) )
1422                 dir = MENU_CACHE_DIR(item);
1423         }
1424         /* FIXME: we really should ref it on return since other thread may
1425            destroy the parent at this time and returned data become invalid.
1426            Therefore this call isn't thread-safe! */
1427         if( ! dir )
1428         {
1429             MENU_CACHE_UNLOCK;
1430             return NULL;
1431         }
1432     }
1433     MENU_CACHE_UNLOCK;
1434     return dir;
1435 }
1436 
1437 /**
1438  * menu_cache_item_from_path
1439  * @cache: cache to inspect
1440  * @path: item path
1441  *
1442  * Searches item @path in the @cache. The @path consists of item IDs
1443  * separated by slash ('/'). Returned data should be freed with
1444  * menu_cache_item_unref() after usage.
1445  *
1446  * Returns: (transfer full): found item or %NULL if no item found.
1447  *
1448  * Since: 0.3.4
1449  */
menu_cache_item_from_path(MenuCache * cache,const char * path)1450 MenuCacheItem* menu_cache_item_from_path( MenuCache* cache, const char* path )
1451 {
1452     char** names = g_strsplit( path + 1, "/", -1 );
1453     int i;
1454     MenuCacheDir* dir;
1455     MenuCacheItem* item = NULL;
1456 
1457     if( !names )
1458         return NULL;
1459 
1460     if( G_UNLIKELY(!names[0]) )
1461     {
1462         g_strfreev(names);
1463         return NULL;
1464     }
1465     /* the topmost dir of the path should be the root menu dir. */
1466     MENU_CACHE_LOCK;
1467     dir = cache->root_dir;
1468     if( G_UNLIKELY(!dir) || strcmp(names[0], MENU_CACHE_ITEM(dir)->id) != 0 )
1469         goto _end;
1470 
1471     for( i = 1; names[i]; ++i )
1472     {
1473         GSList* l;
1474         item = NULL;
1475         if( !dir )
1476             break;
1477         l = dir->children;
1478         dir = NULL;
1479         for( ; l; l = l->next )
1480         {
1481             item = MENU_CACHE_ITEM(l->data);
1482             if( g_strcmp0( item->id, names[i] ) == 0 )
1483             {
1484                 if( item->type == MENU_CACHE_TYPE_DIR )
1485                     dir = MENU_CACHE_DIR(item);
1486                 break;
1487             }
1488             item = NULL;
1489         }
1490         if( !item )
1491             break;
1492     }
1493     if(item)
1494         menu_cache_item_ref(item);
1495 _end:
1496     MENU_CACHE_UNLOCK;
1497     g_strfreev(names);
1498     return item;
1499 }
1500 
1501 /**
1502  * menu_cache_dir_make_path
1503  * @dir: a menu cache item
1504  *
1505  * Retrieves path of @dir. The path consists of item IDs separated by
1506  * slash ('/'). Returned data should be freed with g_free() after usage.
1507  *
1508  * Returns: (transfer full): item path.
1509  *
1510  * Since: 0.1.0
1511  */
menu_cache_dir_make_path(MenuCacheDir * dir)1512 char* menu_cache_dir_make_path( MenuCacheDir* dir )
1513 {
1514     GString* path = g_string_sized_new(1024);
1515     MenuCacheItem* it;
1516 
1517     MENU_CACHE_LOCK;
1518     while( (it = MENU_CACHE_ITEM(dir)) ) /* this is not top dir */
1519     {
1520         g_string_prepend( path, menu_cache_item_get_id(it) );
1521         g_string_prepend_c( path, '/' );
1522         /* FIXME: if parent is already unref'd by another thread then
1523            path being made will be broken. Is there any way to avoid that? */
1524         dir = it->parent;
1525     }
1526     MENU_CACHE_UNLOCK;
1527     return g_string_free( path, FALSE );
1528 }
1529 
get_socket_name(char * buf,int len)1530 static void get_socket_name( char* buf, int len )
1531 {
1532     char* dpy = g_strdup(g_getenv("DISPLAY"));
1533     if(dpy && *dpy)
1534     {
1535         char* p = strchr(dpy, ':');
1536         for(++p; *p && *p != '.' && *p != '\n';)
1537             ++p;
1538         if(*p)
1539             *p = '\0';
1540     }
1541 #if GLIB_CHECK_VERSION(2, 28, 0)
1542     g_snprintf( buf, len, "%s/menu-cached-%s", g_get_user_runtime_dir(),
1543                 dpy ? dpy : ":0" );
1544 #else
1545     g_snprintf( buf, len, "%s/.menu-cached-%s-%s", g_get_tmp_dir(),
1546                 dpy ? dpy : ":0", g_get_user_name() );
1547 #endif
1548     g_free(dpy);
1549 }
1550 
1551 #define MAX_RETRIES 25
1552 
fork_server(const char * path)1553 static gboolean fork_server(const char *path)
1554 {
1555     int ret, pid, status;
1556 
1557     if (!g_file_test (MENUCACHE_LIBEXECDIR "/menu-cached", G_FILE_TEST_IS_EXECUTABLE))
1558     {
1559         g_error("failed to find menu-cached");
1560     }
1561 
1562     /* Start daemon */
1563     pid = fork();
1564     if (pid == 0)
1565     {
1566         execl(MENUCACHE_LIBEXECDIR "/menu-cached", MENUCACHE_LIBEXECDIR "/menu-cached",
1567               path, NULL);
1568         g_print("failed to exec %s %s\n", MENUCACHE_LIBEXECDIR "/menu-cached", path);
1569     }
1570 
1571     /*
1572      * do a waitpid on the intermediate process to avoid zombies.
1573      */
1574 retry_wait:
1575     ret = waitpid(pid, &status, 0);
1576     if (ret < 0) {
1577         if (errno == EINTR)
1578             goto retry_wait;
1579     }
1580     return TRUE;
1581 }
1582 
1583 /* this thread is started by connect_server() */
server_io_thread(gpointer data)1584 static gpointer server_io_thread(gpointer data)
1585 {
1586     char buf[1024]; /* protocol has a lot shorter strings */
1587     ssize_t sz;
1588     size_t ptr = 0;
1589     int fd = GPOINTER_TO_INT(data);
1590     GHashTableIter it;
1591     char* menu_name;
1592     MenuCache* cache;
1593 
1594     while(fd >= 0)
1595     {
1596         sz = read(fd, &buf[ptr], sizeof(buf) - ptr);
1597         if(sz <= 0) /* socket error or EOF */
1598         {
1599             MENU_CACHE_LOCK;
1600             ptr = hash ? g_hash_table_size(hash) : 0;
1601             MENU_CACHE_UNLOCK;
1602             if (ptr == 0) /* don't need it anymore */
1603                 break;
1604             G_LOCK(connect);
1605             if(fd != server_fd) /* someone replaced us?! go out immediately! */
1606             {
1607                 G_UNLOCK(connect);
1608                 break;
1609             }
1610             server_fd = -1;
1611             G_UNLOCK(connect);
1612             DEBUG("connect failed, trying reconnect");
1613             sleep(1);
1614             if( ! connect_server(NULL) )
1615             {
1616                 g_critical("fail to re-connect to the server.");
1617                 MENU_CACHE_LOCK;
1618                 if(hash)
1619                 {
1620                     g_hash_table_iter_init(&it, hash);
1621                     while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
1622                         SET_CACHE_READY(cache);
1623                 }
1624                 MENU_CACHE_UNLOCK;
1625                 break;
1626             }
1627             DEBUG("successfully reconnected server, re-register menus.");
1628             /* re-register all menu caches */
1629             MENU_CACHE_LOCK;
1630             if(hash)
1631             {
1632                 g_hash_table_iter_init(&it, hash);
1633                 while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
1634                     register_menu_to_server(cache);
1635                     /* FIXME: need we remove it from hash if failed? */
1636             }
1637             MENU_CACHE_UNLOCK;
1638             break; /* next thread will do it */
1639         }
1640         while(sz > 0)
1641         {
1642             while(sz > 0)
1643             {
1644                 if(buf[ptr] == '\n')
1645                     break;
1646                 sz--;
1647                 ptr++;
1648             }
1649             if(ptr == sizeof(buf)) /* EOB reached, seems we got garbage */
1650             {
1651                 g_warning("menu cache: got garbage from server, break connect");
1652                 shutdown(fd, SHUT_RDWR); /* drop connection */
1653                 break; /* we handle it above */
1654             }
1655             else if(sz == 0) /* incomplete line, wait for data again */
1656                 break;
1657             /* we got a line, let check what we got */
1658             buf[ptr] = '\0';
1659             if(memcmp(buf, "REL:", 4) == 0) /* reload */
1660             {
1661                 DEBUG("server ask us to reload cache: %s", &buf[4]);
1662                 MENU_CACHE_LOCK;
1663                 if(hash)
1664                 {
1665                     g_hash_table_iter_init(&it, hash);
1666                     while(g_hash_table_iter_next(&it, (gpointer*)&menu_name, (gpointer*)&cache))
1667                     {
1668                         if(memcmp(cache->md5, &buf[4], 32) == 0)
1669                         {
1670                             DEBUG("RELOAD!");
1671                             menu_cache_reload(cache);
1672                             SET_CACHE_READY(cache);
1673                             break;
1674                         }
1675                     }
1676                 }
1677                 MENU_CACHE_UNLOCK;
1678                 /* DEBUG("cache reloaded"); */
1679             }
1680             else
1681                 g_warning("menu cache: unrecognized input: %s", buf);
1682             /* go to next line */
1683             sz--;
1684             if(sz > 0)
1685                 memmove(buf, &buf[ptr+1], sz);
1686             ptr = 0;
1687         }
1688     }
1689     G_LOCK(connect);
1690     if (fd == server_fd)
1691         server_fd = -1;
1692     G_UNLOCK(connect);
1693     close(fd);
1694     /* DEBUG("server io thread terminated"); */
1695 #if GLIB_CHECK_VERSION(2, 32, 0)
1696     g_thread_unref(g_thread_self());
1697 #endif
1698     return NULL;
1699 }
1700 
connect_server(GCancellable * cancellable)1701 static gboolean connect_server(GCancellable* cancellable)
1702 {
1703     int fd, rc;
1704     struct sockaddr_un addr;
1705     int retries = 0;
1706 
1707     G_LOCK(connect);
1708     if(server_fd != -1 || (cancellable && g_cancellable_is_cancelled(cancellable)))
1709     {
1710         G_UNLOCK(connect);
1711         return TRUE;
1712     }
1713 
1714 retry:
1715     fd = socket(PF_UNIX, SOCK_STREAM, 0);
1716     if (fd < 0)
1717     {
1718         g_print("Failed to create socket\n");
1719         G_UNLOCK(connect);
1720         return FALSE;
1721     }
1722 
1723     fcntl (fd, F_SETFD, FD_CLOEXEC);
1724 
1725     memset(&addr, 0, sizeof(addr));
1726     addr.sun_family = AF_UNIX;
1727 
1728     get_socket_name( addr.sun_path, sizeof( addr.sun_path ) );
1729 
1730     if( connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
1731     {
1732         rc = errno;
1733         close(fd);
1734         if(cancellable && g_cancellable_is_cancelled(cancellable))
1735         {
1736             G_UNLOCK(connect);
1737             return TRUE;
1738         }
1739         if((rc == ECONNREFUSED || rc == ENOENT) && retries == 0)
1740         {
1741             DEBUG("no running server found, starting it");
1742             fork_server(addr.sun_path);
1743             ++retries;
1744             goto retry;
1745         }
1746         if(retries < MAX_RETRIES)
1747         {
1748             usleep(50000);
1749             ++retries;
1750             goto retry;
1751         }
1752         g_print("Unable to connect\n");
1753         G_UNLOCK(connect);
1754         return FALSE;
1755     }
1756     server_fd = fd;
1757     G_UNLOCK(connect);
1758 #if GLIB_CHECK_VERSION(2, 32, 0)
1759     g_thread_new("menu-cache-io", server_io_thread, GINT_TO_POINTER(fd));
1760 #else
1761     g_thread_create(server_io_thread, GINT_TO_POINTER(fd), FALSE, NULL);
1762 #endif
1763     return TRUE;
1764 }
1765 
1766 #define CACHE_VERSION __num2str(VER_MAJOR) "." __num2str(VER_MINOR)
1767 #define __num2str(s) __def2str(s)
1768 #define __def2str(s) #s
1769 
_validate_env(const char * env)1770 static inline char *_validate_env(const char *env)
1771 {
1772     char *res, *c;
1773 
1774     if (env)
1775         res = g_strdup(env);
1776     else
1777         res = g_strdup("");
1778     for (c = res; *c; c++)
1779         if (*c == '\n' || *c == '\t')
1780             *c = ' ';
1781     return res;
1782 }
1783 
menu_cache_create(const char * menu_name)1784 static MenuCache* menu_cache_create(const char* menu_name)
1785 {
1786     MenuCache* cache;
1787     const gchar * const * langs = g_get_language_names();
1788     const char* xdg_cfg_env = g_getenv("XDG_CONFIG_DIRS");
1789     const char* xdg_prefix_env = g_getenv("XDG_MENU_PREFIX");
1790     const char* xdg_data_env = g_getenv("XDG_DATA_DIRS");
1791     const char* xdg_cfg_home_env = g_getenv("XDG_CONFIG_HOME");
1792     const char* xdg_data_home_env = g_getenv("XDG_DATA_HOME");
1793     const char* xdg_cache_home_env = g_getenv("XDG_CACHE_HOME");
1794     char *xdg_cfg, *xdg_prefix, *xdg_data, *xdg_cfg_home, *xdg_data_home, *xdg_cache_home;
1795     char* buf;
1796     const char* md5;
1797     char* file_name;
1798     int len = 0;
1799     GChecksum *sum;
1800     char *langs_list;
1801 
1802     xdg_cfg = _validate_env(xdg_cfg_env);
1803     xdg_prefix = _validate_env(xdg_prefix_env);
1804     xdg_data = _validate_env(xdg_data_env);
1805     xdg_cfg_home = _validate_env(xdg_cfg_home_env);
1806     xdg_data_home = _validate_env(xdg_data_home_env);
1807     xdg_cache_home = _validate_env(xdg_cache_home_env);
1808 
1809     /* reconstruct languages list in form as it should be in $LANGUAGES */
1810     langs_list = g_strjoinv(":", (char **)langs);
1811     for (buf = langs_list; *buf; buf++) /* reusing buf var as char pointer */
1812         if (*buf == '\n' || *buf == '\t')
1813             *buf = ' ';
1814 
1815     buf = g_strdup_printf( "REG:%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t" CACHE_VERSION
1816                            "\t00000000000000000000000000000000\n",
1817                             menu_name,
1818                             langs_list,
1819                             xdg_cache_home,
1820                             xdg_cfg,
1821                             xdg_prefix,
1822                             xdg_data,
1823                             xdg_cfg_home,
1824                             xdg_data_home );
1825 
1826     /* calculate the md5 sum of menu name + lang + all environment variables */
1827     sum = g_checksum_new(G_CHECKSUM_MD5);
1828     len = strlen(buf);
1829     g_checksum_update(sum, (guchar*)buf + 4, len - 38);
1830     md5 = g_checksum_get_string(sum);
1831     file_name = g_build_filename( g_get_user_cache_dir(), "menus", md5, NULL );
1832     DEBUG("cache file_name = %s", file_name);
1833     cache = menu_cache_new( file_name );
1834     cache->reg = buf;
1835     cache->md5 = buf + len - 33;
1836     memcpy( cache->md5, md5, 32 );
1837     cache->menu_name = g_strdup(menu_name);
1838     g_free( file_name );
1839     g_free(langs_list);
1840     g_free(xdg_cfg);
1841     g_free(xdg_prefix);
1842     g_free(xdg_data);
1843     g_free(xdg_cfg_home);
1844     g_free(xdg_data_home);
1845     g_free(xdg_cache_home);
1846 
1847     g_checksum_free(sum); /* md5 is also freed here */
1848 
1849     MENU_CACHE_LOCK;
1850     g_hash_table_insert( hash, g_strdup(menu_name), cache );
1851     MENU_CACHE_UNLOCK;
1852 
1853     return cache;
1854 }
1855 
register_menu_to_server(MenuCache * cache)1856 static gboolean register_menu_to_server(MenuCache* cache)
1857 {
1858     ssize_t len = strlen(cache->reg);
1859     /* FIXME: do unblocking I/O */
1860     if(write(server_fd, cache->reg, len) < len)
1861     {
1862         DEBUG("register_menu_to_server: sending failed");
1863         return FALSE; /* socket write failed */
1864     }
1865     return TRUE;
1866 }
1867 
unregister_menu_from_server(MenuCache * cache)1868 static void unregister_menu_from_server( MenuCache* cache )
1869 {
1870     char buf[38];
1871     g_snprintf( buf, 38, "UNR:%s\n", cache->md5 );
1872     /* FIXME: do unblocking I/O */
1873     if(write( server_fd, buf, 37 ) <= 0)
1874     {
1875         DEBUG("unregister_menu_from_server: sending failed");
1876     }
1877 }
1878 
menu_cache_loader_thread(gpointer data)1879 static gpointer menu_cache_loader_thread(gpointer data)
1880 {
1881     MenuCache* cache = (MenuCache*)data;
1882 
1883     /* try to connect server now */
1884     if(!connect_server(cache->cancellable))
1885     {
1886         g_print("unable to connect to menu-cached.\n");
1887         SET_CACHE_READY(cache);
1888         return NULL;
1889     }
1890     /* and request update from server */
1891     if ((cache->cancellable && g_cancellable_is_cancelled(cache->cancellable)) ||
1892         !register_menu_to_server(cache))
1893         SET_CACHE_READY(cache);
1894     return NULL;
1895 }
1896 
1897 /**
1898  * menu_cache_lookup
1899  * @menu_name: a menu name
1900  *
1901  * Searches for connection to menu-cached for @menu_name. If there is no
1902  * such connection exist then creates new one. Caller can be notified
1903  * when cache is (re)loaded by adding callback. Caller should check if
1904  * the cache is already loaded trying to retrieve its root.
1905  *
1906  * See also: menu_cache_add_reload_notify(), menu_cache_item_dup_parent().
1907  *
1908  * Returns: (transfer full): menu cache descriptor.
1909  *
1910  * Since: 0.1.0
1911  */
menu_cache_lookup(const char * menu_name)1912 MenuCache* menu_cache_lookup( const char* menu_name )
1913 {
1914     MenuCache* cache;
1915 
1916     /* lookup in a hash table for already loaded menus */
1917     MENU_CACHE_LOCK;
1918 #if !GLIB_CHECK_VERSION(2, 32, 0)
1919     /* FIXME: destroy them on application exit? */
1920     if(!sync_run_mutex)
1921         sync_run_mutex = g_mutex_new();
1922     if(!sync_run_cond)
1923         sync_run_cond = g_cond_new();
1924 #endif
1925     if( G_UNLIKELY( ! hash ) )
1926         hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL );
1927     else
1928     {
1929         cache = (MenuCache*)g_hash_table_lookup(hash, menu_name);
1930         if( cache )
1931         {
1932             menu_cache_ref(cache);
1933             MENU_CACHE_UNLOCK;
1934             return cache;
1935         }
1936     }
1937     MENU_CACHE_UNLOCK;
1938 
1939     cache = menu_cache_create(menu_name);
1940     cache->cancellable = g_cancellable_new();
1941 #if GLIB_CHECK_VERSION(2, 32, 0)
1942     cache->thr = g_thread_new(menu_name, menu_cache_loader_thread, cache);
1943 #else
1944     cache->thr = g_thread_create(menu_cache_loader_thread, cache, TRUE, NULL);
1945 #endif
1946     return cache;
1947 }
1948 
1949 /**
1950  * menu_cache_lookup_sync
1951  * @menu_name: a menu name
1952  *
1953  * Searches for data from menu-cached for @menu_name. If no connection
1954  * exists yet then creates new one and retrieves all data.
1955  *
1956  * Returns: (transfer full): menu cache descriptor.
1957  *
1958  * Since: 0.3.1
1959  */
menu_cache_lookup_sync(const char * menu_name)1960 MenuCache* menu_cache_lookup_sync( const char* menu_name )
1961 {
1962     MenuCache* mc = menu_cache_lookup(menu_name);
1963     MenuCacheDir* root_dir = menu_cache_dup_root_dir(mc);
1964     /* ensure that the menu cache is loaded */
1965     if(root_dir)
1966         menu_cache_item_unref(MENU_CACHE_ITEM(root_dir));
1967     else /* if it's not yet loaded */
1968     {
1969         MenuCacheNotifyId notify_id;
1970         /* add stub */
1971         notify_id = menu_cache_add_reload_notify(mc, NULL, NULL);
1972 #if GLIB_CHECK_VERSION(2, 32, 0)
1973         g_mutex_lock(&sync_run_mutex);
1974         while(!mc->ready)
1975             g_cond_wait(&sync_run_cond, &sync_run_mutex);
1976         g_mutex_unlock(&sync_run_mutex);
1977 #else
1978         g_mutex_lock(sync_run_mutex);
1979         g_debug("menu_cache_lookup_sync: enter wait %p", mc);
1980         while(!mc->ready)
1981             g_cond_wait(sync_run_cond, sync_run_mutex);
1982         g_debug("menu_cache_lookup_sync: leave wait");
1983         g_mutex_unlock(sync_run_mutex);
1984 #endif
1985         menu_cache_remove_reload_notify(mc, notify_id);
1986     }
1987     return mc;
1988 }
1989 
list_app_in_dir(MenuCacheDir * dir,GSList * list)1990 static GSList* list_app_in_dir(MenuCacheDir* dir, GSList* list)
1991 {
1992     GSList* l;
1993     for( l = dir->children; l; l = l->next )
1994     {
1995         MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
1996         switch( menu_cache_item_get_type(item) )
1997         {
1998         case MENU_CACHE_TYPE_DIR:
1999             list = list_app_in_dir( MENU_CACHE_DIR(item), list );
2000             break;
2001         case MENU_CACHE_TYPE_APP:
2002             list = g_slist_prepend(list, menu_cache_item_ref(item));
2003             break;
2004         case MENU_CACHE_TYPE_NONE:
2005         case MENU_CACHE_TYPE_SEP:
2006             break;
2007         }
2008     }
2009     return list;
2010 }
2011 
2012 /**
2013  * menu_cache_list_all_apps
2014  * @cache: a menu cache descriptor
2015  *
2016  * Retrieves full list of applications in menu cache. Returned list
2017  * should be freed with g_slist_free_full(list, menu_cache_item_unref)
2018  * after usage.
2019  *
2020  * Returns: (transfer full) (element-type MenuCacheItem): list of items.
2021  *
2022  * Since: 0.1.2
2023  */
menu_cache_list_all_apps(MenuCache * cache)2024 GSList* menu_cache_list_all_apps(MenuCache* cache)
2025 {
2026     GSList* list;
2027     MENU_CACHE_LOCK;
2028     if (G_UNLIKELY(!cache->root_dir)) /* empty cache */
2029         list = NULL;
2030     else
2031         list = list_app_in_dir(cache->root_dir, NULL);
2032     MENU_CACHE_UNLOCK;
2033     return list;
2034 }
2035 
2036 /**
2037  * menu_cache_get_desktop_env_flag
2038  * @cache: a menu cache descriptor
2039  * @desktop_env: desktop environment name
2040  *
2041  * Makes bit mask of desktop environment from its name. The @desktop_env
2042  * may be simple string or colon separated list of compatible session
2043  * names according to XDG_CURRENT_DESKTOP freedesktop.org specification.
2044  *
2045  * Returns: DE bit mask.
2046  *
2047  * Since: 0.2.0
2048  */
menu_cache_get_desktop_env_flag(MenuCache * cache,const char * desktop_env)2049 guint32 menu_cache_get_desktop_env_flag( MenuCache* cache, const char* desktop_env )
2050 {
2051     char** de;
2052     char **envs;
2053     guint32 flags = 0;
2054     int j;
2055 
2056     if (desktop_env == NULL || desktop_env[0] == '\0')
2057         return flags;
2058 
2059     envs = g_strsplit(desktop_env, ":", -1);
2060     MENU_CACHE_LOCK;
2061     de = cache->known_des;
2062     for (j = 0; envs[j]; j++)
2063     {
2064         if( de )
2065         {
2066             int i;
2067             for( i = 0; de[i]; ++i )
2068                 if (strcmp(envs[j], de[i]) == 0)
2069                     break;
2070             if (de[i])
2071             {
2072                 flags |= 1 << (i + N_KNOWN_DESKTOPS);
2073                 continue;
2074             }
2075         }
2076         if (strcmp(envs[j], "GNOME") == 0)
2077             flags |= SHOW_IN_GNOME;
2078         else if (strcmp(envs[j], "KDE") == 0)
2079             flags |= SHOW_IN_KDE;
2080         else if (strcmp(envs[j], "XFCE") == 0)
2081             flags |= SHOW_IN_XFCE;
2082         else if (strcmp(envs[j], "LXDE") == 0)
2083             flags |= SHOW_IN_LXDE;
2084         else if (strcmp(envs[j], "ROX") == 0)
2085             flags |= SHOW_IN_ROX;
2086     }
2087     MENU_CACHE_UNLOCK;
2088     g_strfreev(envs);
2089     return flags;
2090 }
2091 
_scan_by_id(MenuCacheItem * item,const char * id)2092 static MenuCacheItem *_scan_by_id(MenuCacheItem *item, const char *id)
2093 {
2094     GSList *l;
2095 
2096     if (item)
2097         switch (menu_cache_item_get_type(item))
2098         {
2099             case MENU_CACHE_TYPE_DIR:
2100                 for (l = MENU_CACHE_DIR(item)->children; l; l = l->next)
2101                 {
2102                     item = _scan_by_id(MENU_CACHE_ITEM(l->data), id);
2103                     if (item)
2104                         return item;
2105                 }
2106                 break;
2107             case MENU_CACHE_TYPE_APP:
2108                 if (g_strcmp0(menu_cache_item_get_id(item), id) == 0)
2109                     return item;
2110                 break;
2111             default: ;
2112         }
2113     return NULL;
2114 }
2115 
2116 /**
2117  * menu_cache_find_item_by_id
2118  * @cache: a menu cache descriptor
2119  * @id: item ID (name such as 'application.desktop')
2120  *
2121  * Searches if @id already exists within @cache and returns found item.
2122  * Returned data should be freed with menu_cache_item_unref() after usage.
2123  *
2124  * Returns: (transfer full): found item or %NULL.
2125  *
2126  * Since: 0.5.0
2127  */
menu_cache_find_item_by_id(MenuCache * cache,const char * id)2128 MenuCacheItem *menu_cache_find_item_by_id(MenuCache *cache, const char *id)
2129 {
2130     MenuCacheItem *item = NULL;
2131 
2132     MENU_CACHE_LOCK;
2133     if (cache && id)
2134         item = _scan_by_id(MENU_CACHE_ITEM(cache->root_dir), id);
2135     if (item)
2136         menu_cache_item_ref(item);
2137     MENU_CACHE_UNLOCK;
2138     return item;
2139 }
2140 
list_app_in_dir_for_cat(MenuCacheDir * dir,GSList * list,const char * id)2141 static GSList* list_app_in_dir_for_cat(MenuCacheDir *dir, GSList *list, const char *id)
2142 {
2143     const char **cat;
2144     GSList *l;
2145 
2146     for (l = dir->children; l; l = l->next)
2147     {
2148         MenuCacheItem *item = MENU_CACHE_ITEM(l->data);
2149         switch (item->type)
2150         {
2151         case MENU_CACHE_TYPE_DIR:
2152             list = list_app_in_dir_for_cat(MENU_CACHE_DIR(item), list, id);
2153             break;
2154         case MENU_CACHE_TYPE_APP:
2155             cat = MENU_CACHE_APP(item)->categories;
2156             if (cat) while (*cat)
2157                 if (*cat++ == id)
2158                 {
2159                     list = g_slist_prepend(list, menu_cache_item_ref(item));
2160                     break;
2161                 }
2162             break;
2163         case MENU_CACHE_TYPE_NONE:
2164         case MENU_CACHE_TYPE_SEP:
2165             break;
2166         }
2167     }
2168     return list;
2169 }
2170 
2171 /**
2172  * menu_cache_list_all_for_category
2173  * @cache: a menu cache descriptor
2174  * @category: category to list items
2175  *
2176  * Retrieves list of applications in menu cache which have @category in
2177  * their list of categories. The search is case-sensitive. Returned list
2178  * should be freed with g_slist_free_full(list, menu_cache_item_unref)
2179  * after usage.
2180  *
2181  * Returns: (transfer full) (element-type MenuCacheItem): list of items.
2182  *
2183  * Since: 1.0.0
2184  */
menu_cache_list_all_for_category(MenuCache * cache,const char * category)2185 GSList *menu_cache_list_all_for_category(MenuCache* cache, const char *category)
2186 {
2187     GQuark q;
2188     GSList *list;
2189 
2190     g_return_val_if_fail(cache != NULL && category != NULL, NULL);
2191     q = g_quark_try_string(category);
2192     if (q == 0)
2193         return NULL;
2194     MENU_CACHE_LOCK;
2195     if (G_UNLIKELY(cache->root_dir == NULL))
2196         list = NULL;
2197     else
2198         list = list_app_in_dir_for_cat(cache->root_dir, NULL, g_quark_to_string(q));
2199     MENU_CACHE_UNLOCK;
2200     return list;
2201 }
2202 
list_app_in_dir_for_kw(MenuCacheDir * dir,GSList * list,const char * kw)2203 static GSList* list_app_in_dir_for_kw(MenuCacheDir *dir, GSList *list, const char *kw)
2204 {
2205     GSList *l;
2206 
2207     for (l = dir->children; l; l = l->next)
2208     {
2209         MenuCacheItem *item = MENU_CACHE_ITEM(l->data);
2210         switch (item->type)
2211         {
2212         case MENU_CACHE_TYPE_DIR:
2213             list = list_app_in_dir_for_kw(MENU_CACHE_DIR(item), list, kw);
2214             break;
2215         case MENU_CACHE_TYPE_APP:
2216             if (strstr(MENU_CACHE_APP(item)->keywords, kw) != NULL)
2217                 list = g_slist_prepend(list, menu_cache_item_ref(item));
2218             break;
2219         case MENU_CACHE_TYPE_NONE:
2220         case MENU_CACHE_TYPE_SEP:
2221             break;
2222         }
2223     }
2224     return list;
2225 }
2226 
2227 /**
2228  * menu_cache_list_all_for_keyword
2229  * @cache: a menu cache descriptor
2230  * @keyword: a keyword to search
2231  *
2232  * Retrieves list of applications in menu cache which have a @keyword
2233  * as either a word or part of word in exec command, name, generic name
2234  * or defined keywords. The search is case-insensitive. Returned list
2235  * should be freed with g_slist_free_full(list, menu_cache_item_unref)
2236  * after usage.
2237  *
2238  * Returns: (transfer full) (element-type MenuCacheItem): list of items.
2239  *
2240  * Since: 1.0.0
2241  */
menu_cache_list_all_for_keyword(MenuCache * cache,const char * keyword)2242 GSList *menu_cache_list_all_for_keyword(MenuCache* cache, const char *keyword)
2243 {
2244     char *casefolded = g_utf8_casefold(keyword, -1);
2245     GSList *list;
2246 
2247     g_return_val_if_fail(cache != NULL && keyword != NULL, NULL);
2248     MENU_CACHE_LOCK;
2249     if (G_UNLIKELY(cache->root_dir == NULL))
2250         list = NULL;
2251     else
2252         list = list_app_in_dir_for_kw(cache->root_dir, NULL, casefolded);
2253     MENU_CACHE_UNLOCK;
2254     g_free(casefolded);
2255     return list;
2256 }
2257