1 /* vim: set ts=2 sw=2 et: */
2
3 /*
4 * Copyright (C) 2002, 2004 Red Hat, Inc.
5 * Copyright (C) 2006-2008 Vincent Untz
6 *
7 * Program written by Havoc Pennington <hp@pobox.com>
8 * Ray Strode <rstrode@redhat.com>
9 * Vincent Untz <vuntz@gnome.org>
10 *
11 * update-desktop-database is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General Public License as
13 * published by the Free Software Foundation; either version 2 of the
14 * License, or (at your option) any later version.
15 *
16 * update-desktop-database is distributed in the hope that it will be
17 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
18 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with update-desktop-database; see the file COPYING. If not,
23 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite
24 * 330, Boston, MA 02111-1307, USA.
25 */
26
27 #include <config.h>
28
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <glib/gi18n.h>
32
33 #include <stdlib.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <locale.h>
38
39 #include "keyfileutils.h"
40 #include "validate.h"
41
42 static gboolean edit_mode = FALSE;
43 static const char** args = NULL;
44 static gboolean delete_original = FALSE;
45 static gboolean rebuild_mime_info_cache = FALSE;
46 static char *vendor_name = NULL;
47 static char *target_dir = NULL;
48 static GSList *edit_actions = NULL;
49 static mode_t permissions = 0644;
50
51 typedef enum
52 {
53 DFU_SET_KEY_BUILDING, /* temporary type to create an action in multiple steps */
54 DFU_SET_KEY,
55 DFU_REMOVE_KEY,
56 DFU_ADD_TO_LIST,
57 DFU_REMOVE_FROM_LIST,
58 DFU_COPY_KEY
59 } DfuEditActionType;
60
61 typedef struct
62 {
63 DfuEditActionType type;
64 char *key;
65 char *action_value;
66 } DfuEditAction;
67
68 static DfuEditAction *
dfu_edit_action_new(DfuEditActionType type,const char * key,const char * action_value)69 dfu_edit_action_new (DfuEditActionType type,
70 const char *key,
71 const char *action_value)
72 {
73 DfuEditAction *action;
74
75 action = g_slice_new0 (DfuEditAction);
76 action->type = type;
77 action->key = g_strdup (key);
78 action->action_value = g_strdup (action_value);
79
80 return action;
81 }
82
83 static void
dfu_edit_action_free(DfuEditAction * action)84 dfu_edit_action_free (DfuEditAction *action)
85 {
86 g_assert (action != NULL);
87
88 g_free (action->key);
89 g_free (action->action_value);
90
91 g_slice_free (DfuEditAction, action);
92 }
93
94 static gboolean
files_are_the_same(const char * first,const char * second)95 files_are_the_same (const char *first,
96 const char *second)
97 {
98 /* This check gets confused by hard links.
99 * but it's too annoying to check if two
100 * paths are the same (though I'm sure there's a
101 * "path canonicalizer" I should be using...)
102 */
103
104 struct stat first_sb;
105 struct stat second_sb;
106
107 if (stat (first, &first_sb) < 0)
108 {
109 g_printerr (_("Could not stat \"%s\": %s\n"), first, g_strerror (errno));
110 return TRUE;
111 }
112
113 if (stat (second, &second_sb) < 0)
114 {
115 g_printerr (_("Could not stat \"%s\": %s\n"), first, g_strerror (errno));
116 return TRUE;
117 }
118
119 return ((first_sb.st_dev == second_sb.st_dev) &&
120 (first_sb.st_ino == second_sb.st_ino) &&
121 /* Broken paranoia in case an OS doesn't use inodes or something */
122 (first_sb.st_size == second_sb.st_size) &&
123 (first_sb.st_mtime == second_sb.st_mtime));
124 }
125
126 static gboolean
rebuild_cache(const char * dir,GError ** err)127 rebuild_cache (const char *dir,
128 GError **err)
129 {
130 GError *spawn_error;
131 char *argv[4] = { "update-desktop-database", "-q", (char *) dir, NULL };
132 int exit_status;
133 gboolean retval;
134
135 spawn_error = NULL;
136
137 retval = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
138 NULL, NULL, &exit_status, &spawn_error);
139
140 if (spawn_error != NULL)
141 {
142 g_propagate_error (err, spawn_error);
143 return FALSE;
144 }
145
146 return exit_status == 0 && retval;
147 }
148
149 static void
process_one_file(const char * filename,GError ** err)150 process_one_file (const char *filename,
151 GError **err)
152 {
153 char *new_filename;
154 GKeyFile *kf = NULL;
155 GError *rebuild_error;
156 GSList *tmp;
157
158 kf = g_key_file_new ();
159 if (!g_key_file_load_from_file (kf, filename,
160 G_KEY_FILE_KEEP_COMMENTS|
161 G_KEY_FILE_KEEP_TRANSLATIONS,
162 err)) {
163 g_key_file_free (kf);
164 return;
165 }
166
167 if (!desktop_file_fixup (kf, filename)) {
168 g_key_file_free (kf);
169 g_set_error (err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE,
170 _("Failed to fix the content of the desktop file"));
171 return;
172 }
173
174 /* Mark file as having been processed by us, so automated
175 * tools can check that desktop files went through our
176 * munging
177 */
178 g_key_file_set_string (kf, GROUP_DESKTOP_ENTRY,
179 "X-Desktop-File-Install-Version", VERSION);
180
181 tmp = edit_actions;
182 while (tmp != NULL)
183 {
184 DfuEditAction *action = tmp->data;
185
186 switch (action->type)
187 {
188 case DFU_SET_KEY:
189 g_key_file_set_string (kf, GROUP_DESKTOP_ENTRY,
190 action->key, action->action_value);
191 dfu_key_file_drop_locale_strings (kf, GROUP_DESKTOP_ENTRY,
192 action->key);
193 break;
194 case DFU_REMOVE_KEY:
195 g_key_file_remove_key (kf, GROUP_DESKTOP_ENTRY,
196 action->key, NULL);
197 dfu_key_file_drop_locale_strings (kf, GROUP_DESKTOP_ENTRY,
198 action->key);
199 break;
200 case DFU_ADD_TO_LIST:
201 dfu_key_file_merge_list (kf, GROUP_DESKTOP_ENTRY,
202 action->key, action->action_value);
203 break;
204 case DFU_REMOVE_FROM_LIST:
205 dfu_key_file_remove_list (kf, GROUP_DESKTOP_ENTRY,
206 action->key, action->action_value);
207 break;
208 case DFU_COPY_KEY:
209 dfu_key_file_copy_key (kf, GROUP_DESKTOP_ENTRY, action->key,
210 GROUP_DESKTOP_ENTRY, action->action_value);
211 break;
212 default:
213 g_assert_not_reached ();
214 }
215
216 tmp = tmp->next;
217 }
218
219 if (edit_mode)
220 {
221 new_filename = g_strdup (filename);
222 }
223 else
224 {
225 char *basename = g_path_get_basename (filename);
226
227 if (vendor_name && !g_str_has_prefix (basename, vendor_name))
228 {
229 char *new_base;
230 new_base = g_strconcat (vendor_name, "-", basename, NULL);
231 new_filename = g_build_filename (target_dir, new_base, NULL);
232 g_free (new_base);
233 }
234 else
235 {
236 new_filename = g_build_filename (target_dir, basename, NULL);
237 }
238
239 g_free (basename);
240 }
241
242 if (!dfu_key_file_to_path (kf, new_filename, err)) {
243 g_key_file_free (kf);
244 g_free (new_filename);
245 return;
246 }
247
248 g_key_file_free (kf);
249
250 /* Load and validate the file we just wrote */
251 if (!desktop_file_validate (new_filename, FALSE, TRUE, TRUE))
252 {
253 g_set_error (err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE,
254 _("Failed to validate the created desktop file"));
255
256 if (!files_are_the_same (filename, new_filename))
257 g_unlink (new_filename);
258
259 g_free (new_filename);
260 return;
261 }
262
263 if (!edit_mode)
264 {
265 if (g_chmod (new_filename, permissions) < 0)
266 {
267 g_set_error (err, G_FILE_ERROR,
268 g_file_error_from_errno (errno),
269 _("Failed to set permissions %o on \"%s\": %s"),
270 permissions, new_filename, g_strerror (errno));
271
272 if (!files_are_the_same (filename, new_filename))
273 g_unlink (new_filename);
274
275 g_free (new_filename);
276 return;
277 }
278
279 if (delete_original &&
280 !files_are_the_same (filename, new_filename))
281 {
282 if (g_unlink (filename) < 0)
283 g_printerr (_("Error removing original file \"%s\": %s\n"),
284 filename, g_strerror (errno));
285 }
286 }
287
288 if (rebuild_mime_info_cache)
289 {
290 rebuild_error = NULL;
291 rebuild_cache (target_dir, &rebuild_error);
292
293 if (rebuild_error != NULL)
294 g_propagate_error (err, rebuild_error);
295 }
296
297 g_free (new_filename);
298 }
299
300 static gboolean parse_install_options_callback (const gchar *option_name,
301 const gchar *value,
302 gpointer data,
303 GError **error);
304
305 static gboolean parse_edit_options_callback (const gchar *option_name,
306 const gchar *value,
307 gpointer data,
308 GError **error);
309
310
311 static const GOptionEntry main_options[] = {
312 {
313 "rebuild-mime-info-cache",
314 '\0',
315 '\0',
316 G_OPTION_ARG_NONE,
317 &rebuild_mime_info_cache,
318 N_("Rebuild the MIME types application database after processing desktop files"),
319 NULL
320 },
321 {
322 "edit-mode",
323 '\0',
324 G_OPTION_FLAG_HIDDEN, /* just for development purpose */
325 G_OPTION_ARG_NONE,
326 &edit_mode,
327 N_("Force edit mode"),
328 NULL
329 },
330 { G_OPTION_REMAINING,
331 0,
332 0,
333 G_OPTION_ARG_FILENAME_ARRAY,
334 &args,
335 NULL,
336 N_("[FILE...]") },
337 {
338 NULL
339 }
340 };
341
342 static const GOptionEntry install_options[] = {
343 {
344 #define OPTION_DIR "dir"
345 OPTION_DIR,
346 '\0',
347 '\0',
348 G_OPTION_ARG_CALLBACK,
349 parse_install_options_callback,
350 N_("Install desktop files to the DIR directory"),
351 N_("DIR")
352 },
353 {
354 #define OPTION_MODE "mode"
355 OPTION_MODE,
356 'm',
357 '\0',
358 G_OPTION_ARG_CALLBACK,
359 parse_install_options_callback,
360 N_("Set the permissions of the destination files to MODE"),
361 N_("MODE")
362 },
363 {
364 #define OPTION_VENDOR "vendor"
365 OPTION_VENDOR,
366 '\0',
367 '\0',
368 G_OPTION_ARG_CALLBACK,
369 parse_install_options_callback,
370 N_("Add a vendor prefix to the desktop files, if not already present"),
371 N_("VENDOR")
372 },
373 {
374 "delete-original",
375 '\0',
376 '\0',
377 G_OPTION_ARG_NONE,
378 &delete_original,
379 N_("Delete the source desktop files, leaving only the target files (effectively \"renames\" the desktop files)"),
380 NULL
381 },
382 {
383 NULL
384 }
385 };
386
387 static const GOptionEntry edit_options[] = {
388 {
389 #define OPTION_SET_KEY "set-key"
390 OPTION_SET_KEY,
391 '\0',
392 '\0',
393 G_OPTION_ARG_CALLBACK,
394 parse_edit_options_callback,
395 N_("Set the KEY key to VALUE passed to next --set-value option"),
396 N_("KEY")
397 },
398 {
399 #define OPTION_SET_VALUE "set-value"
400 OPTION_SET_VALUE,
401 '\0',
402 '\0',
403 G_OPTION_ARG_CALLBACK,
404 parse_edit_options_callback,
405 N_("Set the KEY key from previous --set-key option to VALUE"),
406 N_("VALUE")
407 },
408 {
409 #define OPTION_SET_NAME "set-name"
410 OPTION_SET_NAME,
411 '\0',
412 '\0',
413 G_OPTION_ARG_CALLBACK,
414 parse_edit_options_callback,
415 N_("Set the \"Name\" key to NAME"),
416 N_("NAME")
417 },
418 {
419 #define OPTION_COPY_GENERIC_NAME "copy-generic-name-to-name"
420 OPTION_COPY_GENERIC_NAME,
421 '\0',
422 G_OPTION_FLAG_NO_ARG,
423 G_OPTION_ARG_CALLBACK,
424 parse_edit_options_callback,
425 N_("Copy the value of the \"GenericName\" key to the \"Name\" key"),
426 NULL
427 },
428 {
429 #define OPTION_SET_GENERIC_NAME "set-generic-name"
430 OPTION_SET_GENERIC_NAME,
431 '\0',
432 '\0',
433 G_OPTION_ARG_CALLBACK,
434 parse_edit_options_callback,
435 N_("Set the \"GenericName\" key to GENERIC-NAME"),
436 N_("GENERIC-NAME")
437 },
438 {
439 #define OPTION_COPY_NAME "copy-name-to-generic-name"
440 OPTION_COPY_NAME,
441 '\0',
442 G_OPTION_FLAG_NO_ARG,
443 G_OPTION_ARG_CALLBACK,
444 parse_edit_options_callback,
445 N_("Copy the value of the \"Name\" key to the \"GenericName\" key"),
446 NULL
447 },
448 {
449 #define OPTION_SET_COMMENT "set-comment"
450 OPTION_SET_COMMENT,
451 '\0',
452 '\0',
453 G_OPTION_ARG_CALLBACK,
454 parse_edit_options_callback,
455 N_("Set the \"Comment\" key to COMMENT"),
456 N_("COMMENT")
457 },
458 {
459 #define OPTION_SET_ICON "set-icon"
460 OPTION_SET_ICON,
461 '\0',
462 '\0',
463 G_OPTION_ARG_CALLBACK,
464 parse_edit_options_callback,
465 N_("Set the \"Icon\" key to ICON"),
466 N_("ICON")
467 },
468 {
469 #define OPTION_ADD_CATEGORY "add-category"
470 OPTION_ADD_CATEGORY,
471 '\0',
472 '\0',
473 G_OPTION_ARG_CALLBACK,
474 parse_edit_options_callback,
475 N_("Add CATEGORY to the list of categories"),
476 N_("CATEGORY")
477 },
478 {
479 #define OPTION_REMOVE_CATEGORY "remove-category"
480 OPTION_REMOVE_CATEGORY,
481 '\0',
482 '\0',
483 G_OPTION_ARG_CALLBACK,
484 parse_edit_options_callback,
485 N_("Remove CATEGORY from the list of categories"),
486 N_("CATEGORY")
487 },
488 {
489 #define OPTION_ADD_MIME_TYPE "add-mime-type"
490 OPTION_ADD_MIME_TYPE,
491 '\0',
492 '\0',
493 G_OPTION_ARG_CALLBACK,
494 parse_edit_options_callback,
495 N_("Add MIME-TYPE to the list of MIME types"),
496 N_("MIME-TYPE")
497 },
498 {
499 #define OPTION_REMOVE_MIME_TYPE "remove-mime-type"
500 OPTION_REMOVE_MIME_TYPE,
501 '\0',
502 '\0',
503 G_OPTION_ARG_CALLBACK,
504 parse_edit_options_callback,
505 N_("Remove MIME-TYPE from the list of MIME types"),
506 N_("MIME-TYPE")
507 },
508 {
509 #define OPTION_ADD_ONLY_SHOW_IN "add-only-show-in"
510 OPTION_ADD_ONLY_SHOW_IN,
511 '\0',
512 '\0',
513 G_OPTION_ARG_CALLBACK,
514 parse_edit_options_callback,
515 N_("Add ENVIRONMENT to the list of desktop environment where the desktop files should be displayed"),
516 N_("ENVIRONMENT")
517 },
518 {
519 #define OPTION_REMOVE_ONLY_SHOW_IN "remove-only-show-in"
520 OPTION_REMOVE_ONLY_SHOW_IN,
521 '\0',
522 '\0',
523 G_OPTION_ARG_CALLBACK,
524 parse_edit_options_callback,
525 N_("Remove ENVIRONMENT from the list of desktop environment where the desktop files should be displayed"),
526 N_("ENVIRONMENT")
527 },
528 {
529 #define OPTION_ADD_NOT_SHOW_IN "add-not-show-in"
530 OPTION_ADD_NOT_SHOW_IN,
531 '\0',
532 '\0',
533 G_OPTION_ARG_CALLBACK,
534 parse_edit_options_callback,
535 N_("Add ENVIRONMENT to the list of desktop environment where the desktop files should not be displayed"),
536 N_("ENVIRONMENT")
537 },
538 {
539 #define OPTION_REMOVE_NOT_SHOW_IN "remove-not-show-in"
540 OPTION_REMOVE_NOT_SHOW_IN,
541 '\0',
542 '\0',
543 G_OPTION_ARG_CALLBACK,
544 parse_edit_options_callback,
545 N_("Remove ENVIRONMENT from the list of desktop environment where the desktop files should not be displayed"),
546 N_("ENVIRONMENT")
547 },
548 {
549 #define OPTION_REMOVE_KEY "remove-key"
550 OPTION_REMOVE_KEY,
551 '\0',
552 '\0',
553 G_OPTION_ARG_CALLBACK,
554 parse_edit_options_callback,
555 N_("Remove the KEY key from the desktop files, if present"),
556 N_("KEY")
557 },
558 {
559 NULL
560 }
561 };
562
563 static gboolean
parse_install_options_callback(const gchar * option_name,const gchar * value,gpointer data,GError ** error)564 parse_install_options_callback (const gchar *option_name,
565 const gchar *value,
566 gpointer data,
567 GError **error)
568 {
569 /* skip "-" or "--" */
570 option_name++;
571 if (*option_name == '-')
572 option_name++;
573
574 if (strcmp (OPTION_DIR, option_name) == 0)
575 {
576 if (target_dir)
577 {
578 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
579 _("Can only specify --dir once"));
580 return FALSE;
581 }
582
583 target_dir = g_strdup (value);
584 }
585
586 else if (strcmp (OPTION_MODE, option_name) == 0 ||
587 strcmp ("m", option_name) == 0)
588 {
589 unsigned long ul;
590 char *end;
591
592 end = NULL;
593 ul = strtoul (value, &end, 8);
594 if (*value && end && *end == '\0')
595 permissions = ul;
596 else
597 {
598 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
599 _("Could not parse mode string \"%s\""), value);
600
601 return FALSE;
602 }
603 }
604
605 else if (strcmp (OPTION_VENDOR, option_name) == 0)
606 {
607 if (vendor_name)
608 {
609 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
610 _("Can only specify --vendor once"));
611 return FALSE;
612 }
613
614 vendor_name = g_strdup (value);
615 }
616
617 else
618 {
619 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
620 _("Unknown option \"%s\""), option_name);
621
622 return FALSE;
623 }
624
625 return TRUE;
626 }
627
628 static gboolean
parse_edit_options_callback(const gchar * option_name,const gchar * value,gpointer data,GError ** error)629 parse_edit_options_callback (const gchar *option_name,
630 const gchar *value,
631 gpointer data,
632 GError **error)
633 {
634 /* Note: we prepend actions to the list, so we'll need to reverse it later */
635
636 DfuEditAction *action;
637 char **list;
638 int i;
639
640 /* skip "-" or "--" */
641 option_name++;
642 if (*option_name == '-')
643 option_name++;
644
645 #define PARSE_OPTION_LIST(type, key) \
646 do { \
647 list = g_strsplit (value, ";", 0); \
648 for (i = 0; list[i]; i++) \
649 { \
650 if (*list[i] == '\0') \
651 continue; \
652 \
653 action = dfu_edit_action_new (type, key, list[i]); \
654 edit_actions = g_slist_prepend (edit_actions, action); \
655 } \
656 } while (0)
657
658 if (strcmp (OPTION_SET_KEY, option_name) == 0)
659 {
660 action = dfu_edit_action_new (DFU_SET_KEY_BUILDING, value, NULL);
661 edit_actions = g_slist_prepend (edit_actions, action);
662 }
663
664 else if (strcmp (OPTION_SET_VALUE, option_name) == 0)
665 {
666 gboolean handled = FALSE;
667
668 if (edit_actions != NULL)
669 {
670 action = edit_actions->data;
671 if (action->type == DFU_SET_KEY_BUILDING)
672 {
673 action->type = DFU_SET_KEY;
674 action->action_value = g_strdup (value);
675
676 handled = TRUE;
677 }
678 }
679
680 if (!handled)
681 {
682 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
683 _("Option \"--%s\" used without a prior \"--%s\" option"),
684 OPTION_SET_VALUE, OPTION_SET_KEY);
685
686 return FALSE;
687 }
688 }
689
690 else if (strcmp (OPTION_SET_NAME, option_name) == 0)
691 {
692 action = dfu_edit_action_new (DFU_SET_KEY, "Name", value);
693 edit_actions = g_slist_prepend (edit_actions, action);
694 }
695
696 else if (strcmp (OPTION_COPY_GENERIC_NAME, option_name) == 0)
697 {
698 action = dfu_edit_action_new (DFU_COPY_KEY, "GenericName", "Name");
699 edit_actions = g_slist_prepend (edit_actions, action);
700 }
701
702 else if (strcmp (OPTION_SET_GENERIC_NAME, option_name) == 0)
703 {
704 action = dfu_edit_action_new (DFU_SET_KEY, "GenericName", value);
705 edit_actions = g_slist_prepend (edit_actions, action);
706 }
707
708 else if (strcmp (OPTION_COPY_NAME, option_name) == 0)
709 {
710 action = dfu_edit_action_new (DFU_COPY_KEY, "Name", "GenericName");
711 edit_actions = g_slist_prepend (edit_actions, action);
712 }
713
714 else if (strcmp (OPTION_SET_COMMENT, option_name) == 0)
715 {
716 action = dfu_edit_action_new (DFU_SET_KEY, "Comment", value);
717 edit_actions = g_slist_prepend (edit_actions, action);
718 }
719
720 else if (strcmp (OPTION_SET_ICON, option_name) == 0)
721 {
722 action = dfu_edit_action_new (DFU_SET_KEY, "Icon", value);
723 edit_actions = g_slist_prepend (edit_actions, action);
724 }
725
726 else if (strcmp (OPTION_ADD_CATEGORY, option_name) == 0)
727 {
728 PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "Categories");
729 }
730
731 else if (strcmp (OPTION_REMOVE_CATEGORY, option_name) == 0)
732 {
733 PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "Categories");
734 }
735
736 else if (strcmp (OPTION_ADD_MIME_TYPE, option_name) == 0)
737 {
738 PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "MimeType");
739 }
740
741 else if (strcmp (OPTION_REMOVE_MIME_TYPE, option_name) == 0)
742 {
743 PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "MimeType");
744 }
745
746 else if (strcmp (OPTION_ADD_ONLY_SHOW_IN, option_name) == 0)
747 {
748 PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "OnlyShowIn");
749 }
750
751 else if (strcmp (OPTION_REMOVE_ONLY_SHOW_IN, option_name) == 0)
752 {
753 PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "OnlyShowIn");
754 }
755
756 else if (strcmp (OPTION_ADD_NOT_SHOW_IN, option_name) == 0)
757 {
758 PARSE_OPTION_LIST (DFU_ADD_TO_LIST, "NotShowIn");
759 }
760
761 else if (strcmp (OPTION_REMOVE_NOT_SHOW_IN, option_name) == 0)
762 {
763 PARSE_OPTION_LIST (DFU_REMOVE_FROM_LIST, "NotShowIn");
764 }
765
766 else if (strcmp (OPTION_REMOVE_KEY, option_name) == 0)
767 {
768 action = dfu_edit_action_new (DFU_REMOVE_KEY, value, NULL);
769 edit_actions = g_slist_prepend (edit_actions, action);
770 }
771
772 else
773 {
774 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
775 _("Unknown option \"%s\""), option_name);
776
777 return FALSE;
778 }
779
780 return TRUE;
781 }
782
783 static gboolean
check_no_building_in_edit_actions(GError ** error)784 check_no_building_in_edit_actions (GError **error)
785 {
786 GSList *tmp = edit_actions;
787
788 while (tmp != NULL)
789 {
790 /* If we have an action that is BUILDING, then it means that we had a
791 * --set-key not followed by a --set-value. This can happen when:
792 * + the very last argument was --set-key
793 * + --set-key was followed by another editing option
794 * In both cases, that's bad as what we want is a --set-value following
795 * each --set-key.
796 */
797 DfuEditAction *action = tmp->data;
798
799 if (action->type == DFU_SET_KEY_BUILDING)
800 {
801 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
802 _("Option \"--%s\" used without a following \"--%s\" option"),
803 OPTION_SET_KEY, OPTION_SET_VALUE);
804
805 return FALSE;
806 }
807
808 tmp = tmp->next;
809 }
810
811 return TRUE;
812 }
813
814 static gboolean
post_parse_edit_options_callback(GOptionContext * context,GOptionGroup * group,gpointer data,GError ** error)815 post_parse_edit_options_callback (GOptionContext *context,
816 GOptionGroup *group,
817 gpointer data,
818 GError **error)
819 {
820 if (!check_no_building_in_edit_actions (error))
821 return FALSE;
822
823 /* Reverse list we created by prepending elements */
824 edit_actions = g_slist_reverse (edit_actions);
825
826 return TRUE;
827 }
828
829 int
main(int argc,char ** argv)830 main (int argc, char **argv)
831 {
832 GOptionContext *context;
833 GOptionGroup *group;
834 GError* err = NULL;
835 int i;
836 int args_len;
837 mode_t dir_permissions;
838 char *basename;
839
840 setlocale (LC_ALL, "");
841
842 basename = g_path_get_basename (argv[0]);
843 if (g_strcmp0 (basename, "desktop-file-edit") == 0)
844 edit_mode = TRUE;
845 g_free (basename);
846
847 context = g_option_context_new ("");
848 g_option_context_set_summary (context, edit_mode ? _("Edit a desktop file.") : _("Install desktop files."));
849 g_option_context_add_main_entries (context, main_options, NULL);
850
851 if (!edit_mode)
852 {
853 group = g_option_group_new ("install", _("Installation options for desktop file"), _("Show desktop file installation options"), NULL, NULL);
854 g_option_group_add_entries (group, install_options);
855 g_option_context_add_group (context, group);
856 }
857
858 group = g_option_group_new ("edit", _("Edition options for desktop file"), _("Show desktop file edition options"), NULL, NULL);
859 g_option_group_add_entries (group, edit_options);
860 g_option_group_set_parse_hooks (group, NULL, post_parse_edit_options_callback);
861 g_option_context_add_group (context, group);
862
863 err = NULL;
864 g_option_context_parse (context, &argc, &argv, &err);
865
866 if (err != NULL) {
867 g_printerr ("%s\n", err->message);
868 g_printerr (_("Run '%s --help' to see a full list of available command line options.\n"), argv[0]);
869 g_error_free (err);
870 return 1;
871 }
872
873 if (!edit_mode)
874 {
875 if (vendor_name == NULL && g_getenv ("DESKTOP_FILE_VENDOR"))
876 vendor_name = g_strdup (g_getenv ("DESKTOP_FILE_VENDOR"));
877
878 if (target_dir == NULL && g_getenv ("DESKTOP_FILE_INSTALL_DIR"))
879 target_dir = g_strdup (g_getenv ("DESKTOP_FILE_INSTALL_DIR"));
880
881 if (target_dir == NULL)
882 {
883 if (g_getenv ("RPM_BUILD_ROOT"))
884 target_dir = g_build_filename (g_getenv ("RPM_BUILD_ROOT"), DATADIR, "applications", NULL);
885 else
886 target_dir = g_build_filename (DATADIR, "applications", NULL);
887 }
888
889 /* Create the target directory */
890 dir_permissions = permissions;
891
892 /* Add search bit when the target file is readable */
893 if (permissions & 0400)
894 dir_permissions |= 0100;
895 if (permissions & 0040)
896 dir_permissions |= 0010;
897 if (permissions & 0004)
898 dir_permissions |= 0001;
899
900 g_mkdir_with_parents (target_dir, dir_permissions);
901 }
902
903 args_len = 0;
904 for (i = 0; args && args[i]; i++)
905 args_len++;
906
907 if (edit_mode)
908 {
909 if (args_len == 0)
910 {
911 g_printerr (_("Must specify a desktop file to process.\n"));
912 return 1;
913 }
914 if (args_len > 1)
915 {
916 g_printerr (_("Only one desktop file can be processed at once.\n"));
917 return 1;
918 }
919 }
920 else
921 {
922 if (args_len == 0)
923 {
924 g_printerr (_("Must specify one or more desktop files to process.\n"));
925 return 1;
926 }
927 }
928
929 for (i = 0; args && args[i]; i++)
930 {
931 err = NULL;
932 process_one_file (args[i], &err);
933 if (err != NULL)
934 {
935 g_printerr (_("Error on file \"%s\": %s\n"),
936 args[i], err->message);
937 g_error_free (err);
938
939 return 1;
940 }
941 }
942
943 #if GLIB_CHECK_VERSION(2,28,0)
944 g_slist_free_full (edit_actions, (GDestroyNotify) dfu_edit_action_free);
945 #else
946 g_slist_foreach (edit_actions, (GFunc) dfu_edit_action_free, NULL);
947 g_slist_free (edit_actions);
948 #endif
949
950 g_option_context_free (context);
951
952 return 0;
953 }
954