1 /*
2  *      fm-vfs-menu.c
3  *      VFS for "menu://applications/" path using menu-cache library.
4  *
5  *      Copyright 2012-2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6  *
7  *      This library is free software; you can redistribute it and/or
8  *      modify it under the terms of the GNU Lesser General Public
9  *      License as published by the Free Software Foundation; either
10  *      version 2.1 of the License, or (at your option) any later version.
11  *
12  *      This library is distributed in the hope that it will be useful,
13  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *      Lesser General Public License for more details.
16  *
17  *      You should have received a copy of the GNU Lesser General Public
18  *      License along with this library; if not, write to the Free Software
19  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include "fm-file.h"
27 #include "glib-compat.h"
28 #include "fm-xml-file.h"
29 
30 #include <glib/gi18n-lib.h>
31 #include <menu-cache.h>
32 
33 /* support for libmenu-cache 0.4.x */
34 #ifndef MENU_CACHE_CHECK_VERSION
35 # ifdef HAVE_MENU_CACHE_DIR_LIST_CHILDREN
36 #  define MENU_CACHE_CHECK_VERSION(_a,_b,_c) (_a == 0 && _b < 5) /* < 0.5.0 */
37 # else
38 #  define MENU_CACHE_CHECK_VERSION(_a,_b,_c) 0 /* not even 0.4.0 */
39 # endif
40 #endif
41 
42 /* libmenu-cache is multithreaded since 0.4.x */
43 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
44 # define RUN_WITH_MENU_CACHE(__func,__data) __func(__data)
45 #else
46 # define RUN_WITH_MENU_CACHE(__func,__data) fm_run_in_default_main_context(__func,__data)
47 #endif
48 
49 /* beforehand declarations */
50 GFile *_fm_vfs_menu_new_for_uri(const char *uri);
51 
52 
53 /* ---- applications.menu manipulations ---- */
54 typedef struct _FmMenuMenuTree          FmMenuMenuTree;
55 
56 struct _FmMenuMenuTree
57 {
58     FmXmlFile *menu; /* composite tree to analyze */
59     char *file_path; /* current file */
60     GCancellable *cancellable;
61     gint line, pos; /* we remember position in deepest file */
62 };
63 
64 G_LOCK_DEFINE_STATIC(menuTree); /* locks all .menu file access data below */
65 static FmXmlFileTag menuTag_Menu = 0; /* tags that are supported */
66 static FmXmlFileTag menuTag_Include = 0;
67 static FmXmlFileTag menuTag_Exclude = 0;
68 static FmXmlFileTag menuTag_Filename = 0;
69 static FmXmlFileTag menuTag_Category = 0;
70 static FmXmlFileTag menuTag_MergeFile = 0;
71 static FmXmlFileTag menuTag_Directory = 0;
72 static FmXmlFileTag menuTag_Name = 0;
73 static FmXmlFileTag menuTag_Deleted = 0;
74 static FmXmlFileTag menuTag_NotDeleted = 0;
75 
76 /* this handler does nothing, used just to remember its id */
_menu_xml_handler_pass(FmXmlFileItem * item,GList * children,char * const * attribute_names,char * const * attribute_values,guint n_attributes,gint line,gint pos,GError ** error,gpointer user_data)77 static gboolean _menu_xml_handler_pass(FmXmlFileItem *item, GList *children,
78                                        char * const *attribute_names,
79                                        char * const *attribute_values,
80                                        guint n_attributes, gint line, gint pos,
81                                        GError **error, gpointer user_data)
82 {
83     FmMenuMenuTree *data = user_data;
84     return !g_cancellable_set_error_if_cancelled(data->cancellable, error);
85 }
86 
87 /* FIXME: handle <Move><Old>...</Old><New>...</New></Move> */
88 
_get_menu_name(FmXmlFileItem * item)89 static inline const char *_get_menu_name(FmXmlFileItem *item)
90 {
91     if (fm_xml_file_item_get_tag(item) != menuTag_Menu) /* skip not menu */
92         return NULL;
93     item = fm_xml_file_item_find_child(item, menuTag_Name);
94     if (item == NULL) /* no Name tag? */
95         return NULL;
96     item = fm_xml_file_item_find_child(item, FM_XML_FILE_TEXT);
97     if (item == NULL) /* empty Name tag? */
98         return NULL;
99     return fm_xml_file_item_get_data(item, NULL);
100 }
101 
_find_in_children(GList * list,const char * path)102 static FmXmlFileItem *_find_in_children(GList *list, const char *path)
103 {
104     const char *ptr;
105     char *_ptr;
106 
107     if (list == NULL)
108         return NULL;
109     g_debug("menu tree: searching for '%s'", path);
110     ptr = strchr(path, '/');
111     if (ptr == NULL)
112     {
113         ptr = path;
114         path = _ptr = NULL;
115     }
116     else
117     {
118         _ptr = g_strndup(path, ptr - path);
119         path = ptr + 1;
120         ptr = _ptr;
121     }
122     while (list)
123     {
124         const char *elem_name = _get_menu_name(list->data);
125         /* g_debug("got child %d: %s", fm_xml_file_item_get_tag(list->data), elem_name); */
126         if (g_strcmp0(elem_name, ptr) == 0)
127             break;
128         else
129             list = list->next;
130     }
131     g_free(_ptr);
132     if (list && path)
133     {
134         FmXmlFileItem *item;
135 
136         list = fm_xml_file_item_get_children(list->data);
137         item = _find_in_children(list, path);
138         g_list_free(list);
139         return item;
140     }
141     return list ? list->data : NULL;
142 }
143 
144 /* returns only <Menu> child */
_set_default_contents(FmXmlFile * file,const char * basename)145 static FmXmlFileItem *_set_default_contents(FmXmlFile *file, const char *basename)
146 {
147     FmXmlFileItem *item, *child;
148     char *path;
149 
150     /* set DTD */
151     fm_xml_file_set_dtd(file,
152                         "Menu PUBLIC '-//freedesktop//DTD Menu 1.0//EN'\n"
153                         " 'http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd'",
154                         NULL);
155     /* set content:
156         <Menu>
157             <Name>Applications</Name>
158             <MergeFile type='parent'>/etc/xdg/menus/%s</MergeFile>
159         </Menu> */
160     item = fm_xml_file_item_new(menuTag_Menu);
161     fm_xml_file_insert_first(file, item);
162     child = fm_xml_file_item_new(menuTag_Name);
163     fm_xml_file_item_append_text(child, "Applications", -1, FALSE);
164     fm_xml_file_item_append_child(item, child);
165     child = fm_xml_file_item_new(menuTag_MergeFile);
166     fm_xml_file_item_set_attribute(child, "type", "parent");
167     /* FIXME: what is correct way to handle this? is it required at all? */
168     path = g_strdup_printf("/etc/xdg/menus/%s", basename);
169     fm_xml_file_item_append_text(child, path, -1, FALSE);
170     g_free(path);
171     fm_xml_file_item_append_child(item, child);
172     return item;
173     /* FIXME: can errors happen above? */
174 }
175 
176 /* tries to create <Menu>... path in children list and returns <Menu> for it */
_create_path_in_tree(FmXmlFileItem * parent,const char * path)177 static FmXmlFileItem *_create_path_in_tree(FmXmlFileItem *parent, const char *path)
178 {
179     const char *ptr;
180     char *_ptr;
181     GList *list, *l;
182     FmXmlFileItem *item, *name;
183 
184     if (path == NULL)
185         return NULL;
186     list = fm_xml_file_item_get_children(parent);
187     /* g_debug("menu tree: creating '%s'", path); */
188     ptr = strchr(path, '/');
189     if (ptr == NULL)
190     {
191         ptr = path;
192         path = _ptr = NULL;
193     }
194     else
195     {
196         _ptr = g_strndup(path, ptr - path);
197         path = ptr + 1;
198         ptr = _ptr;
199     }
200     for (l = list; l; l = l->next)
201     {
202         const char *elem_name = _get_menu_name(l->data);
203         /* g_debug("got child %d: %s", fm_xml_file_item_get_tag(list->data), elem_name); */
204         if (g_strcmp0(elem_name, ptr) == 0)
205             break;
206     }
207     if (l) /* subpath already exists */
208     {
209         item = l->data;
210         g_list_free(list);
211         g_free(_ptr);
212         return _create_path_in_tree(item, path);
213     }
214     g_list_free(list);
215     /* create subtag <Name> */
216     name = fm_xml_file_item_new(menuTag_Name);
217     fm_xml_file_item_append_text(name, ptr, -1, FALSE);
218     g_free(_ptr);
219     /* create <Menu> and insert it */
220     item = fm_xml_file_item_new(menuTag_Menu);
221     if (!fm_xml_file_item_append_child(parent, item) ||
222         !fm_xml_file_item_append_child(item, name))
223     {
224         /* FIXME: it cannot fail on newly created items! */
225         fm_xml_file_item_destroy(name);
226         fm_xml_file_item_destroy(item);
227         return NULL;
228     }
229     /* path is NULL if it is a final subpath */
230     return path ? _create_path_in_tree(item, path) : item;
231 }
232 
233 /* locks menuTree, sets fields in data, sets gf
234    returns "Applications" menu on success and NULL on failure */
_prepare_contents(FmMenuMenuTree * data,GCancellable * cancellable,GError ** error,GFile ** gf)235 static FmXmlFileItem *_prepare_contents(FmMenuMenuTree *data, GCancellable *cancellable,
236                                         GError **error, GFile **gf)
237 {
238     const char *xdg_menu_prefix;
239     char *contents;
240     gsize len;
241     GList *xml = NULL;
242     FmXmlFileItem *apps;
243     gboolean ok;
244 
245     /* do it in compatibility with lxpanel */
246     xdg_menu_prefix = g_getenv("XDG_MENU_PREFIX");
247     contents = g_strdup_printf("%sapplications.menu",
248                                xdg_menu_prefix ? xdg_menu_prefix : "lxde-");
249     data->file_path = g_build_filename(g_get_user_config_dir(), "menus",
250                                        contents, NULL);
251     *gf = g_file_new_for_path(data->file_path);
252     data->menu = fm_xml_file_new(NULL);
253     data->line = data->pos = -1;
254     data->cancellable = cancellable;
255     G_LOCK(menuTree);
256     /* set tags, ignore errors */
257     menuTag_Menu = fm_xml_file_set_handler(data->menu, "Menu",
258                                            &_menu_xml_handler_pass, FALSE, NULL);
259     menuTag_Name = fm_xml_file_set_handler(data->menu, "Name",
260                                            &_menu_xml_handler_pass, FALSE, NULL);
261     menuTag_Deleted = fm_xml_file_set_handler(data->menu, "Deleted",
262                                               &_menu_xml_handler_pass, FALSE, NULL);
263     menuTag_NotDeleted = fm_xml_file_set_handler(data->menu, "NotDeleted",
264                                                  &_menu_xml_handler_pass, FALSE, NULL);
265     menuTag_Directory = fm_xml_file_set_handler(data->menu, "Directory",
266                                                 &_menu_xml_handler_pass, FALSE, NULL);
267     menuTag_Include = fm_xml_file_set_handler(data->menu, "Include",
268                                               &_menu_xml_handler_pass, FALSE, NULL);
269     menuTag_Exclude = fm_xml_file_set_handler(data->menu, "Exclude",
270                                               &_menu_xml_handler_pass, FALSE, NULL);
271     menuTag_Filename = fm_xml_file_set_handler(data->menu, "Filename",
272                                                &_menu_xml_handler_pass, FALSE, NULL);
273     menuTag_MergeFile = fm_xml_file_set_handler(data->menu, "MergeFile",
274                                                 &_menu_xml_handler_pass, FALSE, NULL);
275     menuTag_Category = fm_xml_file_set_handler(data->menu, "Category",
276                                                &_menu_xml_handler_pass, FALSE, NULL);
277     if (!g_file_query_exists(*gf, cancellable))
278     {
279         /* if file doesn't exist then it should be created with default contents */
280         apps = _set_default_contents(data->menu, contents);
281         g_free(contents);
282         return apps;
283     }
284     g_free(contents); /* we used it temporarily */
285     contents = NULL;
286     ok = g_file_load_contents(*gf, cancellable, &contents, &len, NULL, error);
287     if (!ok)
288         return NULL;
289     ok = fm_xml_file_parse_data(data->menu, contents, len, error, data);
290     g_free(contents);
291     if (ok)
292         xml = fm_xml_file_finish_parse(data->menu, error);
293     if (xml == NULL) /* error is set by failed function */
294     {
295         if (data->line == -1)
296             data->line = fm_xml_file_get_current_line(data->menu, &data->pos);
297         g_prefix_error(error, _("XML file '%s' error (%d:%d): "), data->file_path,
298                        data->line, data->pos);
299     }
300     else
301     {
302         apps = _find_in_children(xml, "Applications");
303         g_list_free(xml);
304         if (apps)
305             return apps;
306         g_set_error_literal(error, G_FILE_ERROR, G_FILE_ERROR_NOENT,
307                             _("XML file doesn't contain Applications root"));
308     }
309     return NULL;
310 }
311 
312 /* replaces invalid chars in path with minus sign */
_get_pathtag_for_path(const char * path)313 static inline char *_get_pathtag_for_path(const char *path)
314 {
315     char *pathtag, *c;
316 
317     pathtag = g_strdup(path);
318     for (c = pathtag; *c; c++)
319         if (*c == '/' || *c == '\t' || *c == '\n' || *c == '\r' || *c == ' ')
320             *c = '-';
321     return pathtag;
322 }
323 
_save_new_menu_file(GFile * gf,FmXmlFile * file,GCancellable * cancellable,GError ** error)324 static gboolean _save_new_menu_file(GFile *gf, FmXmlFile *file,
325                                     GCancellable *cancellable,
326                                     GError **error)
327 {
328     gsize len;
329     char *contents = fm_xml_file_to_data(file, &len, error);
330     gboolean result = FALSE;
331 
332     if (contents == NULL)
333         return FALSE;
334     /* g_debug("new menu file: %s", contents); */
335     result = g_file_replace_contents(gf, contents, len, NULL, FALSE,
336                                      G_FILE_CREATE_REPLACE_DESTINATION, NULL,
337                                      cancellable, error);
338     g_free(contents);
339     return result;
340 }
341 
342 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
343 /* changes .menu XML file */
_remove_directory(const char * path,GCancellable * cancellable,GError ** error)344 static gboolean _remove_directory(const char *path, GCancellable *cancellable,
345                                   GError **error)
346 {
347     GList *xml = NULL, *it;
348     GFile *gf;
349     FmXmlFileItem *apps, *item;
350     FmMenuMenuTree data;
351     gboolean ok = TRUE;
352 
353     /* g_debug("deleting menu folder '%s'", path); */
354     /* FIXME: check if there is that path in the XML tree before doing anything */
355     apps = _prepare_contents(&data, cancellable, error, &gf);
356     if (apps == NULL)
357     {
358         /* either failed to load contents or cancelled */
359         ok = FALSE;
360     }
361     else if ((xml = fm_xml_file_item_get_children(apps)) != NULL &&
362              (item = _find_in_children(xml, path)) != NULL)
363     {
364         /* if path is found and has <NotDeleted/> then replace it with <Deleted/> */
365         g_list_free(xml);
366         xml = fm_xml_file_item_get_children(item);
367         for (it = xml; it; it = it->next)
368         {
369             FmXmlFileTag tag = fm_xml_file_item_get_tag(it->data);
370             if (tag == menuTag_Deleted || tag == menuTag_NotDeleted)
371                 fm_xml_file_item_destroy(it->data);
372         }
373         goto _add_deleted_tag;
374     }
375     else
376     {
377         /* else create path and add <Deleted/> to it */
378         item = _create_path_in_tree(apps, path);
379         if (item == NULL)
380         {
381             g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
382                         _("Cannot create XML definition for '%s'"), path);
383             ok = FALSE;
384         }
385         else
386         {
387             FmXmlFileItem *item2;
388 
389 _add_deleted_tag:
390             item2 = fm_xml_file_item_new(menuTag_Deleted);
391             fm_xml_file_item_set_comment(item2, "deleted by LibFM");
392             fm_xml_file_item_append_child(item, item2); /* NOTE: it cannot fail */
393         }
394     }
395     if (ok)
396         ok = _save_new_menu_file(gf, data.menu, cancellable, error);
397     G_UNLOCK(menuTree);
398     g_object_unref(gf);
399     g_object_unref(data.menu);
400     g_free(data.file_path);
401     g_list_free(xml);
402     return ok;
403 }
404 
405 /* changes .menu XML file */
_add_directory(const char * path,GCancellable * cancellable,GError ** error)406 static gboolean _add_directory(const char *path, GCancellable *cancellable,
407                                GError **error)
408 {
409     GList *xml = NULL, *it;
410     GFile *gf;
411     FmXmlFileItem *apps, *item, *child;
412     FmMenuMenuTree data;
413     gboolean ok = TRUE;
414 
415     /* g_debug("adding menu folder '%s'", path); */
416     /* FIXME: fail if such Menu Name already not deleted in XML tree */
417     apps = _prepare_contents(&data, cancellable, error, &gf);
418     if (apps == NULL)
419     {
420         /* either failed to load contents or cancelled */
421         ok = FALSE;
422     }
423     else if ((xml = fm_xml_file_item_get_children(apps)) != NULL &&
424              (item = _find_in_children(xml, path)) != NULL)
425     {
426         /* "undelete" the directory: */
427         /* if path is found and has <Deleted/> then replace it with <NotDeleted/> */
428         g_list_free(xml);
429         xml = fm_xml_file_item_get_children(item);
430         ok = FALSE; /* it should be Deleted, otherwise error, see FIXME above */
431         for (it = xml; it; it = it->next)
432         {
433             FmXmlFileTag tag = fm_xml_file_item_get_tag(it->data);
434             if (tag == menuTag_Deleted)
435             {
436                 fm_xml_file_item_destroy(it->data);
437                 ok = TRUE; /* see FIXME above */
438             }
439             else if (tag == menuTag_NotDeleted)
440             {
441                 fm_xml_file_item_destroy(it->data);
442                 ok = FALSE; /* see FIXME above */
443             }
444         }
445         if (!ok) /* see FIXME above */
446             g_set_error(error, G_IO_ERROR, G_IO_ERROR_EXISTS,
447                         _("Menu path '%s' already exists"), path);
448         else
449         {
450             child = fm_xml_file_item_new(menuTag_NotDeleted);
451             fm_xml_file_item_set_comment(child, "undeleted by LibFM");
452             fm_xml_file_item_append_child(item, child); /* NOTE: it cannot fail */
453         }
454     }
455     else
456     {
457         /* else create path and add content to it */
458         item = _create_path_in_tree(apps, path);
459         if (item == NULL)
460         {
461             g_set_error(error, G_IO_ERROR, G_IO_ERROR_EXISTS,
462                         _("Cannot create XML definition for '%s'"), path);
463             ok = FALSE;
464         }
465         else
466         {
467             char *pathtag, *dir, *contents;
468             GString *str;
469 
470             /* add <NotDeleted/> */
471             child = fm_xml_file_item_new(menuTag_NotDeleted);
472             fm_xml_file_item_append_child(item, child);
473             /* touch .directory file, ignore errors */
474             dir = strrchr(path, '/'); /* use it as a storage for basename */
475             if (dir)
476                 dir++;
477             else
478                 dir = (char *)path;
479             contents = g_strdup_printf("[" G_KEY_FILE_DESKTOP_GROUP "]\n"
480                                        G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_DIRECTORY "\n"
481                                        G_KEY_FILE_DESKTOP_KEY_NAME "=%s", dir);
482             pathtag = _get_pathtag_for_path(path);
483             dir = g_build_filename(g_get_user_data_dir(), "desktop-directories",
484                                    pathtag, NULL);
485             str = g_string_new(dir);
486             g_free(dir);
487             g_string_append(str, ".directory");
488             g_file_set_contents(str->str, contents, -1, NULL);
489             /* FIXME: report errors */
490             g_free(contents);
491             /* add <Directory>.....</Directory> */
492             child = fm_xml_file_item_new(menuTag_Directory);
493             g_string_printf(str, "%s.directory", pathtag);
494             fm_xml_file_item_append_text(child, str->str, str->len, FALSE);
495             fm_xml_file_item_append_child(item, child);
496             /* add <Include><Category>......</Category></Include> */
497             child = fm_xml_file_item_new(menuTag_Include);
498             fm_xml_file_item_append_child(item, child);
499             g_string_printf(str, "X-%s", pathtag);
500             g_free(pathtag);
501             item = fm_xml_file_item_new(menuTag_Category); /* reuse item var. */
502             fm_xml_file_item_append_text(item, str->str, str->len, FALSE);
503             fm_xml_file_item_append_child(child, item);
504             g_string_free(str, TRUE);
505             /* ignoring errors since new created items cannot fail on append */
506         }
507     }
508     if (ok)
509         ok = _save_new_menu_file(gf, data.menu, cancellable, error);
510     G_UNLOCK(menuTree);
511     g_object_unref(gf);
512     g_object_unref(data.menu);
513     g_free(data.file_path);
514     g_list_free(xml);
515     return ok;
516 }
517 #endif
518 
519 /* changes .menu XML file */
_add_application(const char * path,GCancellable * cancellable,GError ** error)520 static gboolean _add_application(const char *path, GCancellable *cancellable,
521                                  GError **error)
522 {
523     const char *id;
524     char *dir;
525     GList *xml = NULL, *it;
526     GFile *gf;
527     FmXmlFileItem *apps, *item, *child;
528     FmMenuMenuTree data;
529     gboolean ok = TRUE;
530 
531     id = strrchr(path, '/');
532     if (id == NULL)
533     {
534         dir = NULL;
535         id = path;
536     }
537     else
538     {
539         dir = g_strndup(path, id - path);
540         id++;
541     }
542     apps = _prepare_contents(&data, cancellable, error, &gf);
543     if (apps == NULL)
544     {
545         /* either failed to load contents or cancelled */
546         ok = FALSE;
547     }
548     else if (dir == NULL) /* adding to root, use apps as target */
549     {
550         item = apps;
551         goto _set;
552     }
553     else if ((xml = fm_xml_file_item_get_children(apps)) != NULL &&
554              (item = _find_in_children(xml, dir)) != NULL)
555     {
556         /* already found that path */
557         goto _set;
558     }
559     else
560     {
561         /* else create path and add content to it */
562         item = _create_path_in_tree(apps, dir);
563         if (item == NULL)
564         {
565             g_set_error(error, G_IO_ERROR, G_IO_ERROR_EXISTS,
566                         _("Cannot create XML definition for '%s'"), path);
567             ok = FALSE;
568         }
569         else
570         {
571 _set:
572             g_list_free(xml);
573             xml = fm_xml_file_item_get_children(item);
574             ok = FALSE; /* check if Include is already there */
575             /* remove <Exclude><Filename>id</Filename></Exclude> */
576             for (it = xml; it; it = it->next)
577             {
578                 FmXmlFileTag tag = fm_xml_file_item_get_tag(it->data);
579                 if (tag == menuTag_Exclude)
580                 {
581                     /* get Filename tag */
582                     child = fm_xml_file_item_find_child(it->data, menuTag_Filename);
583                     if (child == NULL)
584                         continue;
585                     /* get contents of the tag */
586                     child = fm_xml_file_item_find_child(child, FM_XML_FILE_TEXT);
587                     if (child == NULL)
588                         continue;
589                     if (strcmp(fm_xml_file_item_get_data(child, NULL), id) != 0)
590                         continue;
591                     fm_xml_file_item_destroy(it->data);
592                     /* it was excluded before so removing exclude will add it */
593                     ok = TRUE;
594                 }
595                 else if (!ok && tag == menuTag_Include)
596                 {
597                     /* get Filename tag */
598                     child = fm_xml_file_item_find_child(it->data, menuTag_Filename);
599                     if (child == NULL)
600                         continue;
601                     /* get contents of the tag */
602                     child = fm_xml_file_item_find_child(child, FM_XML_FILE_TEXT);
603                     if (child == NULL)
604                         continue;
605                     if (strcmp(fm_xml_file_item_get_data(child, NULL), id) == 0)
606                         ok = TRUE; /* found! */
607                 }
608             }
609             if (!ok)
610             {
611                 /* add <Include><Filename>id</Filename></Include> */
612                 child = fm_xml_file_item_new(menuTag_Include);
613                 fm_xml_file_item_set_comment(child, "added by LibFM");
614                 fm_xml_file_item_append_child(item, child);
615                 item = fm_xml_file_item_new(menuTag_Filename);
616                 fm_xml_file_item_append_text(item, id, -1, FALSE);
617                 fm_xml_file_item_append_child(child, item);
618                 ok = TRUE;
619             }
620         }
621     }
622     if (ok)
623         ok = _save_new_menu_file(gf, data.menu, cancellable, error);
624     G_UNLOCK(menuTree);
625     g_object_unref(gf);
626     g_object_unref(data.menu);
627     g_free(data.file_path);
628     g_list_free(xml);
629     g_free(dir);
630     return ok;
631 }
632 
633 /* changes .menu XML file */
_remove_application(const char * path,GCancellable * cancellable,GError ** error)634 static gboolean _remove_application(const char *path, GCancellable *cancellable,
635                                     GError **error)
636 {
637     const char *id;
638     char *dir;
639     GList *xml = NULL, *it;
640     GFile *gf;
641     FmXmlFileItem *apps, *item, *child;
642     FmMenuMenuTree data;
643     gboolean ok = TRUE;
644 
645     id = strrchr(path, '/');
646     if (id == NULL)
647     {
648         dir = NULL;
649         id = path;
650     }
651     else
652     {
653         dir = g_strndup(path, id - path);
654         id++;
655     }
656     apps = _prepare_contents(&data, cancellable, error, &gf);
657     if (apps == NULL)
658     {
659         /* either failed to load contents or cancelled */
660         ok = FALSE;
661     }
662     else if (dir == NULL) /* removing from root, use apps as target */
663     {
664         item = apps;
665         goto _set;
666     }
667     else if ((xml = fm_xml_file_item_get_children(apps)) != NULL &&
668              (item = _find_in_children(xml, dir)) != NULL)
669     {
670         /* already found that path */
671         goto _set;
672     }
673     else
674     {
675         /* else create path and add content to it */
676         item = _create_path_in_tree(apps, dir);
677         if (item == NULL)
678         {
679             g_set_error(error, G_IO_ERROR, G_IO_ERROR_EXISTS,
680                         _("Cannot create XML definition for '%s'"), path);
681             ok = FALSE;
682         }
683         else
684         {
685 _set:
686             g_list_free(xml);
687             xml = fm_xml_file_item_get_children(item);
688             ok = FALSE; /* check if Include is already there */
689             /* remove <Include><Filename>id</Filename></Include> */
690             for (it = xml; it; it = it->next)
691             {
692                 FmXmlFileTag tag = fm_xml_file_item_get_tag(it->data);
693                 if (tag == menuTag_Include)
694                 {
695                     /* get Filename tag */
696                     child = fm_xml_file_item_find_child(it->data, menuTag_Filename);
697                     if (child == NULL)
698                         continue;
699                     /* get contents of the tag */
700                     child = fm_xml_file_item_find_child(child, FM_XML_FILE_TEXT);
701                     if (child == NULL)
702                         continue;
703                     if (strcmp(fm_xml_file_item_get_data(child, NULL), id) != 0)
704                         continue;
705                     fm_xml_file_item_destroy(it->data);
706                     /* it was included before so removing include will remove it */
707                     ok = TRUE;
708                 }
709                 else if (!ok && tag == menuTag_Exclude)
710                 {
711                     /* get Filename tag */
712                     child = fm_xml_file_item_find_child(it->data, menuTag_Filename);
713                     if (child == NULL)
714                         continue;
715                     /* get contents of the tag */
716                     child = fm_xml_file_item_find_child(child, FM_XML_FILE_TEXT);
717                     if (child == NULL)
718                         continue;
719                     if (strcmp(fm_xml_file_item_get_data(child, NULL), id) == 0)
720                         ok = TRUE; /* found! */
721                 }
722             }
723             if (!ok)
724             {
725                 /* add <Exclude><Filename>id</Filename></Exclude> */
726                 child = fm_xml_file_item_new(menuTag_Exclude);
727                 fm_xml_file_item_set_comment(child, "deleted by LibFM");
728                 fm_xml_file_item_append_child(item, child);
729                 item = fm_xml_file_item_new(menuTag_Filename);
730                 fm_xml_file_item_append_text(item, id, -1, FALSE);
731                 fm_xml_file_item_append_child(child, item);
732                 ok = TRUE;
733             }
734         }
735     }
736     if (ok)
737         ok = _save_new_menu_file(gf, data.menu, cancellable, error);
738     G_UNLOCK(menuTree);
739     g_object_unref(gf);
740     g_object_unref(data.menu);
741     g_free(data.file_path);
742     g_list_free(xml);
743     g_free(dir);
744     return ok;
745 }
746 
747 
748 /* ---- FmMenuVFile class ---- */
749 #define FM_TYPE_MENU_VFILE             (fm_vfs_menu_file_get_type())
750 #define FM_MENU_VFILE(o)               (G_TYPE_CHECK_INSTANCE_CAST((o), \
751                                         FM_TYPE_MENU_VFILE, FmMenuVFile))
752 
753 typedef struct _FmMenuVFile             FmMenuVFile;
754 typedef struct _FmMenuVFileClass        FmMenuVFileClass;
755 
756 static GType fm_vfs_menu_file_get_type  (void);
757 
758 struct _FmMenuVFile
759 {
760     GObject parent_object;
761 
762     char *path;
763 };
764 
765 struct _FmMenuVFileClass
766 {
767   GObjectClass parent_class;
768 };
769 
770 static void fm_menu_g_file_init(GFileIface *iface);
771 static void fm_menu_fm_file_init(FmFileInterface *iface);
772 
G_DEFINE_TYPE_WITH_CODE(FmMenuVFile,fm_vfs_menu_file,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_FILE,fm_menu_g_file_init)G_IMPLEMENT_INTERFACE (FM_TYPE_FILE,fm_menu_fm_file_init))773 G_DEFINE_TYPE_WITH_CODE(FmMenuVFile, fm_vfs_menu_file, G_TYPE_OBJECT,
774                         G_IMPLEMENT_INTERFACE(G_TYPE_FILE, fm_menu_g_file_init)
775                         G_IMPLEMENT_INTERFACE(FM_TYPE_FILE, fm_menu_fm_file_init))
776 
777 static void fm_vfs_menu_file_finalize(GObject *object)
778 {
779     FmMenuVFile *item = FM_MENU_VFILE(object);
780 
781     g_free(item->path);
782 
783     G_OBJECT_CLASS(fm_vfs_menu_file_parent_class)->finalize(object);
784 }
785 
fm_vfs_menu_file_class_init(FmMenuVFileClass * klass)786 static void fm_vfs_menu_file_class_init(FmMenuVFileClass *klass)
787 {
788     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
789 
790     gobject_class->finalize = fm_vfs_menu_file_finalize;
791 }
792 
fm_vfs_menu_file_init(FmMenuVFile * item)793 static void fm_vfs_menu_file_init(FmMenuVFile *item)
794 {
795     /* nothing */
796 }
797 
_fm_menu_vfile_new(void)798 static FmMenuVFile *_fm_menu_vfile_new(void)
799 {
800     return (FmMenuVFile*)g_object_new(FM_TYPE_MENU_VFILE, NULL);
801 }
802 
803 
804 /* ---- menu enumerator class ---- */
805 #define FM_TYPE_VFS_MENU_ENUMERATOR        (fm_vfs_menu_enumerator_get_type())
806 #define FM_VFS_MENU_ENUMERATOR(o)          (G_TYPE_CHECK_INSTANCE_CAST((o), \
807                             FM_TYPE_VFS_MENU_ENUMERATOR, FmVfsMenuEnumerator))
808 
809 typedef struct _FmVfsMenuEnumerator         FmVfsMenuEnumerator;
810 typedef struct _FmVfsMenuEnumeratorClass    FmVfsMenuEnumeratorClass;
811 
812 struct _FmVfsMenuEnumerator
813 {
814     GFileEnumerator parent;
815 
816     MenuCache *mc;
817     GSList *child;
818     guint32 de_flag;
819 };
820 
821 struct _FmVfsMenuEnumeratorClass
822 {
823     GFileEnumeratorClass parent_class;
824 };
825 
826 static GType fm_vfs_menu_enumerator_get_type   (void);
827 
G_DEFINE_TYPE(FmVfsMenuEnumerator,fm_vfs_menu_enumerator,G_TYPE_FILE_ENUMERATOR)828 G_DEFINE_TYPE(FmVfsMenuEnumerator, fm_vfs_menu_enumerator, G_TYPE_FILE_ENUMERATOR)
829 
830 static void _fm_vfs_menu_enumerator_dispose(GObject *object)
831 {
832     FmVfsMenuEnumerator *enu = FM_VFS_MENU_ENUMERATOR(object);
833 
834     if(enu->mc)
835     {
836         menu_cache_unref(enu->mc);
837         enu->mc = NULL;
838     }
839 
840     G_OBJECT_CLASS(fm_vfs_menu_enumerator_parent_class)->dispose(object);
841 }
842 
843 GIcon* _fm_icon_from_name(const char* name); /* defined in core/iconinfo.cpp */
844 
_g_file_info_from_menu_cache_item(MenuCacheItem * item,guint32 de_flag)845 static GFileInfo *_g_file_info_from_menu_cache_item(MenuCacheItem *item,
846                                                     /* GFileAttributeMatcher *attribute_matcher, */
847                                                     guint32 de_flag)
848 {
849     GFileInfo *fileinfo = g_file_info_new();
850     const char *icon_name;
851     GIcon* icon;
852 
853     /* FIXME: use g_uri_escape_string() for item name */
854     g_file_info_set_name(fileinfo, menu_cache_item_get_id(item));
855     if(menu_cache_item_get_name(item) != NULL)
856         g_file_info_set_display_name(fileinfo, menu_cache_item_get_name(item));
857 
858     /* the setup below was in fm_file_info_set_from_menu_cache_item()
859        so this setup makes latter API deprecated */
860     icon_name = menu_cache_item_get_icon(item);
861     if(icon_name)
862     {
863         icon = _fm_icon_from_name(icon_name);
864         if(G_LIKELY(icon))
865         {
866             g_file_info_set_icon(fileinfo, icon);
867             g_object_unref(icon);
868         }
869     }
870     if(menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
871     {
872         g_file_info_set_file_type(fileinfo, G_FILE_TYPE_DIRECTORY);
873 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
874         g_file_info_set_is_hidden(fileinfo,
875                                   !menu_cache_dir_is_visible(MENU_CACHE_DIR(item)));
876 #else
877         g_file_info_set_is_hidden(fileinfo, FALSE);
878 #endif
879     }
880     else /* MENU_CACHE_TYPE_APP */
881     {
882         char *path = menu_cache_item_get_file_path(item);
883         g_file_info_set_file_type(fileinfo, G_FILE_TYPE_SHORTCUT);
884         g_file_info_set_attribute_string(fileinfo,
885                                          G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
886                                          path);
887         g_free(path);
888         g_file_info_set_content_type(fileinfo, "application/x-desktop");
889         g_file_info_set_is_hidden(fileinfo,
890                                   !menu_cache_app_get_is_visible(MENU_CACHE_APP(item),
891                                                                  de_flag));
892     }
893 // FIXME: use attribute_matcher and set G_FILE_ATTRIBUTE_ACCESS_CAN_{WRITE,READ,EXECUTE,DELETE}
894     g_file_info_set_attribute_string(fileinfo, G_FILE_ATTRIBUTE_ID_FILESYSTEM,
895                                      "menu-Applications");
896     g_file_info_set_attribute_boolean(fileinfo,
897                                       G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, TRUE);
898     g_file_info_set_attribute_boolean(fileinfo,
899                                       G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
900     return fileinfo;
901 }
902 
903 typedef struct
904 {
905     union
906     {
907         FmVfsMenuEnumerator *enumerator;
908         const char *path_str;
909     };
910     union
911     {
912         FmMenuVFile *destination;
913 //        const char *attributes;
914         const char *display_name;
915         GFileInfo *info;
916     };
917 //    GFileQueryInfoFlags flags;
918     union
919     {
920         GCancellable *cancellable;
921         GFile *file;
922     };
923     GError **error;
924     gpointer result;
925 } FmVfsMenuMainThreadData;
926 
_fm_vfs_menu_enumerator_next_file_real(gpointer data)927 static gboolean _fm_vfs_menu_enumerator_next_file_real(gpointer data)
928 {
929     FmVfsMenuMainThreadData *init = data;
930     FmVfsMenuEnumerator *enu = init->enumerator;
931     GSList *child = enu->child;
932     MenuCacheItem *item;
933 
934     init->result = NULL;
935 
936     if(child == NULL)
937         goto done;
938 
939     for(; child; child = child->next)
940     {
941         if(g_cancellable_set_error_if_cancelled(init->cancellable, init->error))
942             break;
943         item = MENU_CACHE_ITEM(child->data);
944         if(!item || menu_cache_item_get_type(item) == MENU_CACHE_TYPE_SEP ||
945            menu_cache_item_get_type(item) == MENU_CACHE_TYPE_NONE)
946             continue;
947 #if 0
948         /* also hide menu items which should be hidden in current DE. */
949         if(menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP
950            && !menu_cache_app_get_is_visible(MENU_CACHE_APP(item), enu->de_flag))
951             continue;
952 #endif
953 
954         init->result = _g_file_info_from_menu_cache_item(item, enu->de_flag);
955         child = child->next;
956         break;
957     }
958 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
959     while(enu->child != child) /* free skipped/used elements */
960     {
961         GSList *ch = enu->child;
962         enu->child = ch->next;
963         menu_cache_item_unref(ch->data);
964         g_slist_free_1(ch);
965     }
966 #else
967     enu->child = child;
968 #endif
969 
970 done:
971     return FALSE;
972 }
973 
_fm_vfs_menu_enumerator_next_file(GFileEnumerator * enumerator,GCancellable * cancellable,GError ** error)974 static GFileInfo *_fm_vfs_menu_enumerator_next_file(GFileEnumerator *enumerator,
975                                                     GCancellable *cancellable,
976                                                     GError **error)
977 {
978     FmVfsMenuMainThreadData init;
979 
980     init.enumerator = FM_VFS_MENU_ENUMERATOR(enumerator);
981     init.cancellable = cancellable;
982     init.error = error;
983     RUN_WITH_MENU_CACHE(_fm_vfs_menu_enumerator_next_file_real, &init);
984     return init.result;
985 }
986 
_fm_vfs_menu_enumerator_close(GFileEnumerator * enumerator,GCancellable * cancellable,GError ** error)987 static gboolean _fm_vfs_menu_enumerator_close(GFileEnumerator *enumerator,
988                                               GCancellable *cancellable,
989                                               GError **error)
990 {
991     FmVfsMenuEnumerator *enu = FM_VFS_MENU_ENUMERATOR(enumerator);
992 
993     if(enu->mc)
994     {
995         menu_cache_unref(enu->mc);
996         enu->mc = NULL;
997 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
998         g_slist_free_full(enu->child, (GDestroyNotify)menu_cache_item_unref);
999 #endif
1000         enu->child = NULL;
1001     }
1002     return TRUE;
1003 }
1004 
fm_vfs_menu_enumerator_class_init(FmVfsMenuEnumeratorClass * klass)1005 static void fm_vfs_menu_enumerator_class_init(FmVfsMenuEnumeratorClass *klass)
1006 {
1007   GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1008   GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS(klass);
1009 
1010   gobject_class->dispose = _fm_vfs_menu_enumerator_dispose;
1011 
1012   enumerator_class->next_file = _fm_vfs_menu_enumerator_next_file;
1013   enumerator_class->close_fn = _fm_vfs_menu_enumerator_close;
1014 }
1015 
fm_vfs_menu_enumerator_init(FmVfsMenuEnumerator * enumerator)1016 static void fm_vfs_menu_enumerator_init(FmVfsMenuEnumerator *enumerator)
1017 {
1018     /* nothing */
1019 }
1020 
_vfile_path_to_menu_cache_item(MenuCache * mc,const char * path)1021 static MenuCacheItem *_vfile_path_to_menu_cache_item(MenuCache* mc, const char *path)
1022 {
1023     MenuCacheItem *dir;
1024     char *unescaped, *tmp = NULL;
1025 
1026     unescaped = g_uri_unescape_string(path, NULL);
1027 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1028     dir = MENU_CACHE_ITEM(menu_cache_dup_root_dir(mc));
1029 #else
1030     dir = MENU_CACHE_ITEM(menu_cache_get_root_dir(mc));
1031 #endif
1032     if(dir)
1033     {
1034 #if !MENU_CACHE_CHECK_VERSION(0, 5, 0)
1035         char *id;
1036 #if !MENU_CACHE_CHECK_VERSION(0, 4, 0)
1037         GSList *child;
1038 #endif
1039 #endif
1040         tmp = g_strconcat("/", menu_cache_item_get_id(dir), "/", unescaped, NULL);
1041 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1042         menu_cache_item_unref(dir);
1043         dir = menu_cache_item_from_path(mc, tmp);
1044 #else
1045         /* access not dir is a bit tricky */
1046         id = strrchr(tmp, '/');
1047         *id++ = '\0';
1048         dir = MENU_CACHE_ITEM(menu_cache_get_dir_from_path(mc, tmp));
1049         child = menu_cache_dir_get_children(MENU_CACHE_DIR(dir));
1050         dir = NULL;
1051         while (child)
1052         {
1053             if (g_strcmp0(id, menu_cache_item_get_id(child->data)) == 0)
1054             {
1055                 dir = child->data;
1056                 break;
1057             }
1058             child = child->next;
1059         }
1060 #endif
1061 #if !MENU_CACHE_CHECK_VERSION(0, 5, 0)
1062         /* The menu-cache is buggy and returns parent for invalid path
1063            instead of failure so we check what we got here.
1064            Unfortunately we cannot detect if requested name is the same
1065            as its parent and menu-cache returned the parent. */
1066         id = strrchr(unescaped, '/');
1067         if(id)
1068             id++;
1069         else
1070             id = unescaped;
1071         if(dir != NULL && strcmp(id, menu_cache_item_get_id(dir)) != 0)
1072             dir = NULL;
1073 #endif
1074     }
1075     g_free(unescaped);
1076     g_free(tmp);
1077     /* NOTE: returned value is referenced for >= 0.4.0 only */
1078     return dir;
1079 }
1080 
_get_menu_cache(GError ** error)1081 static MenuCache *_get_menu_cache(GError **error)
1082 {
1083     MenuCache *mc;
1084     static gboolean environment_tested = FALSE;
1085     static gboolean requires_prefix = FALSE;
1086 
1087     /* do it in compatibility with lxpanel */
1088     if(!environment_tested)
1089     {
1090         requires_prefix = (g_getenv("XDG_MENU_PREFIX") == NULL);
1091         environment_tested = TRUE;
1092     }
1093 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
1094     mc = menu_cache_lookup_sync(requires_prefix ? "lxde-applications.menu+hidden" : "applications.menu+hidden");
1095 #else
1096     mc = menu_cache_lookup_sync(requires_prefix ? "lxde-applications.menu" : "applications.menu");
1097 #endif
1098     /* FIXME: may be it is reasonable to set XDG_MENU_PREFIX ? */
1099 
1100     if(mc == NULL) /* initialization failed */
1101         g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED,
1102                             _("Menu cache error"));
1103     return mc;
1104 }
1105 
_fm_vfs_menu_enumerator_new_real(gpointer data)1106 static gboolean _fm_vfs_menu_enumerator_new_real(gpointer data)
1107 {
1108     FmVfsMenuMainThreadData *init = data;
1109     FmVfsMenuEnumerator *enumerator;
1110     MenuCache* mc;
1111     const char *de_name;
1112     MenuCacheItem *dir;
1113 
1114     mc = _get_menu_cache(init->error);
1115 
1116     if(mc == NULL) /* initialization failed */
1117         return FALSE;
1118 
1119     enumerator = g_object_new(FM_TYPE_VFS_MENU_ENUMERATOR, "container",
1120                               init->file, NULL);
1121     enumerator->mc = mc;
1122     de_name = g_getenv("XDG_CURRENT_DESKTOP");
1123 
1124     if(de_name)
1125         enumerator->de_flag = menu_cache_get_desktop_env_flag(mc, de_name);
1126     else
1127         enumerator->de_flag = (guint32)-1;
1128 
1129     /* the menu should be loaded now */
1130     if(init->path_str)
1131         dir = _vfile_path_to_menu_cache_item(mc, init->path_str);
1132     else
1133 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1134         dir = MENU_CACHE_ITEM(menu_cache_dup_root_dir(mc));
1135 #else
1136         dir = MENU_CACHE_ITEM(menu_cache_get_root_dir(mc));
1137 #endif
1138     if(dir)
1139     {
1140 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1141         enumerator->child = menu_cache_dir_list_children(MENU_CACHE_DIR(dir));
1142         menu_cache_item_unref(dir);
1143 #else
1144         enumerator->child = menu_cache_dir_get_children(MENU_CACHE_DIR(dir));
1145 #endif
1146     }
1147     /* FIXME: do something with attributes and flags */
1148 
1149     init->result = enumerator;
1150     return FALSE;
1151 }
1152 
_fm_vfs_menu_enumerator_new(GFile * file,const char * path_str,const char * attributes,GFileQueryInfoFlags flags,GError ** error)1153 static GFileEnumerator *_fm_vfs_menu_enumerator_new(GFile *file,
1154                                                     const char *path_str,
1155                                                     const char *attributes,
1156                                                     GFileQueryInfoFlags flags,
1157                                                     GError **error)
1158 {
1159     FmVfsMenuMainThreadData enu;
1160 
1161     enu.path_str = path_str;
1162 //    enu.attributes = attributes;
1163 //    enu.flags = flags;
1164     enu.file = file;
1165     enu.error = error;
1166     enu.result = NULL;
1167     RUN_WITH_MENU_CACHE(_fm_vfs_menu_enumerator_new_real, &enu);
1168     return enu.result;
1169 }
1170 
1171 
1172 /* ---- GFile implementation ---- */
1173 static GFileAttributeInfoList *_fm_vfs_menu_settable_attributes = NULL;
1174 
1175 #define ERROR_UNSUPPORTED(err) g_set_error_literal(err, G_IO_ERROR, \
1176                         G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"))
1177 
_fm_vfs_menu_dup(GFile * file)1178 static GFile *_fm_vfs_menu_dup(GFile *file)
1179 {
1180     FmMenuVFile *item, *new_item;
1181 
1182     item = FM_MENU_VFILE(file);
1183     new_item = _fm_menu_vfile_new();
1184     if(item->path)
1185         new_item->path = g_strdup(item->path);
1186     return (GFile*)new_item;
1187 }
1188 
_fm_vfs_menu_hash(GFile * file)1189 static guint _fm_vfs_menu_hash(GFile *file)
1190 {
1191     return g_str_hash(FM_MENU_VFILE(file)->path ? FM_MENU_VFILE(file)->path : "/");
1192 }
1193 
_fm_vfs_menu_equal(GFile * file1,GFile * file2)1194 static gboolean _fm_vfs_menu_equal(GFile *file1, GFile *file2)
1195 {
1196     char *path1 = FM_MENU_VFILE(file1)->path;
1197     char *path2 = FM_MENU_VFILE(file2)->path;
1198 
1199     return g_strcmp0(path1, path2) == 0;
1200 }
1201 
_fm_vfs_menu_is_native(GFile * file)1202 static gboolean _fm_vfs_menu_is_native(GFile *file)
1203 {
1204     return FALSE;
1205 }
1206 
_fm_vfs_menu_has_uri_scheme(GFile * file,const char * uri_scheme)1207 static gboolean _fm_vfs_menu_has_uri_scheme(GFile *file, const char *uri_scheme)
1208 {
1209     return g_ascii_strcasecmp(uri_scheme, "menu") == 0;
1210 }
1211 
_fm_vfs_menu_get_uri_scheme(GFile * file)1212 static char *_fm_vfs_menu_get_uri_scheme(GFile *file)
1213 {
1214     return g_strdup("menu");
1215 }
1216 
_fm_vfs_menu_get_basename(GFile * file)1217 static char *_fm_vfs_menu_get_basename(GFile *file)
1218 {
1219     /* g_debug("_fm_vfs_menu_get_basename %s", FM_MENU_VFILE(file)->path); */
1220     if(FM_MENU_VFILE(file)->path == NULL)
1221         return g_strdup("/");
1222     return g_path_get_basename(FM_MENU_VFILE(file)->path);
1223 }
1224 
_fm_vfs_menu_get_path(GFile * file)1225 static char *_fm_vfs_menu_get_path(GFile *file)
1226 {
1227     return NULL;
1228 }
1229 
_fm_vfs_menu_get_uri(GFile * file)1230 static char *_fm_vfs_menu_get_uri(GFile *file)
1231 {
1232     return g_strconcat("menu://applications/", FM_MENU_VFILE(file)->path, NULL);
1233 }
1234 
_fm_vfs_menu_get_parse_name(GFile * file)1235 static char *_fm_vfs_menu_get_parse_name(GFile *file)
1236 {
1237     char *unescaped, *path;
1238 
1239     /* g_debug("_fm_vfs_menu_get_parse_name %s", FM_MENU_VFILE(file)->path); */
1240     unescaped = g_uri_unescape_string(FM_MENU_VFILE(file)->path, NULL);
1241     path = g_strconcat("menu://applications/", unescaped, NULL);
1242     g_free(unescaped);
1243     return path;
1244 }
1245 
_fm_vfs_menu_get_parent(GFile * file)1246 static GFile *_fm_vfs_menu_get_parent(GFile *file)
1247 {
1248     char *path = FM_MENU_VFILE(file)->path;
1249     char *dirname;
1250     GFile *parent;
1251 
1252     /* g_debug("_fm_vfs_menu_get_parent %s", path); */
1253     if(path)
1254     {
1255         dirname = g_path_get_dirname(path);
1256         if(strcmp(dirname, ".") == 0)
1257         {
1258             g_free(dirname);
1259             path = NULL;
1260         }
1261         else
1262             path = dirname;
1263     }
1264     parent = _fm_vfs_menu_new_for_uri(path);
1265     if(path)
1266         g_free(path);
1267     return parent;
1268 }
1269 
1270 /* this function is taken from GLocalFile implementation */
match_prefix(const char * path,const char * prefix)1271 static const char *match_prefix (const char *path, const char *prefix)
1272 {
1273   int prefix_len;
1274 
1275   prefix_len = strlen (prefix);
1276   if (strncmp (path, prefix, prefix_len) != 0)
1277     return NULL;
1278 
1279   if (prefix_len > 0 && (prefix[prefix_len-1]) == '/')
1280     prefix_len--;
1281 
1282   return path + prefix_len;
1283 }
1284 
_fm_vfs_menu_prefix_matches(GFile * prefix,GFile * file)1285 static gboolean _fm_vfs_menu_prefix_matches(GFile *prefix, GFile *file)
1286 {
1287     const char *path = FM_MENU_VFILE(file)->path;
1288     const char *pp = FM_MENU_VFILE(prefix)->path;
1289     const char *remainder;
1290 
1291     if(pp == NULL)
1292         return TRUE;
1293     if(path == NULL)
1294         return FALSE;
1295     remainder = match_prefix(path, pp);
1296     if(remainder != NULL && *remainder == '/')
1297         return TRUE;
1298     return FALSE;
1299 }
1300 
_fm_vfs_menu_get_relative_path(GFile * parent,GFile * descendant)1301 static char *_fm_vfs_menu_get_relative_path(GFile *parent, GFile *descendant)
1302 {
1303     const char *path = FM_MENU_VFILE(descendant)->path;
1304     const char *pp = FM_MENU_VFILE(parent)->path;
1305     const char *remainder;
1306 
1307     if(pp == NULL)
1308         return g_strdup(path);
1309     if(path == NULL)
1310         return NULL;
1311     remainder = match_prefix(path, pp);
1312     if(remainder != NULL && *remainder == '/')
1313         return g_uri_unescape_string(&remainder[1], NULL);
1314     return NULL;
1315 }
1316 
_fm_vfs_menu_resolve_relative_path(GFile * file,const char * relative_path)1317 static GFile *_fm_vfs_menu_resolve_relative_path(GFile *file, const char *relative_path)
1318 {
1319     const char *path = FM_MENU_VFILE(file)->path;
1320     FmMenuVFile *new_item = _fm_menu_vfile_new();
1321 
1322     /* g_debug("_fm_vfs_menu_resolve_relative_path %s %s", path, relative_path); */
1323     /* FIXME: handle if relative_path is invalid */
1324     if(relative_path == NULL || *relative_path == '\0')
1325         new_item->path = g_strdup(path);
1326     else if(path == NULL)
1327         new_item->path = g_strdup(relative_path);
1328     else
1329     {
1330         /* relative_path is the most probably unescaped string (at least GFVS
1331            works such way) so we have to escape invalid chars here. */
1332         char *escaped = g_uri_escape_string(relative_path,
1333                                             G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
1334                                             TRUE);
1335         new_item->path = g_strconcat(path, "/", relative_path, NULL);
1336         g_free(escaped);
1337     }
1338     return (GFile*)new_item;
1339 }
1340 
_fm_vfs_menu_get_child_for_display_name_real(gpointer data)1341 static gboolean _fm_vfs_menu_get_child_for_display_name_real(gpointer data)
1342 {
1343     FmVfsMenuMainThreadData *init = data;
1344     MenuCache *mc;
1345     MenuCacheItem *dir;
1346     gboolean is_invalid = FALSE;
1347 
1348     init->result = NULL;
1349     mc = _get_menu_cache(init->error);
1350     if(mc == NULL)
1351         goto _mc_failed;
1352 
1353     if(init->path_str)
1354     {
1355         dir = _vfile_path_to_menu_cache_item(mc, init->path_str);
1356         if(dir == NULL || menu_cache_item_get_type(dir) != MENU_CACHE_TYPE_DIR)
1357             is_invalid = TRUE;
1358     }
1359     else
1360 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1361         dir = MENU_CACHE_ITEM(menu_cache_dup_root_dir(mc));
1362 #else
1363         dir = MENU_CACHE_ITEM(menu_cache_get_root_dir(mc));
1364 #endif
1365     if(is_invalid)
1366         g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1367                             _("Invalid menu directory"));
1368     else if(dir)
1369     {
1370 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
1371         MenuCacheItem *item = menu_cache_find_child_by_name(MENU_CACHE_DIR(dir),
1372                                                             init->display_name);
1373         g_debug("searched for child '%s' found '%s'", init->display_name,
1374                 item ? menu_cache_item_get_id(item) : "(nil)");
1375         if (item == NULL)
1376             init->result = _fm_vfs_menu_resolve_relative_path(init->file,
1377                                                               init->display_name);
1378         else
1379         {
1380             init->result = _fm_vfs_menu_resolve_relative_path(init->file,
1381                                                 menu_cache_item_get_id(item));
1382             menu_cache_item_unref(item);
1383         }
1384 #else /* < 0.5.0 */
1385         GSList *l;
1386 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1387         GSList *children = menu_cache_dir_list_children(MENU_CACHE_DIR(dir));
1388 #else
1389         GSList *children = menu_cache_dir_get_children(MENU_CACHE_DIR(dir));
1390 #endif
1391         for (l = children; l; l = l->next)
1392             if (g_strcmp0(init->display_name, menu_cache_item_get_name(l->data)) == 0)
1393                 break;
1394         if (l == NULL) /* not found */
1395             init->result = _fm_vfs_menu_resolve_relative_path(init->file,
1396                                                               init->display_name);
1397         else
1398             init->result = _fm_vfs_menu_resolve_relative_path(init->file,
1399                                                 menu_cache_item_get_id(l->data));
1400 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1401         g_slist_free_full(children, (GDestroyNotify)menu_cache_item_unref);
1402 #endif
1403 #endif /* < 0.5.0 */
1404     }
1405     else /* menu_cache_get_root_dir failed */
1406         g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_FAILED,
1407                             _("Menu cache error"));
1408 
1409 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1410     if(dir)
1411         menu_cache_item_unref(dir);
1412 #endif
1413     menu_cache_unref(mc);
1414 
1415 _mc_failed:
1416     return FALSE;
1417 }
1418 
_fm_vfs_menu_get_child_for_display_name(GFile * file,const char * display_name,GError ** error)1419 static GFile *_fm_vfs_menu_get_child_for_display_name(GFile *file,
1420                                                       const char *display_name,
1421                                                       GError **error)
1422 {
1423     FmMenuVFile *item = FM_MENU_VFILE(file);
1424     FmVfsMenuMainThreadData enu;
1425 
1426     /* g_debug("_fm_vfs_menu_get_child_for_display_name: '%s' '%s'", item->path, display_name); */
1427     if (display_name == NULL || *display_name == '\0')
1428     {
1429         g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED,
1430                             _("Menu item name cannot be empty"));
1431         return NULL;
1432     }
1433     /* NOTE: this violates GFile requirement that this API should not do I/O but
1434        there is no way to do this without dirty tricks which may lead to failure */
1435     enu.path_str = item->path;
1436     enu.error = error;
1437     enu.display_name = display_name;
1438     enu.file = file;
1439     RUN_WITH_MENU_CACHE(_fm_vfs_menu_get_child_for_display_name_real, &enu);
1440     return enu.result;
1441 }
1442 
_fm_vfs_menu_enumerate_children(GFile * file,const char * attributes,GFileQueryInfoFlags flags,GCancellable * cancellable,GError ** error)1443 static GFileEnumerator *_fm_vfs_menu_enumerate_children(GFile *file,
1444                                                         const char *attributes,
1445                                                         GFileQueryInfoFlags flags,
1446                                                         GCancellable *cancellable,
1447                                                         GError **error)
1448 {
1449     const char *path = FM_MENU_VFILE(file)->path;
1450 
1451     return _fm_vfs_menu_enumerator_new(file, path, attributes, flags, error);
1452 }
1453 
_fm_vfs_menu_query_info_real(gpointer data)1454 static gboolean _fm_vfs_menu_query_info_real(gpointer data)
1455 {
1456     FmVfsMenuMainThreadData *init = data;
1457     MenuCache *mc;
1458     MenuCacheItem *dir;
1459     gboolean is_invalid = FALSE;
1460 
1461     init->result = NULL;
1462     mc = _get_menu_cache(init->error);
1463     if(mc == NULL)
1464         goto _mc_failed;
1465 
1466     if(init->path_str)
1467     {
1468         dir = _vfile_path_to_menu_cache_item(mc, init->path_str);
1469         if(dir == NULL)
1470             is_invalid = TRUE;
1471     }
1472     else
1473 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1474         dir = MENU_CACHE_ITEM(menu_cache_dup_root_dir(mc));
1475 #else
1476         dir = MENU_CACHE_ITEM(menu_cache_get_root_dir(mc));
1477 #endif
1478     if(is_invalid)
1479         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1480                     _("Invalid menu directory '%s'"), init->path_str);
1481     else if(dir)
1482     {
1483         const char *de_name = g_getenv("XDG_CURRENT_DESKTOP");
1484 
1485         if(de_name)
1486             init->result = _g_file_info_from_menu_cache_item(dir,
1487                                 menu_cache_get_desktop_env_flag(mc, de_name));
1488         else
1489             init->result = _g_file_info_from_menu_cache_item(dir, (guint32)-1);
1490     }
1491     else /* menu_cache_get_root_dir failed */
1492         g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_FAILED,
1493                             _("Menu cache error"));
1494 
1495 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1496     if(dir)
1497         menu_cache_item_unref(dir);
1498 #endif
1499     menu_cache_unref(mc);
1500 
1501 _mc_failed:
1502     return FALSE;
1503 }
1504 
_fm_vfs_menu_query_info(GFile * file,const char * attributes,GFileQueryInfoFlags flags,GCancellable * cancellable,GError ** error)1505 static GFileInfo *_fm_vfs_menu_query_info(GFile *file,
1506                                           const char *attributes,
1507                                           GFileQueryInfoFlags flags,
1508                                           GCancellable *cancellable,
1509                                           GError **error)
1510 {
1511     FmMenuVFile *item = FM_MENU_VFILE(file);
1512     GFileInfo *info;
1513     GFileAttributeMatcher *matcher;
1514     char *basename, *id;
1515     FmVfsMenuMainThreadData enu;
1516 
1517     matcher = g_file_attribute_matcher_new(attributes);
1518 
1519     if(item->path == NULL) /* menu root */
1520     {
1521         info = g_file_info_new();
1522         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_NAME))
1523             g_file_info_set_name(info, "/");
1524         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ID_FILESYSTEM))
1525             g_file_info_set_attribute_string(info, G_FILE_ATTRIBUTE_ID_FILESYSTEM,
1526                                              "menu-Applications");
1527         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_TYPE))
1528             g_file_info_set_file_type(info, G_FILE_TYPE_DIRECTORY);
1529         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_ICON))
1530         {
1531             GIcon *icon = g_themed_icon_new("system-software-install");
1532             g_file_info_set_icon(info, icon);
1533             g_object_unref(icon);
1534         }
1535         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN))
1536             g_file_info_set_is_hidden(info, FALSE);
1537         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
1538             g_file_info_set_display_name(info, _("Applications"));
1539         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME))
1540             g_file_info_set_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE);
1541         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH))
1542             g_file_info_set_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
1543 #if 0
1544         /* FIXME: is this ever needed? */
1545         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
1546             g_file_info_set_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE);
1547         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
1548             g_file_info_set_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
1549         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
1550             g_file_info_set_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, FALSE);
1551         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE))
1552             g_file_info_set_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
1553 #endif
1554     }
1555     else if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_TYPE) ||
1556             g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_ICON) ||
1557             g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI) ||
1558             g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE) ||
1559             g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) ||
1560             g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
1561     {
1562         /* retrieve matching attributes from menu-cache */
1563         enu.path_str = item->path;
1564 //        enu.attributes = attributes;
1565 //        enu.flags = flags;
1566         enu.cancellable = cancellable;
1567         enu.error = error;
1568         RUN_WITH_MENU_CACHE(_fm_vfs_menu_query_info_real, &enu);
1569         info = enu.result;
1570     }
1571     else
1572     {
1573         info = g_file_info_new();
1574         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_STANDARD_NAME))
1575         {
1576             basename = g_path_get_basename(item->path);
1577             id = g_uri_unescape_string(basename, NULL);
1578             g_free(basename);
1579             g_file_info_set_name(info, id);
1580             g_free(id);
1581         }
1582         if(g_file_attribute_matcher_matches(matcher, G_FILE_ATTRIBUTE_ID_FILESYSTEM))
1583             g_file_info_set_attribute_string(info, G_FILE_ATTRIBUTE_ID_FILESYSTEM,
1584                                              "menu-Applications");
1585     }
1586 
1587     g_file_attribute_matcher_unref(matcher);
1588 
1589     return info;
1590 }
1591 
_fm_vfs_menu_query_filesystem_info(GFile * file,const char * attributes,GCancellable * cancellable,GError ** error)1592 static GFileInfo *_fm_vfs_menu_query_filesystem_info(GFile *file,
1593                                                      const char *attributes,
1594                                                      GCancellable *cancellable,
1595                                                      GError **error)
1596 {
1597     ERROR_UNSUPPORTED(error);
1598     return NULL;
1599 }
1600 
_fm_vfs_menu_find_enclosing_mount(GFile * file,GCancellable * cancellable,GError ** error)1601 static GMount *_fm_vfs_menu_find_enclosing_mount(GFile *file,
1602                                                  GCancellable *cancellable,
1603                                                  GError **error)
1604 {
1605     ERROR_UNSUPPORTED(error);
1606     return NULL;
1607 }
1608 
_fm_vfs_menu_set_display_name_real(gpointer data)1609 static gboolean _fm_vfs_menu_set_display_name_real(gpointer data)
1610 {
1611     FmVfsMenuMainThreadData *init = data;
1612     MenuCache *mc;
1613     MenuCacheItem *dir;
1614     gboolean ok = FALSE;
1615 
1616     mc = _get_menu_cache(init->error);
1617     if(mc == NULL)
1618         goto _mc_failed;
1619 
1620     dir = _vfile_path_to_menu_cache_item(mc, init->path_str);
1621     if(dir == NULL)
1622         g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1623                             _("Invalid menu item"));
1624     else if (menu_cache_item_get_file_basename(dir) == NULL ||
1625              menu_cache_item_get_file_dirname(dir) == NULL)
1626         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1627                     _("The menu item '%s' doesn't have appropriate entry file"),
1628                     menu_cache_item_get_id(dir));
1629     else if (!g_cancellable_set_error_if_cancelled(init->cancellable, init->error))
1630     {
1631         char *path = menu_cache_item_get_file_path(dir);
1632         GKeyFile *kf = g_key_file_new();
1633 
1634         ok = g_key_file_load_from_file(kf, path, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
1635                                        init->error);
1636         g_free(path);
1637         if (ok)
1638         {
1639             /* get locale name */
1640             const gchar * const *langs = g_get_language_names();
1641             char *contents;
1642             gsize length;
1643 
1644             if (strcmp(langs[0], "C") != 0)
1645             {
1646                 char *lang;
1647                 /* remove encoding from locale name */
1648                 char *sep = strchr(langs[0], '.');
1649 
1650                 if (sep)
1651                     lang = g_strndup(langs[0], sep - langs[0]);
1652                 else
1653                     lang = g_strdup(langs[0]);
1654                 g_key_file_set_locale_string(kf, G_KEY_FILE_DESKTOP_GROUP,
1655                                              G_KEY_FILE_DESKTOP_KEY_NAME, lang,
1656                                              init->display_name);
1657                 g_free(lang);
1658             }
1659             else
1660                 g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP,
1661                                       G_KEY_FILE_DESKTOP_KEY_NAME, init->display_name);
1662             contents = g_key_file_to_data(kf, &length, init->error);
1663             if (contents == NULL)
1664                 ok = FALSE;
1665             else
1666             {
1667                 path = g_build_filename(g_get_user_data_dir(),
1668                                         (menu_cache_item_get_type(dir) == MENU_CACHE_TYPE_DIR) ? "desktop-directories" : "applications",
1669                                         menu_cache_item_get_file_basename(dir), NULL);
1670                 ok = g_file_set_contents(path, contents, length, init->error);
1671                 /* FIXME: handle case if directory doesn't exist */
1672                 g_free(contents);
1673                 g_free(path);
1674             }
1675         }
1676         g_key_file_free(kf);
1677     }
1678 
1679 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1680     if(dir)
1681         menu_cache_item_unref(dir);
1682 #endif
1683     menu_cache_unref(mc);
1684 
1685 _mc_failed:
1686     return ok;
1687 }
1688 
_fm_vfs_menu_set_display_name(GFile * file,const char * display_name,GCancellable * cancellable,GError ** error)1689 static GFile *_fm_vfs_menu_set_display_name(GFile *file,
1690                                             const char *display_name,
1691                                             GCancellable *cancellable,
1692                                             GError **error)
1693 {
1694     FmMenuVFile *item = FM_MENU_VFILE(file);
1695     FmVfsMenuMainThreadData enu;
1696 
1697     /* g_debug("_fm_vfs_menu_set_display_name: %s -> %s", item->path, display_name); */
1698     if (item->path == NULL)
1699     {
1700         ERROR_UNSUPPORTED(error);
1701         return NULL;
1702     }
1703     if (display_name == NULL || *display_name == '\0')
1704     {
1705         g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED,
1706                             _("Menu item name cannot be empty"));
1707         return NULL;
1708     }
1709     enu.path_str = item->path;
1710     enu.display_name = display_name;
1711     enu.cancellable = cancellable;
1712     enu.error = error;
1713     if (RUN_WITH_MENU_CACHE(_fm_vfs_menu_set_display_name_real, &enu))
1714         return g_object_ref(file);
1715     return NULL;
1716 }
1717 
_fm_vfs_menu_query_settable_attributes(GFile * file,GCancellable * cancellable,GError ** error)1718 static GFileAttributeInfoList *_fm_vfs_menu_query_settable_attributes(GFile *file,
1719                                                                       GCancellable *cancellable,
1720                                                                       GError **error)
1721 {
1722     return g_file_attribute_info_list_ref(_fm_vfs_menu_settable_attributes);
1723 }
1724 
_fm_vfs_menu_query_writable_namespaces(GFile * file,GCancellable * cancellable,GError ** error)1725 static GFileAttributeInfoList *_fm_vfs_menu_query_writable_namespaces(GFile *file,
1726                                                                       GCancellable *cancellable,
1727                                                                       GError **error)
1728 {
1729     ERROR_UNSUPPORTED(error);
1730     return NULL;
1731 }
1732 
_fm_vfs_menu_set_attributes_from_info_real(gpointer data)1733 static gboolean _fm_vfs_menu_set_attributes_from_info_real(gpointer data)
1734 {
1735     FmVfsMenuMainThreadData *init = data;
1736     MenuCache *mc;
1737     MenuCacheItem *item;
1738     gpointer value;
1739     const char *display_name = NULL;
1740     GIcon *icon = NULL;
1741     gint set_hidden = -1;
1742     gboolean ok = FALSE;
1743 
1744     /* check attributes first */
1745     if (g_file_info_get_attribute_data(init->info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
1746                                        NULL, &value, NULL))
1747         display_name = value;
1748     if (g_file_info_get_attribute_data(init->info, G_FILE_ATTRIBUTE_STANDARD_ICON,
1749                                        NULL, &value, NULL))
1750         icon = value;
1751     if (g_file_info_get_attribute_data(init->info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
1752                                        NULL, &value, NULL))
1753         set_hidden = (*(gboolean *)value) ? 1 : 0;
1754     if (display_name == NULL && icon == NULL && set_hidden < 0)
1755         return TRUE; /* nothing to do */
1756     /* now try access item */
1757     mc = _get_menu_cache(init->error);
1758     if(mc == NULL)
1759         goto _mc_failed;
1760 
1761     item = _vfile_path_to_menu_cache_item(mc, init->path_str);
1762     if(item == NULL)
1763         g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1764                             _("Invalid menu item"));
1765     else if (menu_cache_item_get_file_basename(item) == NULL ||
1766              menu_cache_item_get_file_dirname(item) == NULL)
1767         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1768                     _("The menu item '%s' doesn't have appropriate entry file"),
1769                     menu_cache_item_get_id(item));
1770     else if (!g_cancellable_set_error_if_cancelled(init->cancellable, init->error))
1771     {
1772         char *path;
1773         GKeyFile *kf;
1774         GError *err = NULL;
1775         gboolean no_error = TRUE;
1776 
1777         /* for hidden on directory: use _add_directory() or _remove_directory() */
1778         if (set_hidden >= 0 && menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
1779         {
1780 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
1781             char *unescaped = g_uri_unescape_string(init->path_str, NULL);
1782             if (set_hidden > 0)
1783                 no_error = _remove_directory(unescaped, init->cancellable, init->error);
1784             else
1785                 no_error = _add_directory(unescaped, init->cancellable, init->error);
1786             g_free(unescaped);
1787             ok = no_error;
1788 #else
1789             g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1790                                 _("Change hidden status isn't supported for menu directory"));
1791             no_error = FALSE;
1792 #endif
1793             if (display_name == NULL && icon == NULL) /* nothing else to update */
1794                 goto _done;
1795             set_hidden = -1; /* don't set NoDisplay for a directory */
1796         }
1797         /* in all other cases - update Name, Icon or NoDisplay and save keyfile */
1798         path = menu_cache_item_get_file_path(item);
1799         kf = g_key_file_new();
1800         ok = g_key_file_load_from_file(kf, path, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
1801                                        &err);
1802         g_free(path);
1803         if (ok) /* otherwise there is no reason to continue */
1804         {
1805             char *contents;
1806             gsize length;
1807 
1808             if (display_name)
1809             {
1810                 /* get locale name */
1811                 const gchar * const *langs = g_get_language_names();
1812 
1813                 if (strcmp(langs[0], "C") != 0)
1814                 {
1815                     char *lang;
1816                     /* remove encoding from locale name */
1817                     char *sep = strchr(langs[0], '.');
1818 
1819                     if (sep)
1820                         lang = g_strndup(langs[0], sep - langs[0]);
1821                     else
1822                         lang = g_strdup(langs[0]);
1823                     g_key_file_set_locale_string(kf, G_KEY_FILE_DESKTOP_GROUP,
1824                                                  G_KEY_FILE_DESKTOP_KEY_NAME, lang,
1825                                                  display_name);
1826                     g_free(lang);
1827                 }
1828                 else
1829                     g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP,
1830                                           G_KEY_FILE_DESKTOP_KEY_NAME, display_name);
1831             }
1832             if (icon)
1833             {
1834                 char *icon_str = g_icon_to_string(icon);
1835                 /* FIXME: need to change encoding in some cases? */
1836                 g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP,
1837                                       G_KEY_FILE_DESKTOP_KEY_ICON, icon_str);
1838                 g_free(icon_str);
1839             }
1840             if (set_hidden >= 0)
1841             {
1842                 g_key_file_set_boolean(kf, G_KEY_FILE_DESKTOP_GROUP,
1843                                        G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY,
1844                                        (set_hidden > 0));
1845             }
1846             contents = g_key_file_to_data(kf, &length, &err);
1847             if (contents == NULL)
1848                 ok = FALSE;
1849             else
1850             {
1851                 path = g_build_filename(g_get_user_data_dir(),
1852                                         (menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR) ? "desktop-directories" : "applications",
1853                                         menu_cache_item_get_file_basename(item), NULL);
1854                 ok = g_file_set_contents(path, contents, length, &err);
1855                 /* FIXME: handle case if directory doesn't exist */
1856                 g_free(contents);
1857                 g_free(path);
1858             }
1859         }
1860         g_key_file_free(kf);
1861         if (no_error && !ok) /* we got error in err */
1862             g_propagate_error(init->error, err);
1863         else if (!ok) /* both init->error and err contain error */
1864             g_error_free(err);
1865         else if (!no_error) /* we got error in init->error */
1866             ok = FALSE;
1867     }
1868 
1869 _done:
1870 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
1871     if(item)
1872         menu_cache_item_unref(item);
1873 #endif
1874     menu_cache_unref(mc);
1875 
1876 _mc_failed:
1877     return ok;
1878 }
1879 
_fm_vfs_menu_set_attributes_from_info(GFile * file,GFileInfo * info,GFileQueryInfoFlags flags,GCancellable * cancellable,GError ** error)1880 static gboolean _fm_vfs_menu_set_attributes_from_info(GFile *file,
1881                                                       GFileInfo *info,
1882                                                       GFileQueryInfoFlags flags,
1883                                                       GCancellable *cancellable,
1884                                                       GError **error)
1885 {
1886     FmMenuVFile *item = FM_MENU_VFILE(file);
1887     FmVfsMenuMainThreadData enu;
1888 
1889     if (item->path == NULL)
1890     {
1891         ERROR_UNSUPPORTED(error);
1892         return FALSE;
1893     }
1894     enu.path_str = item->path;
1895     enu.info = info;
1896 //    enu.flags = flags;
1897     enu.cancellable = cancellable;
1898     enu.error = error;
1899     return (RUN_WITH_MENU_CACHE(_fm_vfs_menu_set_attributes_from_info_real, &enu));
1900 }
1901 
_fm_vfs_menu_set_attribute(GFile * file,const char * attribute,GFileAttributeType type,gpointer value_p,GFileQueryInfoFlags flags,GCancellable * cancellable,GError ** error)1902 static gboolean _fm_vfs_menu_set_attribute(GFile *file,
1903                                            const char *attribute,
1904                                            GFileAttributeType type,
1905                                            gpointer value_p,
1906                                            GFileQueryInfoFlags flags,
1907                                            GCancellable *cancellable,
1908                                            GError **error)
1909 {
1910     FmMenuVFile *item = FM_MENU_VFILE(file);
1911     GFileInfo *info;
1912     gboolean result;
1913 
1914     g_debug("_fm_vfs_menu_set_attribute: %s on %s", attribute, item->path);
1915     if (item->path == NULL)
1916     {
1917         ERROR_UNSUPPORTED(error);
1918         return FALSE;
1919     }
1920     if (value_p == NULL)
1921         goto _invalid_arg;
1922     if (strcmp(attribute, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME) == 0)
1923     {
1924         if (type != G_FILE_ATTRIBUTE_TYPE_STRING)
1925             goto _invalid_arg;
1926         info = g_file_info_new();
1927         g_file_info_set_display_name(info, value_p);
1928     }
1929     else if (strcmp(attribute, G_FILE_ATTRIBUTE_STANDARD_ICON) == 0)
1930     {
1931         if (type != G_FILE_ATTRIBUTE_TYPE_OBJECT)
1932             goto _invalid_arg;
1933         if (!G_IS_ICON(value_p))
1934             goto _invalid_arg;
1935         info = g_file_info_new();
1936         g_file_info_set_icon(info, value_p);
1937     }
1938     else if (strcmp(attribute, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) == 0)
1939     {
1940         if (type != G_FILE_ATTRIBUTE_TYPE_BOOLEAN)
1941             goto _invalid_arg;
1942         info = g_file_info_new();
1943         g_file_info_set_is_hidden(info, *(gboolean *)value_p);
1944     }
1945     else
1946     {
1947         g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1948                     _("Setting attribute '%s' not supported"), attribute);
1949         return FALSE;
1950     }
1951     result = _fm_vfs_menu_set_attributes_from_info(file, info, flags,
1952                                                    cancellable, error);
1953     g_object_unref(info);
1954     return result;
1955 
1956 _invalid_arg:
1957     g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1958                 _("Invalid value for attribute '%s'"), attribute);
1959     return FALSE;
1960 }
1961 
_g_file_new_for_id(const char * id)1962 static inline GFile *_g_file_new_for_id(const char *id)
1963 {
1964     char *file_path;
1965     GFile *file;
1966 
1967     file_path = g_build_filename(g_get_user_data_dir(), "applications", id, NULL);
1968     /* we can try to guess file path and make directories but it
1969        hardly worth the efforts so it's easier to just make new file
1970        by its ID since ID is unique thru all the menu */
1971     if (file_path == NULL)
1972         return NULL;
1973     file = g_file_new_for_path(file_path);
1974     g_free(file_path);
1975     return file;
1976 }
1977 
_fm_vfs_menu_read_fn_real(gpointer data)1978 static gboolean _fm_vfs_menu_read_fn_real(gpointer data)
1979 {
1980     FmVfsMenuMainThreadData *init = data;
1981     MenuCache *mc;
1982     MenuCacheItem *item = NULL;
1983 
1984     init->result = NULL;
1985     mc = _get_menu_cache(init->error);
1986     if(mc == NULL)
1987         goto _mc_failed;
1988 
1989     if(init->path_str)
1990         item = _vfile_path_to_menu_cache_item(mc, init->path_str);
1991 
1992         /* If item wasn't found or isn't a file then we cannot read it. */
1993     if(item != NULL && menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
1994         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
1995                     _("The '%s' is a menu directory"), init->path_str);
1996     else if(item == NULL || menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP)
1997         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1998                     _("The '%s' isn't a menu item"),
1999                     init->path_str ? init->path_str : "/");
2000     else
2001     {
2002         char *file_path;
2003         GFile *gf;
2004         GError *err = NULL;
2005 
2006         file_path = menu_cache_item_get_file_path(item);
2007         if (file_path)
2008         {
2009             gf = g_file_new_for_path(file_path);
2010             g_free(file_path);
2011             if (gf)
2012             {
2013                 init->result = g_file_read(gf, init->cancellable, &err);
2014                 if (init->result == NULL)
2015                 {
2016                     /* never return G_IO_ERROR_IS_DIRECTORY */
2017                     if (err->domain == G_IO_ERROR &&
2018                         err->code == G_IO_ERROR_IS_DIRECTORY)
2019                     {
2020                         g_error_free(err);
2021                         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE,
2022                                     _("The '%s' entry file is broken"), init->path_str);
2023                     }
2024                     else
2025                         g_propagate_error(init->error, err);
2026                 }
2027                 g_object_unref(gf);
2028             }
2029         }
2030     }
2031 
2032 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2033     if(item)
2034         menu_cache_item_unref(item);
2035 #endif
2036     menu_cache_unref(mc);
2037 
2038 _mc_failed:
2039     return FALSE;
2040 }
2041 
_fm_vfs_menu_read_fn(GFile * file,GCancellable * cancellable,GError ** error)2042 static GFileInputStream *_fm_vfs_menu_read_fn(GFile *file,
2043                                               GCancellable *cancellable,
2044                                               GError **error)
2045 {
2046     FmMenuVFile *item = FM_MENU_VFILE(file);
2047     FmVfsMenuMainThreadData enu;
2048 
2049     /* g_debug("_fm_vfs_menu_read_fn %s", item->path); */
2050     enu.path_str = item->path;
2051     enu.cancellable = cancellable;
2052     enu.error = error;
2053     RUN_WITH_MENU_CACHE(_fm_vfs_menu_read_fn_real, &enu);
2054     return enu.result;
2055 }
2056 
_fm_vfs_menu_append_to(GFile * file,GFileCreateFlags flags,GCancellable * cancellable,GError ** error)2057 static GFileOutputStream *_fm_vfs_menu_append_to(GFile *file,
2058                                                  GFileCreateFlags flags,
2059                                                  GCancellable *cancellable,
2060                                                  GError **error)
2061 {
2062     ERROR_UNSUPPORTED(error);
2063     return NULL;
2064 }
2065 
2066 
2067 /* ---- FmMenuVFileOutputStream class ---- */
2068 #define FM_TYPE_MENU_VFILE_OUTPUT_STREAM  (fm_vfs_menu_file_output_stream_get_type())
2069 #define FM_MENU_VFILE_OUTPUT_STREAM(o)    (G_TYPE_CHECK_INSTANCE_CAST((o), \
2070                                            FM_TYPE_MENU_VFILE_OUTPUT_STREAM, \
2071                                            FmMenuVFileOutputStream))
2072 
2073 typedef struct _FmMenuVFileOutputStream      FmMenuVFileOutputStream;
2074 typedef struct _FmMenuVFileOutputStreamClass FmMenuVFileOutputStreamClass;
2075 
2076 struct _FmMenuVFileOutputStream
2077 {
2078     GFileOutputStream parent;
2079     GOutputStream *real_stream;
2080     gchar *path; /* "Dir/App.desktop" */
2081     GString *content;
2082     gboolean do_close;
2083 };
2084 
2085 struct _FmMenuVFileOutputStreamClass
2086 {
2087     GFileOutputStreamClass parent_class;
2088 };
2089 
2090 static GType fm_vfs_menu_file_output_stream_get_type  (void);
2091 
2092 G_DEFINE_TYPE(FmMenuVFileOutputStream, fm_vfs_menu_file_output_stream, G_TYPE_FILE_OUTPUT_STREAM);
2093 
fm_vfs_menu_file_output_stream_finalize(GObject * object)2094 static void fm_vfs_menu_file_output_stream_finalize(GObject *object)
2095 {
2096     FmMenuVFileOutputStream *stream = FM_MENU_VFILE_OUTPUT_STREAM(object);
2097     if(stream->real_stream)
2098         g_object_unref(stream->real_stream);
2099     g_free(stream->path);
2100     g_string_free(stream->content, TRUE);
2101     G_OBJECT_CLASS(fm_vfs_menu_file_output_stream_parent_class)->finalize(object);
2102 }
2103 
fm_vfs_menu_file_output_stream_write(GOutputStream * stream,const void * buffer,gsize count,GCancellable * cancellable,GError ** error)2104 static gssize fm_vfs_menu_file_output_stream_write(GOutputStream *stream,
2105                                                    const void *buffer, gsize count,
2106                                                    GCancellable *cancellable,
2107                                                    GError **error)
2108 {
2109     if (g_cancellable_set_error_if_cancelled(cancellable, error))
2110         return -1;
2111     g_string_append_len(FM_MENU_VFILE_OUTPUT_STREAM(stream)->content, buffer, count);
2112     return (gssize)count;
2113 }
2114 
fm_vfs_menu_file_output_stream_close(GOutputStream * gos,GCancellable * cancellable,GError ** error)2115 static gboolean fm_vfs_menu_file_output_stream_close(GOutputStream *gos,
2116                                                      GCancellable *cancellable,
2117                                                      GError **error)
2118 {
2119     FmMenuVFileOutputStream *stream = FM_MENU_VFILE_OUTPUT_STREAM(gos);
2120     GKeyFile *kf;
2121     gsize len = 0;
2122     gchar *content;
2123     gboolean ok;
2124 
2125     if (g_cancellable_set_error_if_cancelled(cancellable, error))
2126         return FALSE;
2127     if (!stream->do_close)
2128         return TRUE;
2129     kf = g_key_file_new();
2130     /* parse entered file content first */
2131     if (stream->content->len > 0)
2132         g_key_file_load_from_data(kf, stream->content->str, stream->content->len,
2133                                   G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
2134                                   NULL); /* FIXME: don't ignore some errors? */
2135     /* correct invalid data in desktop entry file: Name and Exec are mandatory,
2136        Type must be Application, and Category should include requested one */
2137     if(!g_key_file_has_key(kf, G_KEY_FILE_DESKTOP_GROUP,
2138                            G_KEY_FILE_DESKTOP_KEY_NAME, NULL))
2139         g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP,
2140                               G_KEY_FILE_DESKTOP_KEY_NAME, "");
2141     if(!g_key_file_has_key(kf, G_KEY_FILE_DESKTOP_GROUP,
2142                            G_KEY_FILE_DESKTOP_KEY_EXEC, NULL))
2143         g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP,
2144                               G_KEY_FILE_DESKTOP_KEY_EXEC, "");
2145     g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE,
2146                           G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
2147     content = g_key_file_to_data(kf, &len, error);
2148     g_key_file_free(kf);
2149     if (!content)
2150         return FALSE;
2151     ok = g_output_stream_write_all(stream->real_stream, content, len, &len,
2152                                    cancellable, error);
2153     g_free(content);
2154     if (!ok || !g_output_stream_close(stream->real_stream, cancellable, error))
2155         return FALSE;
2156     stream->do_close = FALSE;
2157     if (stream->path && !_add_application(stream->path, cancellable, error))
2158         return FALSE;
2159     return TRUE;
2160 }
2161 
fm_vfs_menu_file_output_stream_class_init(FmMenuVFileOutputStreamClass * klass)2162 static void fm_vfs_menu_file_output_stream_class_init(FmMenuVFileOutputStreamClass *klass)
2163 {
2164     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
2165     GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS(klass);
2166 
2167     gobject_class->finalize = fm_vfs_menu_file_output_stream_finalize;
2168     stream_class->write_fn = fm_vfs_menu_file_output_stream_write;
2169     stream_class->close_fn = fm_vfs_menu_file_output_stream_close;
2170     /* we don't implement seek/truncate/etag/query so no GFileOutputStream funcs */
2171 }
2172 
fm_vfs_menu_file_output_stream_init(FmMenuVFileOutputStream * stream)2173 static void fm_vfs_menu_file_output_stream_init(FmMenuVFileOutputStream *stream)
2174 {
2175     stream->content = g_string_sized_new(1024);
2176     stream->do_close = TRUE;
2177 }
2178 
_fm_vfs_menu_file_output_stream_new(const gchar * path)2179 static FmMenuVFileOutputStream *_fm_vfs_menu_file_output_stream_new(const gchar *path)
2180 {
2181     FmMenuVFileOutputStream *stream;
2182 
2183     stream = g_object_new(FM_TYPE_MENU_VFILE_OUTPUT_STREAM, NULL);
2184     if (path)
2185         stream->path = g_strdup(path);
2186     return stream;
2187 }
2188 
_vfile_menu_create(GFile * file,GFileCreateFlags flags,GCancellable * cancellable,GError ** error,const gchar * path)2189 static GFileOutputStream *_vfile_menu_create(GFile *file,
2190                                              GFileCreateFlags flags,
2191                                              GCancellable *cancellable,
2192                                              GError **error,
2193                                              const gchar *path)
2194 {
2195     FmMenuVFileOutputStream *stream;
2196     GFileOutputStream *ostream;
2197     GError *err = NULL;
2198     GFile *parent;
2199 
2200     if (g_cancellable_set_error_if_cancelled(cancellable, error))
2201         return NULL;
2202 //    g_file_delete(file, cancellable, NULL); /* remove old if there is any */
2203     ostream = g_file_create(file, flags, cancellable, &err);
2204     if (ostream == NULL)
2205     {
2206         if (g_cancellable_is_cancelled(cancellable) ||
2207             err->domain != G_IO_ERROR || err->code != G_IO_ERROR_NOT_FOUND)
2208         {
2209             g_propagate_error(error, err);
2210             return NULL;
2211         }
2212         /* .local/share/applications/ isn't found? make it! */
2213         g_clear_error(&err);
2214         parent = g_file_get_parent(file);
2215         if (!g_file_make_directory_with_parents(parent, cancellable, error))
2216         {
2217             g_object_unref(parent);
2218             return NULL;
2219         }
2220         g_object_unref(parent);
2221         ostream = g_file_create(file, flags, cancellable, error);
2222         if (ostream == NULL)
2223             return ostream;
2224     }
2225     stream = _fm_vfs_menu_file_output_stream_new(path);
2226     stream->real_stream = G_OUTPUT_STREAM(ostream);
2227     return (GFileOutputStream*)stream;
2228 }
2229 
_vfile_menu_replace(GFile * file,const char * etag,gboolean make_backup,GFileCreateFlags flags,GCancellable * cancellable,GError ** error,const gchar * path)2230 static GFileOutputStream *_vfile_menu_replace(GFile *file,
2231                                               const char *etag,
2232                                               gboolean make_backup,
2233                                               GFileCreateFlags flags,
2234                                               GCancellable *cancellable,
2235                                               GError **error,
2236                                               const gchar *path)
2237 {
2238     FmMenuVFileOutputStream *stream;
2239     GFileOutputStream *ostream;
2240 
2241     if (g_cancellable_set_error_if_cancelled(cancellable, error))
2242         return NULL;
2243     stream = _fm_vfs_menu_file_output_stream_new(path);
2244     ostream = g_file_replace(file, etag, make_backup, flags, cancellable, error);
2245     if (ostream == NULL)
2246     {
2247         g_object_unref(stream);
2248         return NULL;
2249     }
2250     stream->real_stream = G_OUTPUT_STREAM(ostream);
2251     return (GFileOutputStream*)stream;
2252 }
2253 
_fm_vfs_menu_create_real(gpointer data)2254 static gboolean _fm_vfs_menu_create_real(gpointer data)
2255 {
2256     FmVfsMenuMainThreadData *init = data;
2257     MenuCache *mc;
2258     char *unescaped = NULL, *id;
2259     gboolean is_invalid = TRUE;
2260 
2261     init->result = NULL;
2262     if(init->path_str)
2263     {
2264         MenuCacheItem *item;
2265 #if !MENU_CACHE_CHECK_VERSION(0, 5, 0)
2266         GSList *list, *l;
2267 #endif
2268 
2269         mc = _get_menu_cache(init->error);
2270         if(mc == NULL)
2271             goto _mc_failed;
2272         unescaped = g_uri_unescape_string(init->path_str, NULL);
2273         /* ensure new menu item has suffix .desktop */
2274         if (!g_str_has_suffix(unescaped, ".desktop"))
2275         {
2276             id = unescaped;
2277             unescaped = g_strconcat(unescaped, ".desktop", NULL);
2278             g_free(id);
2279         }
2280         id = strrchr(unescaped, '/');
2281         if (id)
2282             id++;
2283         else
2284             id = unescaped;
2285 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
2286         item = menu_cache_find_item_by_id(mc, id);
2287         if (item)
2288             menu_cache_item_unref(item); /* use item simply as marker */
2289 #else
2290         list = menu_cache_list_all_apps(mc);
2291         for (l = list; l; l = l->next)
2292             if (strcmp(menu_cache_item_get_id(l->data), id) == 0)
2293                 break;
2294         if (l)
2295             item = l->data;
2296         else
2297             item = NULL;
2298         g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref);
2299 #endif
2300         if(item == NULL)
2301             is_invalid = FALSE;
2302         /* g_debug("create id %s, category %s", id, category); */
2303         menu_cache_unref(mc);
2304     }
2305 
2306     if(is_invalid)
2307         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_EXISTS,
2308                     _("Cannot create menu item '%s'"),
2309                     init->path_str ? init->path_str : "/");
2310     else
2311     {
2312         GFile *gf = _g_file_new_for_id(id);
2313 
2314         if (gf)
2315         {
2316             init->result = _vfile_menu_create(gf, G_FILE_CREATE_NONE,
2317                                               init->cancellable, init->error,
2318                                               unescaped);
2319             g_object_unref(gf);
2320         }
2321     }
2322     g_free(unescaped);
2323 
2324 _mc_failed:
2325     return FALSE;
2326 }
2327 
_fm_vfs_menu_create(GFile * file,GFileCreateFlags flags,GCancellable * cancellable,GError ** error)2328 static GFileOutputStream *_fm_vfs_menu_create(GFile *file,
2329                                               GFileCreateFlags flags,
2330                                               GCancellable *cancellable,
2331                                               GError **error)
2332 {
2333     FmMenuVFile *item = FM_MENU_VFILE(file);
2334     FmVfsMenuMainThreadData enu;
2335 
2336     /* g_debug("_fm_vfs_menu_create %s", item->path); */
2337     enu.path_str = item->path;
2338     enu.cancellable = cancellable;
2339     enu.error = error;
2340     // enu.flags = flags;
2341     RUN_WITH_MENU_CACHE(_fm_vfs_menu_create_real, &enu);
2342     return enu.result;
2343 }
2344 
_fm_vfs_menu_replace_real(gpointer data)2345 static gboolean _fm_vfs_menu_replace_real(gpointer data)
2346 {
2347     FmVfsMenuMainThreadData *init = data;
2348     MenuCache *mc;
2349     char *unescaped = NULL, *id;
2350     gboolean is_invalid = TRUE;
2351 
2352     init->result = NULL;
2353     if(init->path_str)
2354     {
2355         MenuCacheItem *item, *item2;
2356 
2357         mc = _get_menu_cache(init->error);
2358         if(mc == NULL)
2359             goto _mc_failed;
2360         /* prepare id first */
2361         unescaped = g_uri_unescape_string(init->path_str, NULL);
2362         id = strrchr(unescaped, '/');
2363         if (id != NULL)
2364             id++;
2365         else
2366             id = unescaped;
2367         /* get existing item */
2368         item = _vfile_path_to_menu_cache_item(mc, init->path_str);
2369         if (item != NULL) /* item is there, OK, we'll replace it then */
2370             is_invalid = FALSE;
2371         /* if not found then check item by id to exclude conflicts */
2372         else
2373         {
2374 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
2375             item2 = menu_cache_find_item_by_id(mc, id);
2376 #else
2377             GSList *list = menu_cache_list_all_apps(mc), *l;
2378             for (l = list; l; l = l->next)
2379                 if (strcmp(menu_cache_item_get_id(l->data), id) == 0)
2380                     break;
2381             if (l)
2382                 item2 = menu_cache_item_ref(l->data);
2383             else
2384                 item2 = NULL;
2385             g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref);
2386 #endif
2387             if(item2 == NULL)
2388                 is_invalid = FALSE;
2389             else /* item was found in another category */
2390                 menu_cache_item_unref(item2);
2391         }
2392         menu_cache_unref(mc);
2393     }
2394 
2395     if(is_invalid)
2396         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_EXISTS,
2397                     _("Cannot create menu item '%s'"),
2398                     init->path_str ? init->path_str : "/");
2399     else
2400     {
2401         GFile *gf = _g_file_new_for_id(id);
2402 
2403         if (gf)
2404         {
2405             /* FIXME: use flags and make_backup */
2406             init->result = _vfile_menu_replace(gf, NULL, FALSE,
2407                                                G_FILE_CREATE_REPLACE_DESTINATION,
2408                                                init->cancellable, init->error,
2409                                                /* don't insert it into XML */
2410                                                NULL);
2411             g_object_unref(gf);
2412         }
2413     }
2414     g_free(unescaped);
2415 
2416 _mc_failed:
2417     return FALSE;
2418 }
2419 
_fm_vfs_menu_replace(GFile * file,const char * etag,gboolean make_backup,GFileCreateFlags flags,GCancellable * cancellable,GError ** error)2420 static GFileOutputStream *_fm_vfs_menu_replace(GFile *file,
2421                                                const char *etag,
2422                                                gboolean make_backup,
2423                                                GFileCreateFlags flags,
2424                                                GCancellable *cancellable,
2425                                                GError **error)
2426 {
2427     FmMenuVFile *item = FM_MENU_VFILE(file);
2428     FmVfsMenuMainThreadData enu;
2429 
2430     /* g_debug("_fm_vfs_menu_replace %s", item->path); */
2431     enu.path_str = item->path;
2432     enu.cancellable = cancellable;
2433     enu.error = error;
2434     // enu.flags = flags;
2435     // enu.make_backup = make_backup;
2436     RUN_WITH_MENU_CACHE(_fm_vfs_menu_replace_real, &enu);
2437     return enu.result;
2438 }
2439 
2440 /* not in main thread; returns NULL on failure */
_g_key_file_from_item(GFile * file,GCancellable * cancellable,GError ** error)2441 static GKeyFile *_g_key_file_from_item(GFile *file, GCancellable *cancellable,
2442                                        GError **error)
2443 {
2444     char *contents;
2445     gsize length;
2446     GKeyFile *kf;
2447 
2448     if (!g_file_load_contents(file, cancellable, &contents, &length, NULL, error))
2449         return NULL;
2450     kf = g_key_file_new();
2451     if (!g_key_file_load_from_data(kf, contents, length,
2452                                    G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
2453                                    error))
2454     {
2455         g_key_file_free(kf);
2456         kf = NULL;
2457     }
2458     g_free(contents);
2459     return kf;
2460 }
2461 
2462 /* not in main thread; returns FALSE on failure, consumes key file */
_g_key_file_into_item(GFile * file,GKeyFile * kf,GCancellable * cancellable,GError ** error)2463 static gboolean _g_key_file_into_item(GFile *file, GKeyFile *kf,
2464                                       GCancellable *cancellable, GError **error)
2465 {
2466     char *contents;
2467     gsize length;
2468     gboolean result = FALSE;
2469 
2470     contents = g_key_file_to_data(kf, &length, error);
2471     g_key_file_free(kf);
2472     if (contents == NULL)
2473         return FALSE;
2474     result = g_file_replace_contents(file, contents, length, NULL, FALSE,
2475                                      G_FILE_CREATE_REPLACE_DESTINATION, NULL,
2476                                      cancellable, error);
2477     g_free(contents);
2478     return result;
2479 }
2480 
_fm_vfs_menu_delete_file(GFile * file,GCancellable * cancellable,GError ** error)2481 static gboolean _fm_vfs_menu_delete_file(GFile *file,
2482                                          GCancellable *cancellable,
2483                                          GError **error)
2484 {
2485     FmMenuVFile *item = FM_MENU_VFILE(file);
2486     GKeyFile *kf;
2487     GError *err = NULL;
2488 
2489     g_debug("_fm_vfs_menu_delete_file %s", item->path);
2490     /* load contents */
2491     kf = _g_key_file_from_item(file, cancellable, &err);
2492     if (kf == NULL)
2493     {
2494 #if MENU_CACHE_CHECK_VERSION(0, 5, 0)
2495         /* it might be just a directory */
2496         if (err->domain == G_IO_ERROR && err->code == G_IO_ERROR_IS_DIRECTORY)
2497         {
2498             char *unescaped = g_uri_unescape_string(item->path, NULL);
2499             gboolean ok = _remove_directory(unescaped, cancellable, error);
2500             g_error_free(err);
2501             g_free(unescaped);
2502             return ok;
2503         }
2504         /* else it just failed */
2505 #endif
2506         g_propagate_error(error, err);
2507         return FALSE;
2508     }
2509     /* set NoDisplay=true and save */
2510     g_key_file_set_boolean(kf, G_KEY_FILE_DESKTOP_GROUP,
2511                            G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
2512     return _g_key_file_into_item(file, kf, cancellable, error);
2513 }
2514 
_fm_vfs_menu_trash(GFile * file,GCancellable * cancellable,GError ** error)2515 static gboolean _fm_vfs_menu_trash(GFile *file,
2516                                    GCancellable *cancellable,
2517                                    GError **error)
2518 {
2519     ERROR_UNSUPPORTED(error);
2520     return FALSE;
2521 }
2522 
_fm_vfs_menu_make_directory(GFile * file,GCancellable * cancellable,GError ** error)2523 static gboolean _fm_vfs_menu_make_directory(GFile *file,
2524                                             GCancellable *cancellable,
2525                                             GError **error)
2526 {
2527 #if !MENU_CACHE_CHECK_VERSION(0, 5, 0)
2528     /* creating a directory with libmenu-cache < 0.5.0 will lead to invisible
2529        directory; inexperienced user will be confused; therefore we disable
2530        such operation in such conditions */
2531     ERROR_UNSUPPORTED(error);
2532     return FALSE;
2533 #else
2534     FmMenuVFile *item = FM_MENU_VFILE(file);
2535     char *unescaped;
2536     gboolean ok;
2537 
2538     /* XDG desktop menu specification: desktop-entry-id should be *.desktop */
2539     if (g_str_has_suffix(item->path, ".desktop"))
2540     {
2541         g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME,
2542                             _("Name of menu directory should not end with"
2543                               " \".desktop\""));
2544         return FALSE;
2545     }
2546     unescaped = g_uri_unescape_string(item->path, NULL);
2547     ok = _add_directory(unescaped, cancellable, error);
2548     g_free(unescaped);
2549     return ok;
2550 #endif
2551 }
2552 
_fm_vfs_menu_make_symbolic_link(GFile * file,const char * symlink_value,GCancellable * cancellable,GError ** error)2553 static gboolean _fm_vfs_menu_make_symbolic_link(GFile *file,
2554                                                 const char *symlink_value,
2555                                                 GCancellable *cancellable,
2556                                                 GError **error)
2557 {
2558     ERROR_UNSUPPORTED(error);
2559     return FALSE;
2560 }
2561 
_fm_vfs_menu_copy(GFile * source,GFile * destination,GFileCopyFlags flags,GCancellable * cancellable,GFileProgressCallback progress_callback,gpointer progress_callback_data,GError ** error)2562 static gboolean _fm_vfs_menu_copy(GFile *source,
2563                                   GFile *destination,
2564                                   GFileCopyFlags flags,
2565                                   GCancellable *cancellable,
2566                                   GFileProgressCallback progress_callback,
2567                                   gpointer progress_callback_data,
2568                                   GError **error)
2569 {
2570     ERROR_UNSUPPORTED(error);
2571     return FALSE;
2572 }
2573 
_fm_vfs_menu_move_real(gpointer data)2574 static gboolean _fm_vfs_menu_move_real(gpointer data)
2575 {
2576     FmVfsMenuMainThreadData *init = data;
2577     MenuCache *mc = NULL;
2578     MenuCacheItem *item = NULL, *item2;
2579     char *src_path, *dst_path;
2580     char *src_id, *dst_id;
2581     gboolean result = FALSE;
2582 
2583     dst_path = init->destination->path;
2584     if (init->path_str == NULL || dst_path == NULL)
2585     {
2586         g_set_error_literal(init->error, G_IO_ERROR, G_IO_ERROR_FAILED,
2587                             _("Invalid operation with menu root"));
2588         return FALSE;
2589     }
2590     /* make path strings */
2591     src_path = g_uri_unescape_string(init->path_str, NULL);
2592     dst_path = g_uri_unescape_string(dst_path, NULL);
2593     src_id = strrchr(src_path, '/');
2594     if (src_id)
2595         src_id++;
2596     else
2597         src_id = src_path;
2598     dst_id = strrchr(dst_path, '/');
2599     if (dst_id)
2600         dst_id++;
2601     else
2602         dst_id = dst_path;
2603     if (strcmp(src_id, dst_id))
2604     {
2605         /* ID change isn't supported now */
2606         ERROR_UNSUPPORTED(init->error);
2607         goto _failed;
2608     }
2609     if (strcmp(src_path, dst_path) == 0)
2610     {
2611         g_warning("menu: tried to move '%s' into itself", src_path);
2612         g_free(src_path);
2613         g_free(dst_path);
2614         return TRUE; /* nothing was changed */
2615     }
2616     /* do actual move */
2617     mc = _get_menu_cache(init->error);
2618     if(mc == NULL)
2619         goto _failed;
2620     item = _vfile_path_to_menu_cache_item(mc, init->path_str);
2621     /* TODO: if id changed then check for ID conflicts */
2622     /* TODO: save updated desktop entry for old ID (if different) */
2623     if(item == NULL || menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP)
2624     {
2625         /* FIXME: implement directories movement */
2626         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
2627                     _("The '%s' isn't a menu item"), init->path_str);
2628         goto _failed;
2629     }
2630     item2 = _vfile_path_to_menu_cache_item(mc, init->destination->path);
2631     if (item2)
2632     {
2633         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_EXISTS,
2634                     _("Menu path '%s' already exists"), dst_path);
2635 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2636         menu_cache_item_unref(item2);
2637 #endif
2638         goto _failed;
2639     }
2640     /* do actual move */
2641     if (_add_application(dst_path, init->cancellable, init->error))
2642     {
2643         if (_remove_application(src_path, init->cancellable, init->error))
2644             result = TRUE;
2645         else /* failed, rollback */
2646             _remove_application(dst_path, init->cancellable, NULL);
2647     }
2648 
2649 _failed:
2650 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2651     if(item)
2652         menu_cache_item_unref(item);
2653 #endif
2654     if(mc)
2655         menu_cache_unref(mc);
2656     g_free(src_path);
2657     g_free(dst_path);
2658     return result;
2659 }
2660 
_fm_vfs_menu_move(GFile * source,GFile * destination,GFileCopyFlags flags,GCancellable * cancellable,GFileProgressCallback progress_callback,gpointer progress_callback_data,GError ** error)2661 static gboolean _fm_vfs_menu_move(GFile *source,
2662                                   GFile *destination,
2663                                   GFileCopyFlags flags,
2664                                   GCancellable *cancellable,
2665                                   GFileProgressCallback progress_callback,
2666                                   gpointer progress_callback_data,
2667                                   GError **error)
2668 {
2669     FmMenuVFile *item = FM_MENU_VFILE(source);
2670     FmVfsMenuMainThreadData enu;
2671 
2672     /* g_debug("_fm_vfs_menu_move"); */
2673     if(!FM_IS_FILE(destination))
2674     {
2675         g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
2676                             _("Invalid destination"));
2677         return FALSE;
2678     }
2679     enu.path_str = item->path;
2680     enu.cancellable = cancellable;
2681     enu.error = error;
2682     // enu.flags = flags;
2683     enu.destination = FM_MENU_VFILE(destination);
2684     /* FIXME: use progress_callback */
2685     return RUN_WITH_MENU_CACHE(_fm_vfs_menu_move_real, &enu);
2686 }
2687 
2688 /* ---- FmMenuVFileMonitor class ---- */
2689 #define FM_TYPE_MENU_VFILE_MONITOR     (fm_vfs_menu_file_monitor_get_type())
2690 #define FM_MENU_VFILE_MONITOR(o)       (G_TYPE_CHECK_INSTANCE_CAST((o), \
2691                                         FM_TYPE_MENU_VFILE_MONITOR, FmMenuVFileMonitor))
2692 
2693 typedef struct _FmMenuVFileMonitor      FmMenuVFileMonitor;
2694 typedef struct _FmMenuVFileMonitorClass FmMenuVFileMonitorClass;
2695 
2696 static GType fm_vfs_menu_file_monitor_get_type  (void);
2697 
2698 struct _FmMenuVFileMonitor
2699 {
2700     GFileMonitor parent_object;
2701 
2702     FmMenuVFile *file;
2703     MenuCache *cache;
2704 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2705     MenuCacheItem *item;
2706     MenuCacheNotifyId notifier;
2707 #else
2708     GSList *items;
2709     gboolean stopped;
2710     gpointer notifier;
2711 #endif
2712 };
2713 
2714 struct _FmMenuVFileMonitorClass
2715 {
2716     GFileMonitorClass parent_class;
2717 };
2718 
2719 G_DEFINE_TYPE(FmMenuVFileMonitor, fm_vfs_menu_file_monitor, G_TYPE_FILE_MONITOR);
2720 
fm_vfs_menu_file_monitor_finalize(GObject * object)2721 static void fm_vfs_menu_file_monitor_finalize(GObject *object)
2722 {
2723     FmMenuVFileMonitor *mon = FM_MENU_VFILE_MONITOR(object);
2724 
2725     if(mon->cache)
2726     {
2727         if(mon->notifier)
2728             menu_cache_remove_reload_notify(mon->cache, mon->notifier);
2729         menu_cache_unref(mon->cache);
2730     }
2731 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2732     if(mon->item)
2733         menu_cache_item_unref(mon->item);
2734 #else
2735     g_slist_free_full(mon->items, (GDestroyNotify)menu_cache_item_unref);
2736 #endif
2737     g_object_unref(mon->file);
2738 
2739     G_OBJECT_CLASS(fm_vfs_menu_file_monitor_parent_class)->finalize(object);
2740 }
2741 
fm_vfs_menu_file_monitor_cancel(GFileMonitor * monitor)2742 static gboolean fm_vfs_menu_file_monitor_cancel(GFileMonitor *monitor)
2743 {
2744     FmMenuVFileMonitor *mon = FM_MENU_VFILE_MONITOR(monitor);
2745 
2746 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2747     if(mon->item)
2748         menu_cache_item_unref(mon->item); /* rest will be done in finalizer */
2749     mon->item = NULL;
2750 #else
2751     mon->stopped = TRUE;
2752 #endif
2753     return TRUE;
2754 }
2755 
fm_vfs_menu_file_monitor_class_init(FmMenuVFileMonitorClass * klass)2756 static void fm_vfs_menu_file_monitor_class_init(FmMenuVFileMonitorClass *klass)
2757 {
2758     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
2759     GFileMonitorClass *gfilemon_class = G_FILE_MONITOR_CLASS (klass);
2760 
2761     gobject_class->finalize = fm_vfs_menu_file_monitor_finalize;
2762     gfilemon_class->cancel = fm_vfs_menu_file_monitor_cancel;
2763 }
2764 
fm_vfs_menu_file_monitor_init(FmMenuVFileMonitor * item)2765 static void fm_vfs_menu_file_monitor_init(FmMenuVFileMonitor *item)
2766 {
2767     /* nothing */
2768 }
2769 
_fm_menu_vfile_monitor_new(void)2770 static FmMenuVFileMonitor *_fm_menu_vfile_monitor_new(void)
2771 {
2772     return (FmMenuVFileMonitor*)g_object_new(FM_TYPE_MENU_VFILE_MONITOR, NULL);
2773 }
2774 
2775 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
_reload_notify_handler(MenuCache * cache,gpointer user_data)2776 static void _reload_notify_handler(MenuCache* cache, gpointer user_data)
2777 #else
2778 static void _reload_notify_handler(gpointer cache, gpointer user_data)
2779 #endif
2780 {
2781     FmMenuVFileMonitor *mon = FM_MENU_VFILE_MONITOR(user_data);
2782     GSList *items, *new_items, *ol, *nl;
2783     MenuCacheItem *dir;
2784     GFile *file;
2785     const char *de_name;
2786     guint32 de_flag;
2787 
2788 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2789     if(mon->item == NULL) /* menu folder was destroyed or monitor cancelled */
2790         return;
2791     dir = mon->item;
2792     if(mon->file->path)
2793         mon->item = _vfile_path_to_menu_cache_item(cache, mon->file->path);
2794     else
2795         mon->item = MENU_CACHE_ITEM(menu_cache_dup_root_dir(cache));
2796     if(mon->item && menu_cache_item_get_type(mon->item) != MENU_CACHE_TYPE_DIR)
2797     {
2798         menu_cache_item_unref(mon->item);
2799         mon->item = NULL;
2800     }
2801     if(mon->item == NULL) /* folder was destroyed - emit event and exit */
2802     {
2803         menu_cache_item_unref(dir);
2804         g_file_monitor_emit_event(G_FILE_MONITOR(mon), G_FILE(mon->file), NULL,
2805                                   G_FILE_MONITOR_EVENT_DELETED);
2806         return;
2807     }
2808     items = menu_cache_dir_list_children(MENU_CACHE_DIR(dir));
2809     menu_cache_item_unref(dir);
2810     new_items = menu_cache_dir_list_children(MENU_CACHE_DIR(mon->item));
2811 #else
2812     if(mon->stopped) /* menu folder was destroyed or monitor cancelled */
2813         return;
2814     if(mon->file->path)
2815         dir = _vfile_path_to_menu_cache_item(cache, mon->file->path);
2816     else
2817         dir = MENU_CACHE_ITEM(menu_cache_get_root_dir(cache));
2818     if(dir == NULL) /* folder was destroyed - emit event and exit */
2819     {
2820         mon->stopped = TRUE;
2821         g_file_monitor_emit_event(G_FILE_MONITOR(mon), G_FILE(mon->file), NULL,
2822                                   G_FILE_MONITOR_EVENT_DELETED);
2823         return;
2824     }
2825     /* emit change on the folder in any case */
2826     g_file_monitor_emit_event(G_FILE_MONITOR(mon), G_FILE(mon->file), NULL,
2827                               G_FILE_MONITOR_EVENT_CHANGED);
2828     items = mon->items;
2829     mon->items = g_slist_copy_deep(menu_cache_dir_get_children(MENU_CACHE_DIR(dir)),
2830                                    (GCopyFunc)menu_cache_item_ref, NULL);
2831     new_items = g_slist_copy_deep(mon->items, (GCopyFunc)menu_cache_item_ref, NULL);
2832 #endif
2833     for (ol = items; ol; ) /* remove all separatorts first */
2834     {
2835         nl = ol->next;
2836         if (menu_cache_item_get_id(ol->data) == NULL)
2837         {
2838             menu_cache_item_unref(ol->data);
2839             items = g_slist_delete_link(items, ol);
2840         }
2841         ol = nl;
2842     }
2843     for (ol = new_items; ol; )
2844     {
2845         nl = ol->next;
2846         if (menu_cache_item_get_id(ol->data) == NULL)
2847         {
2848             menu_cache_item_unref(ol->data);
2849             new_items = g_slist_delete_link(new_items, ol);
2850         }
2851         ol = nl;
2852     }
2853     /* we have two copies of lists now, compare them and emit events */
2854     ol = items;
2855     de_name = g_getenv("XDG_CURRENT_DESKTOP");
2856     if(de_name)
2857         de_flag = menu_cache_get_desktop_env_flag(cache, de_name);
2858     else
2859         de_flag = (guint32)-1;
2860     while (ol)
2861     {
2862         for (nl = new_items; nl; nl = nl->next)
2863             if (strcmp(menu_cache_item_get_id(ol->data),
2864                        menu_cache_item_get_id(nl->data)) == 0)
2865                 break; /* the same id found */
2866         if (nl)
2867         {
2868             /* check if any visible attribute of it was changed */
2869             if (g_strcmp0(menu_cache_item_get_name(ol->data),
2870                           menu_cache_item_get_name(nl->data)) == 0 ||
2871                 g_strcmp0(menu_cache_item_get_icon(ol->data),
2872                           menu_cache_item_get_icon(nl->data)) == 0 ||
2873                 menu_cache_app_get_is_visible(ol->data, de_flag) !=
2874                                 menu_cache_app_get_is_visible(nl->data, de_flag))
2875             {
2876                 file = _fm_vfs_menu_resolve_relative_path(G_FILE(mon->file),
2877                                              menu_cache_item_get_id(nl->data));
2878                 g_file_monitor_emit_event(G_FILE_MONITOR(mon), file, NULL,
2879                                           G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED);
2880                 g_object_unref(file);
2881             }
2882             /* free both new and old from the list */
2883             menu_cache_item_unref(nl->data);
2884             new_items = g_slist_delete_link(new_items, nl);
2885             nl = ol->next; /* use 'nl' as storage */
2886             menu_cache_item_unref(ol->data);
2887             items = g_slist_delete_link(items, ol);
2888             ol = nl;
2889         }
2890         else /* id not found (removed), go to next */
2891             ol = ol->next;
2892     }
2893     /* emit events for removed files */
2894     while (items)
2895     {
2896         file = _fm_vfs_menu_resolve_relative_path(G_FILE(mon->file),
2897                                              menu_cache_item_get_id(items->data));
2898         g_file_monitor_emit_event(G_FILE_MONITOR(mon), file, NULL,
2899                                   G_FILE_MONITOR_EVENT_DELETED);
2900         g_object_unref(file);
2901         menu_cache_item_unref(items->data);
2902         items = g_slist_delete_link(items, items);
2903     }
2904     /* emit events for added files */
2905     while (new_items)
2906     {
2907         file = _fm_vfs_menu_resolve_relative_path(G_FILE(mon->file),
2908                                      menu_cache_item_get_id(new_items->data));
2909         g_file_monitor_emit_event(G_FILE_MONITOR(mon), file, NULL,
2910                                   G_FILE_MONITOR_EVENT_CREATED);
2911         g_object_unref(file);
2912         menu_cache_item_unref(new_items->data);
2913         new_items = g_slist_delete_link(new_items, new_items);
2914     }
2915 }
2916 
_fm_vfs_menu_monitor_dir_real(gpointer data)2917 static gboolean _fm_vfs_menu_monitor_dir_real(gpointer data)
2918 {
2919     FmVfsMenuMainThreadData *init = data;
2920     FmMenuVFileMonitor *mon;
2921 #if !MENU_CACHE_CHECK_VERSION(0, 4, 0)
2922     MenuCacheItem *dir;
2923 #endif
2924 
2925     init->result = NULL;
2926     if(g_cancellable_set_error_if_cancelled(init->cancellable, init->error))
2927         return FALSE;
2928     /* open menu cache instance */
2929     mon = _fm_menu_vfile_monitor_new();
2930     if(mon == NULL) /* out of memory! */
2931         return FALSE;
2932     mon->file = FM_MENU_VFILE(g_object_ref(init->destination));
2933     mon->cache = _get_menu_cache(init->error);
2934     if(mon->cache == NULL)
2935         goto _fail;
2936     /* check if requested path exists within cache */
2937 #if MENU_CACHE_CHECK_VERSION(0, 4, 0)
2938     if(mon->file->path)
2939         mon->item = _vfile_path_to_menu_cache_item(mon->cache, mon->file->path);
2940     else
2941         mon->item = MENU_CACHE_ITEM(menu_cache_dup_root_dir(mon->cache));
2942     if(mon->item == NULL || menu_cache_item_get_type(mon->item) != MENU_CACHE_TYPE_DIR)
2943 #else
2944     if(mon->file->path)
2945         dir = _vfile_path_to_menu_cache_item(mon->cache, mon->file->path);
2946     else
2947         dir = MENU_CACHE_ITEM(menu_cache_get_root_dir(mon->cache));
2948     if(dir == NULL)
2949 #endif
2950     {
2951         g_set_error(init->error, G_IO_ERROR, G_IO_ERROR_FAILED,
2952                     _("FmMenuVFileMonitor: folder '%s' not found in menu cache"),
2953                     mon->file->path);
2954         goto _fail;
2955     }
2956 #if !MENU_CACHE_CHECK_VERSION(0, 4, 0)
2957     /* for old libmenu-cache we have no choice but copy all the data right now */
2958     mon->items = g_slist_copy_deep(menu_cache_dir_get_children(MENU_CACHE_DIR(dir)),
2959                                    (GCopyFunc)menu_cache_item_ref, NULL);
2960 #endif
2961     if(g_cancellable_set_error_if_cancelled(init->cancellable, init->error))
2962         goto _fail;
2963     /* current directory contents belong to mon->item now */
2964     /* attach reload notify handler */
2965     mon->notifier = menu_cache_add_reload_notify(mon->cache,
2966                                                  &_reload_notify_handler, mon);
2967     init->result = mon;
2968     return TRUE;
2969 
2970 _fail:
2971     g_object_unref(mon);
2972     return FALSE;
2973 }
2974 
_fm_vfs_menu_monitor_dir(GFile * file,GFileMonitorFlags flags,GCancellable * cancellable,GError ** error)2975 static GFileMonitor *_fm_vfs_menu_monitor_dir(GFile *file,
2976                                               GFileMonitorFlags flags,
2977                                               GCancellable *cancellable,
2978                                               GError **error)
2979 {
2980     FmVfsMenuMainThreadData enu;
2981 
2982     /* g_debug("_fm_vfs_menu_monitor_dir %s", FM_MENU_VFILE(file)->path); */
2983     enu.cancellable = cancellable;
2984     enu.error = error;
2985     // enu.flags = flags;
2986     enu.destination = FM_MENU_VFILE(file);
2987     RUN_WITH_MENU_CACHE(_fm_vfs_menu_monitor_dir_real, &enu);
2988     return (GFileMonitor*)enu.result;
2989 }
2990 
_fm_vfs_menu_monitor_file(GFile * file,GFileMonitorFlags flags,GCancellable * cancellable,GError ** error)2991 static GFileMonitor *_fm_vfs_menu_monitor_file(GFile *file,
2992                                                GFileMonitorFlags flags,
2993                                                GCancellable *cancellable,
2994                                                GError **error)
2995 {
2996     ERROR_UNSUPPORTED(error);
2997     return NULL;
2998 }
2999 
3000 #if GLIB_CHECK_VERSION(2, 22, 0)
_fm_vfs_menu_open_readwrite(GFile * file,GCancellable * cancellable,GError ** error)3001 static GFileIOStream *_fm_vfs_menu_open_readwrite(GFile *file,
3002                                                   GCancellable *cancellable,
3003                                                   GError **error)
3004 {
3005     ERROR_UNSUPPORTED(error);
3006     return NULL;
3007 }
3008 
_fm_vfs_menu_create_readwrite(GFile * file,GFileCreateFlags flags,GCancellable * cancellable,GError ** error)3009 static GFileIOStream *_fm_vfs_menu_create_readwrite(GFile *file,
3010                                                     GFileCreateFlags flags,
3011                                                     GCancellable *cancellable,
3012                                                     GError **error)
3013 {
3014     ERROR_UNSUPPORTED(error);
3015     return NULL;
3016 }
3017 
_fm_vfs_menu_replace_readwrite(GFile * file,const char * etag,gboolean make_backup,GFileCreateFlags flags,GCancellable * cancellable,GError ** error)3018 static GFileIOStream *_fm_vfs_menu_replace_readwrite(GFile *file,
3019                                                      const char *etag,
3020                                                      gboolean make_backup,
3021                                                      GFileCreateFlags flags,
3022                                                      GCancellable *cancellable,
3023                                                      GError **error)
3024 {
3025     ERROR_UNSUPPORTED(error);
3026     return NULL;
3027 }
3028 #endif /* Glib >= 2.22 */
3029 
fm_menu_g_file_init(GFileIface * iface)3030 static void fm_menu_g_file_init(GFileIface *iface)
3031 {
3032     GFileAttributeInfoList *list;
3033 
3034     iface->dup = _fm_vfs_menu_dup;
3035     iface->hash = _fm_vfs_menu_hash;
3036     iface->equal = _fm_vfs_menu_equal;
3037     iface->is_native = _fm_vfs_menu_is_native;
3038     iface->has_uri_scheme = _fm_vfs_menu_has_uri_scheme;
3039     iface->get_uri_scheme = _fm_vfs_menu_get_uri_scheme;
3040     iface->get_basename = _fm_vfs_menu_get_basename;
3041     iface->get_path = _fm_vfs_menu_get_path;
3042     iface->get_uri = _fm_vfs_menu_get_uri;
3043     iface->get_parse_name = _fm_vfs_menu_get_parse_name;
3044     iface->get_parent = _fm_vfs_menu_get_parent;
3045     iface->prefix_matches = _fm_vfs_menu_prefix_matches;
3046     iface->get_relative_path = _fm_vfs_menu_get_relative_path;
3047     iface->resolve_relative_path = _fm_vfs_menu_resolve_relative_path;
3048     iface->get_child_for_display_name = _fm_vfs_menu_get_child_for_display_name;
3049     iface->enumerate_children = _fm_vfs_menu_enumerate_children;
3050     iface->query_info = _fm_vfs_menu_query_info;
3051     iface->query_filesystem_info = _fm_vfs_menu_query_filesystem_info;
3052     iface->find_enclosing_mount = _fm_vfs_menu_find_enclosing_mount;
3053     iface->set_display_name = _fm_vfs_menu_set_display_name;
3054     iface->query_settable_attributes = _fm_vfs_menu_query_settable_attributes;
3055     iface->query_writable_namespaces = _fm_vfs_menu_query_writable_namespaces;
3056     iface->set_attribute = _fm_vfs_menu_set_attribute;
3057     iface->set_attributes_from_info = _fm_vfs_menu_set_attributes_from_info;
3058     iface->read_fn = _fm_vfs_menu_read_fn;
3059     iface->append_to = _fm_vfs_menu_append_to;
3060     iface->create = _fm_vfs_menu_create;
3061     iface->replace = _fm_vfs_menu_replace;
3062     iface->delete_file = _fm_vfs_menu_delete_file;
3063     iface->trash = _fm_vfs_menu_trash;
3064     iface->make_directory = _fm_vfs_menu_make_directory;
3065     iface->make_symbolic_link = _fm_vfs_menu_make_symbolic_link;
3066     iface->copy = _fm_vfs_menu_copy;
3067     iface->move = _fm_vfs_menu_move;
3068     iface->monitor_dir = _fm_vfs_menu_monitor_dir;
3069     iface->monitor_file = _fm_vfs_menu_monitor_file;
3070 #if GLIB_CHECK_VERSION(2, 22, 0)
3071     iface->open_readwrite = _fm_vfs_menu_open_readwrite;
3072     iface->create_readwrite = _fm_vfs_menu_create_readwrite;
3073     iface->replace_readwrite = _fm_vfs_menu_replace_readwrite;
3074     iface->supports_thread_contexts = TRUE;
3075 #endif /* Glib >= 2.22 */
3076 
3077     list = g_file_attribute_info_list_new();
3078     g_file_attribute_info_list_add(list, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
3079                                    G_FILE_ATTRIBUTE_TYPE_BOOLEAN,
3080                                    G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
3081     g_file_attribute_info_list_add(list, G_FILE_ATTRIBUTE_STANDARD_ICON,
3082                                    G_FILE_ATTRIBUTE_TYPE_OBJECT,
3083                                    G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
3084     _fm_vfs_menu_settable_attributes = list;
3085 }
3086 
3087 
3088 /* ---- FmFile implementation ---- */
_fm_vfs_menu_wants_incremental(GFile * file)3089 static gboolean _fm_vfs_menu_wants_incremental(GFile* file)
3090 {
3091     return FALSE;
3092 }
3093 
fm_menu_fm_file_init(FmFileInterface * iface)3094 static void fm_menu_fm_file_init(FmFileInterface *iface)
3095 {
3096     iface->wants_incremental = _fm_vfs_menu_wants_incremental;
3097 }
3098 
3099 
3100 /* ---- interface for loading ---- */
_fm_vfs_menu_new_for_uri(const char * uri)3101 GFile *_fm_vfs_menu_new_for_uri(const char *uri)
3102 {
3103     FmMenuVFile *item = _fm_menu_vfile_new();
3104 
3105     if(uri == NULL)
3106         uri = "";
3107     /* skip menu:/ */
3108     if(g_ascii_strncasecmp(uri, "menu:", 5) == 0)
3109         uri += 5;
3110     while(*uri == '/')
3111         uri++;
3112     /* skip "applications/" or "applications.menu/" */
3113     if(g_ascii_strncasecmp(uri, "applications", 12) == 0)
3114     {
3115         uri += 12;
3116         if(g_ascii_strncasecmp(uri, ".menu", 5) == 0)
3117             uri += 5;
3118     }
3119     while(*uri == '/') /* skip starting slashes */
3120         uri++;
3121     /* save the rest of path, NULL means the root path */
3122     if(*uri)
3123     {
3124         char *end;
3125 
3126         item->path = g_strdup(uri);
3127         for(end = item->path + strlen(item->path); end > item->path; end--)
3128             if(end[-1] == '/') /* skip trailing slashes */
3129                 end[-1] = '\0';
3130             else
3131                 break;
3132     }
3133     /* g_debug("_fm_vfs_menu_new_for_uri %s -> %s", uri, item->path); */
3134     return (GFile*)item;
3135 }
3136