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