Only allow deleting files that have been created by
[platform/upstream/glib.git] / gio / gdesktopappinfo.c
1 /* GIO - GLib Input, Output and Streaming Library
2  * 
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  * Copyright © 2007 Ryan Lortie
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General
17  * Public License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Author: Alexander Larsson <alexl@redhat.com>
22  */
23
24 #include "config.h"
25
26 #include <errno.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/wait.h>
30
31 #ifdef HAVE_CRT_EXTERNS_H
32 #include <crt_externs.h>
33 #endif
34
35 #include "gcontenttypeprivate.h"
36 #include "gdesktopappinfo.h"
37 #include "gfile.h"
38 #include "gioerror.h"
39 #include "gthemedicon.h"
40 #include "gfileicon.h"
41 #include <glib/gstdio.h>
42 #include "glibintl.h"
43 #include "giomodule-priv.h"
44 #include "gappinfo.h"
45
46 #include "gioalias.h"
47
48 /**
49  * SECTION:gdesktopappinfo
50  * @short_description: Application information from desktop files
51  * @include: gio/gdesktopappinfo.h 
52  * 
53  * #GDesktopAppInfo is an implementation of #GAppInfo based on
54  * desktop files.
55  *
56  **/
57
58 #define DEFAULT_APPLICATIONS_GROUP  "Default Applications" 
59 #define ADDED_ASSOCIATIONS_GROUP    "Added Associations" 
60 #define REMOVED_ASSOCIATIONS_GROUP  "Removed Associations" 
61 #define MIME_CACHE_GROUP            "MIME Cache"
62
63 static void     g_desktop_app_info_iface_init         (GAppInfoIface    *iface);
64 static GList *  get_all_desktop_entries_for_mime_type (const char       *base_mime_type,
65                                                        const char      **except);
66 static void     mime_info_cache_reload                (const char       *dir);
67 static gboolean g_desktop_app_info_ensure_saved       (GDesktopAppInfo  *info,
68                                                        GError          **error);
69
70 /**
71  * GDesktopAppInfo:
72  * 
73  * Information about an installed application from a desktop file.
74  */
75 struct _GDesktopAppInfo
76 {
77   GObject parent_instance;
78
79   char *desktop_id;
80   char *filename;
81
82   char *name;
83   /* FIXME: what about GenericName ? */
84   char *comment;
85   char *icon_name;
86   GIcon *icon;
87   char **only_show_in;
88   char **not_show_in;
89   char *try_exec;
90   char *exec;
91   char *binary;
92   char *path;
93
94   guint nodisplay       : 1;
95   guint hidden          : 1;
96   guint terminal        : 1;
97   guint startup_notify  : 1;
98   /* FIXME: what about StartupWMClass ? */
99 };
100
101 G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT,
102                          G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
103                                                 g_desktop_app_info_iface_init))
104
105 static gpointer
106 search_path_init (gpointer data)
107 {
108   char **args = NULL;
109   const char * const *data_dirs;
110   const char *user_data_dir;
111   int i, length, j;
112
113   data_dirs = g_get_system_data_dirs ();
114   length = g_strv_length ((char **) data_dirs);
115   
116   args = g_new (char *, length + 2);
117   
118   j = 0;
119   user_data_dir = g_get_user_data_dir ();
120   args[j++] = g_build_filename (user_data_dir, "applications", NULL);
121   for (i = 0; i < length; i++)
122     args[j++] = g_build_filename (data_dirs[i],
123                                   "applications", NULL);
124   args[j++] = NULL;
125   
126   return args;
127 }
128   
129 static const char * const *
130 get_applications_search_path (void)
131 {
132   static GOnce once_init = G_ONCE_INIT;
133   return g_once (&once_init, search_path_init, NULL);
134 }
135
136 static void
137 g_desktop_app_info_finalize (GObject *object)
138 {
139   GDesktopAppInfo *info;
140
141   info = G_DESKTOP_APP_INFO (object);
142
143   g_free (info->desktop_id);
144   g_free (info->filename);
145   g_free (info->name);
146   g_free (info->comment);
147   g_free (info->icon_name);
148   if (info->icon)
149     g_object_unref (info->icon);
150   g_strfreev (info->only_show_in);
151   g_strfreev (info->not_show_in);
152   g_free (info->try_exec);
153   g_free (info->exec);
154   g_free (info->binary);
155   g_free (info->path);
156   
157   G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object);
158 }
159
160 static void
161 g_desktop_app_info_class_init (GDesktopAppInfoClass *klass)
162 {
163   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
164   
165   gobject_class->finalize = g_desktop_app_info_finalize;
166 }
167
168 static void
169 g_desktop_app_info_init (GDesktopAppInfo *local)
170 {
171 }
172
173 static char *
174 binary_from_exec (const char *exec)
175 {
176   const char *p, *start;
177   
178   p = exec;
179   while (*p == ' ')
180     p++;
181   start = p;
182   while (*p != ' ' && *p != 0)
183     p++;
184   
185   return g_strndup (start, p - start);
186   
187 }
188
189 /**
190  * g_desktop_app_info_new_from_keyfile:
191  * @key_file: an opened #GKeyFile
192  * 
193  * Creates a new #GDesktopAppInfo.
194  *
195  * Returns: a new #GDesktopAppInfo or %NULL on error.
196  *
197  * Since: 2.18
198  **/
199 GDesktopAppInfo *
200 g_desktop_app_info_new_from_keyfile (GKeyFile *key_file)
201 {
202   GDesktopAppInfo *info;
203   char *start_group;
204   char *type;
205   char *try_exec;
206   
207   start_group = g_key_file_get_start_group (key_file);
208   if (start_group == NULL || strcmp (start_group, G_KEY_FILE_DESKTOP_GROUP) != 0)
209     {
210       g_free (start_group);
211       return NULL;
212     }
213   g_free (start_group);
214
215   type = g_key_file_get_string (key_file,
216                                 G_KEY_FILE_DESKTOP_GROUP,
217                                 G_KEY_FILE_DESKTOP_KEY_TYPE,
218                                 NULL);
219   if (type == NULL || strcmp (type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0)
220     {
221       g_free (type);
222       return NULL;
223     }
224   g_free (type);
225
226   try_exec = g_key_file_get_string (key_file,
227                                     G_KEY_FILE_DESKTOP_GROUP,
228                                     G_KEY_FILE_DESKTOP_KEY_TRY_EXEC,
229                                     NULL);
230   if (try_exec && try_exec[0] != '\0')
231     {
232       char *t;
233       t = g_find_program_in_path (try_exec);
234       if (t == NULL)
235         {
236           g_free (try_exec);
237           return NULL;
238         }
239       g_free (t);
240     }
241
242   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
243   info->filename = NULL;
244
245   info->name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
246   info->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL, NULL);
247   info->nodisplay = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) != FALSE;
248   info->icon_name =  g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL, NULL);
249   info->only_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL, NULL);
250   info->not_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL, NULL);
251   info->try_exec = try_exec;
252   info->exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
253   info->path = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL);
254   info->terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL) != FALSE;
255   info->startup_notify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL) != FALSE;
256   info->hidden = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL) != FALSE;
257   
258   info->icon = NULL;
259   if (info->icon_name)
260     {
261       if (g_path_is_absolute (info->icon_name))
262         {
263           GFile *file;
264           
265           file = g_file_new_for_path (info->icon_name);
266           info->icon = g_file_icon_new (file);
267           g_object_unref (file);
268         }
269       else
270         info->icon = g_themed_icon_new (info->icon_name);
271     }
272   
273   if (info->exec)
274     info->binary = binary_from_exec (info->exec);
275   
276   if (info->path && info->path[0] == '\0')
277     {
278       g_free (info->path);
279       info->path = NULL;
280     }
281
282   return info;
283 }
284
285 /**
286  * g_desktop_app_info_new_from_filename:
287  * @filename: the path of a desktop file, in the GLib filename encoding
288  * 
289  * Creates a new #GDesktopAppInfo.
290  *
291  * Returns: a new #GDesktopAppInfo or %NULL on error.
292  **/
293 GDesktopAppInfo *
294 g_desktop_app_info_new_from_filename (const char *filename)
295 {
296   GKeyFile *key_file;
297   GDesktopAppInfo *info = NULL;
298
299   key_file = g_key_file_new ();
300   
301   if (g_key_file_load_from_file (key_file,
302                                  filename,
303                                  G_KEY_FILE_NONE,
304                                  NULL))
305     {
306       info = g_desktop_app_info_new_from_keyfile (key_file);
307       if (info)
308         info->filename = g_strdup (filename);
309     }  
310
311   g_key_file_free (key_file);
312
313   return info;
314 }
315
316 /**
317  * g_desktop_app_info_new:
318  * @desktop_id: the desktop file id
319  * 
320  * Creates a new #GDesktopAppInfo.
321  * 
322  * Returns: a new #GDesktopAppInfo, or %NULL if no desktop file with that id
323  **/
324 GDesktopAppInfo *
325 g_desktop_app_info_new (const char *desktop_id)
326 {
327   GDesktopAppInfo *appinfo;
328   const char * const *dirs;
329   char *basename;
330   int i;
331
332   dirs = get_applications_search_path ();
333
334   basename = g_strdup (desktop_id);
335   
336   for (i = 0; dirs[i] != NULL; i++)
337     {
338       char *filename;
339       char *p;
340
341       filename = g_build_filename (dirs[i], desktop_id, NULL);
342       appinfo = g_desktop_app_info_new_from_filename (filename);
343       g_free (filename);
344       if (appinfo != NULL)
345         goto found;
346
347       p = basename;
348       while ((p = strchr (p, '-')) != NULL)
349         {
350           *p = '/';
351           
352           filename = g_build_filename (dirs[i], basename, NULL);
353           appinfo = g_desktop_app_info_new_from_filename (filename);
354           g_free (filename);
355           if (appinfo != NULL)
356             goto found;
357           *p = '-';
358           p++;
359         }
360     }
361   
362   g_free (basename);
363   return NULL;
364
365  found:
366   g_free (basename);
367   
368   appinfo->desktop_id = g_strdup (desktop_id);
369
370   if (g_desktop_app_info_get_is_hidden (appinfo))
371     {
372       g_object_unref (appinfo);
373       appinfo = NULL;
374     }
375   
376   return appinfo;
377 }
378
379 static GAppInfo *
380 g_desktop_app_info_dup (GAppInfo *appinfo)
381 {
382   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
383   GDesktopAppInfo *new_info;
384   
385   new_info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
386
387   new_info->filename = g_strdup (info->filename);
388   new_info->desktop_id = g_strdup (info->desktop_id);
389   
390   new_info->name = g_strdup (info->name);
391   new_info->comment = g_strdup (info->comment);
392   new_info->nodisplay = info->nodisplay;
393   new_info->icon_name = g_strdup (info->icon_name);
394   new_info->icon = g_object_ref (info->icon);
395   new_info->only_show_in = g_strdupv (info->only_show_in);
396   new_info->not_show_in = g_strdupv (info->not_show_in);
397   new_info->try_exec = g_strdup (info->try_exec);
398   new_info->exec = g_strdup (info->exec);
399   new_info->binary = g_strdup (info->binary);
400   new_info->path = g_strdup (info->path);
401   new_info->hidden = info->hidden;
402   new_info->terminal = info->terminal;
403   new_info->startup_notify = info->startup_notify;
404   
405   return G_APP_INFO (new_info);
406 }
407
408 static gboolean
409 g_desktop_app_info_equal (GAppInfo *appinfo1,
410                           GAppInfo *appinfo2)
411 {
412   GDesktopAppInfo *info1 = G_DESKTOP_APP_INFO (appinfo1);
413   GDesktopAppInfo *info2 = G_DESKTOP_APP_INFO (appinfo2);
414
415   if (info1->desktop_id == NULL ||
416       info2->desktop_id == NULL)
417     return info1 == info2;
418
419   return strcmp (info1->desktop_id, info2->desktop_id) == 0;
420 }
421
422 static const char *
423 g_desktop_app_info_get_id (GAppInfo *appinfo)
424 {
425   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
426
427   return info->desktop_id;
428 }
429
430 static const char *
431 g_desktop_app_info_get_name (GAppInfo *appinfo)
432 {
433   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
434
435   if (info->name == NULL)
436     return _("Unnamed");
437   return info->name;
438 }
439
440 /**
441  * g_desktop_app_info_get_is_hidden:
442  * @info: a #GDesktopAppInfo.
443  *
444  * A desktop file is hidden if the Hidden key in it is
445  * set to True.
446  *
447  * Returns: %TRUE if hidden, %FALSE otherwise. 
448  **/
449 gboolean
450 g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info)
451 {
452   return info->hidden;
453 }
454
455 static const char *
456 g_desktop_app_info_get_description (GAppInfo *appinfo)
457 {
458   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
459   
460   return info->comment;
461 }
462
463 static const char *
464 g_desktop_app_info_get_executable (GAppInfo *appinfo)
465 {
466   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
467   
468   return info->binary;
469 }
470
471 static GIcon *
472 g_desktop_app_info_get_icon (GAppInfo *appinfo)
473 {
474   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
475
476   return info->icon;
477 }
478
479 static char *
480 expand_macro_single (char macro, char *uri)
481 {
482   GFile *file;
483   char *result = NULL;
484   char *path, *name;
485
486   file = g_file_new_for_uri (uri);
487   path = g_file_get_path (file);
488   g_object_unref (file);
489   
490   switch (macro)
491     {
492     case 'u':
493     case 'U':   
494       result = g_shell_quote (uri);
495       break;
496     case 'f':
497     case 'F':
498       if (path)
499         result = g_shell_quote (path);
500       break;
501     case 'd':
502     case 'D':
503       if (path)
504         {
505           name = g_path_get_dirname (path);
506           result = g_shell_quote (name);
507           g_free (name);
508         }
509       break;
510     case 'n':
511     case 'N':
512       if (path)
513         {
514           name = g_path_get_basename (path);
515           result = g_shell_quote (name);
516           g_free (name);
517         }
518       break;
519     }
520
521   g_free (path);
522   
523   return result;
524 }
525
526 static void
527 expand_macro (char              macro, 
528               GString          *exec, 
529               GDesktopAppInfo  *info, 
530               GList           **uri_list)
531 {
532   GList *uris = *uri_list;
533   char *expanded;
534   gboolean force_file_uri;
535   char force_file_uri_macro;
536
537   g_return_if_fail (exec != NULL);
538
539   /* On %u and %U, pass POSIX file path pointing to the URI via
540    * the FUSE mount in ~/.gvfs. Note that if the FUSE daemon isn't
541    * running or the URI doesn't have a POSIX file path via FUSE
542    * we'll just pass the URI.
543    */
544   switch (macro)
545     {
546     case 'u':
547       force_file_uri_macro = 'f';
548       force_file_uri = TRUE;
549       break;
550     case 'U':
551       force_file_uri_macro = 'F';
552       force_file_uri = TRUE;
553       break;
554     default:
555       force_file_uri_macro = macro;
556       force_file_uri = FALSE;
557       break;
558     }
559
560   switch (macro)
561     {
562     case 'u':
563     case 'f':
564     case 'd':
565     case 'n':
566       if (uris)
567         {
568           if (!force_file_uri)
569             {
570               expanded = expand_macro_single (macro, uris->data);
571             }
572           else
573             {
574               expanded = expand_macro_single (force_file_uri_macro, uris->data);
575               if (expanded == NULL)
576                 expanded = expand_macro_single (macro, uris->data);
577             }
578
579           if (expanded)
580             {
581               g_string_append (exec, expanded);
582               g_free (expanded);
583             }
584           uris = uris->next;
585         }
586
587       break;
588
589     case 'U':   
590     case 'F':
591     case 'D':
592     case 'N':
593       while (uris)
594         {
595           if (!force_file_uri)
596             {
597               expanded = expand_macro_single (macro, uris->data);
598             }
599           else
600             {
601               expanded = expand_macro_single (force_file_uri_macro, uris->data);
602               if (expanded == NULL)
603                 expanded = expand_macro_single (macro, uris->data);
604             }
605
606           if (expanded)
607             {
608               g_string_append (exec, expanded);
609               g_free (expanded);
610             }
611           
612           uris = uris->next;
613           
614           if (uris != NULL && expanded)
615             g_string_append_c (exec, ' ');
616         }
617
618       break;
619
620     case 'i':
621       if (info->icon_name)
622         {
623           g_string_append (exec, "--icon ");
624           g_string_append (exec, info->icon_name);
625         }
626       break;
627
628     case 'c':
629       if (info->name) 
630         g_string_append (exec, info->name);
631       break;
632
633     case 'k':
634       if (info->filename) 
635         g_string_append (exec, info->filename);
636       break;
637
638     case 'm': /* deprecated */
639       break;
640
641     case '%':
642       g_string_append_c (exec, '%');
643       break;
644     }
645   
646   *uri_list = uris;
647 }
648
649 static gboolean
650 expand_application_parameters (GDesktopAppInfo   *info,
651                                GList            **uris,
652                                int               *argc,
653                                char            ***argv,
654                                GError           **error)
655 {
656   GList *uri_list = *uris;
657   const char *p = info->exec;
658   GString *expanded_exec = g_string_new (NULL);
659   gboolean res;
660   
661   if (info->exec == NULL)
662     {
663       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
664                            _("Desktop file didn't specify Exec field"));
665       return FALSE;
666     }
667   
668   while (*p)
669     {
670       if (p[0] == '%' && p[1] != '\0')
671         {
672           expand_macro (p[1], expanded_exec, info, uris);
673           p++;
674         }
675       else
676         g_string_append_c (expanded_exec, *p);
677       
678       p++;
679     }
680   
681   /* No file substitutions */
682   if (uri_list == *uris && uri_list != NULL)
683     {
684       /* If there is no macro default to %f. This is also what KDE does */
685       g_string_append_c (expanded_exec, ' ');
686       expand_macro ('f', expanded_exec, info, uris);
687     }
688   
689   res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
690   g_string_free (expanded_exec, TRUE);
691   return res;
692 }
693
694 static gboolean
695 prepend_terminal_to_vector (int    *argc,
696                             char ***argv)
697 {
698 #ifndef G_OS_WIN32
699   char **real_argv;
700   int real_argc;
701   int i, j;
702   char **term_argv = NULL;
703   int term_argc = 0;
704   char *check;
705   char **the_argv;
706   
707   g_return_val_if_fail (argc != NULL, FALSE);
708   g_return_val_if_fail (argv != NULL, FALSE);
709         
710   /* sanity */
711   if(*argv == NULL)
712     *argc = 0;
713   
714   the_argv = *argv;
715
716   /* compute size if not given */
717   if (*argc < 0)
718     {
719       for (i = 0; the_argv[i] != NULL; i++)
720         ;
721       *argc = i;
722     }
723   
724   term_argc = 2;
725   term_argv = g_new0 (char *, 3);
726
727   check = g_find_program_in_path ("gnome-terminal");
728   if (check != NULL)
729     {
730       term_argv[0] = check;
731       /* Note that gnome-terminal takes -x and
732        * as -e in gnome-terminal is broken we use that. */
733       term_argv[1] = g_strdup ("-x");
734     }
735   else
736     {
737       if (check == NULL)
738         check = g_find_program_in_path ("nxterm");
739       if (check == NULL)
740         check = g_find_program_in_path ("color-xterm");
741       if (check == NULL)
742         check = g_find_program_in_path ("rxvt");
743       if (check == NULL)
744         check = g_find_program_in_path ("xterm");
745       if (check == NULL)
746         check = g_find_program_in_path ("dtterm");
747       if (check == NULL)
748         {
749           check = g_strdup ("xterm");
750           g_warning ("couldn't find a terminal, falling back to xterm");
751         }
752       term_argv[0] = check;
753       term_argv[1] = g_strdup ("-e");
754     }
755
756   real_argc = term_argc + *argc;
757   real_argv = g_new (char *, real_argc + 1);
758   
759   for (i = 0; i < term_argc; i++)
760     real_argv[i] = term_argv[i];
761   
762   for (j = 0; j < *argc; j++, i++)
763     real_argv[i] = (char *)the_argv[j];
764   
765   real_argv[i] = NULL;
766   
767   g_free (*argv);
768   *argv = real_argv;
769   *argc = real_argc;
770   
771   /* we use g_free here as we sucked all the inner strings
772    * out from it into real_argv */
773   g_free (term_argv);
774   return TRUE;
775 #else
776   return FALSE;
777 #endif /* G_OS_WIN32 */
778 }
779
780 /* '=' is the new '\0'.
781  * DO NOT CALL unless at least one string ends with '='
782  */
783 static gboolean
784 is_env (const char *a,
785         const char *b)
786 {
787   while (*a == *b)
788   {
789     if (*a == 0 || *b == 0)
790       return FALSE;
791     
792     if (*a == '=')
793       return TRUE;
794
795     a++;
796     b++;
797   }
798
799   return FALSE;
800 }
801
802 /* free with g_strfreev */
803 static char **
804 replace_env_var (char       **old_environ,
805                  const char  *env_var,
806                  const char  *new_value)
807 {
808   int length, new_length;
809   int index, new_index;
810   char **new_environ;
811   int i, new_i;
812
813   /* do two things at once:
814    *  - discover the length of the environment ('length')
815    *  - find the location (if any) of the env var ('index')
816    */
817   index = -1;
818   for (length = 0; old_environ[length]; length++)
819     {
820       /* if we already have it in our environment, replace */
821       if (is_env (old_environ[length], env_var))
822         index = length;
823     }
824
825   
826   /* no current env var, no desired env value.
827    * this is easy :)
828    */
829   if (new_value == NULL && index == -1)
830     return old_environ;
831
832   /* in all cases now, we will be using a modified environment.
833    * determine its length and allocated it.
834    * 
835    * after this block:
836    *   new_index   = location to insert, if any
837    *   new_length  = length of the new array
838    *   new_environ = the pointer array for the new environment
839    */
840   
841   if (new_value == NULL && index >= 0)
842     {
843       /* in this case, we will be removing an entry */
844       new_length = length - 1;
845       new_index = -1;
846     }
847   else if (new_value != NULL && index < 0)
848     {
849       /* in this case, we will be adding an entry to the end */
850       new_length = length + 1;
851       new_index = length;
852     }
853   else
854     /* in this case, we will be replacing the existing entry */
855     {
856       new_length = length;
857       new_index = index;
858     }
859
860   new_environ = g_malloc (sizeof (char *) * (new_length + 1));
861   new_environ[new_length] = NULL;
862
863   /* now we do the copying.
864    * for each entry in the new environment, we decide what to do
865    */
866   
867   i = 0;
868   for (new_i = 0; new_i < new_length; new_i++)
869     {
870       if (new_i == new_index)
871         {
872           /* insert our new item */
873           new_environ[new_i] = g_strconcat (env_var,
874                                             "=",
875                                             new_value,
876                                             NULL);
877           
878           /* if we had an old entry, skip it now */
879           if (index >= 0)
880             i++;
881         }
882       else
883         {
884           /* if this is the old DESKTOP_STARTUP_ID, skip it */
885           if (i == index)
886             i++;
887           
888           /* copy an old item */
889           new_environ[new_i] = g_strdup (old_environ[i]);
890           i++;
891         }
892     }
893
894   g_strfreev (old_environ);
895   
896   return new_environ;
897 }
898
899 static GList *
900 uri_list_segment_to_files (GList *start,
901                            GList *end)
902 {
903   GList *res;
904   GFile *file;
905
906   res = NULL;
907   while (start != NULL && start != end)
908     {
909       file = g_file_new_for_uri ((char *)start->data);
910       res = g_list_prepend (res, file);
911       start = start->next;
912     }
913
914   return g_list_reverse (res);
915 }
916
917 #ifdef HAVE__NSGETENVIRON
918 #define environ (*_NSGetEnviron())
919 #elif !defined(G_OS_WIN32)
920
921 /* According to the Single Unix Specification, environ is not in 
922  *  * any system header, although unistd.h often declares it.
923  *   */
924 extern char **environ;
925 #endif
926
927 static gboolean
928 g_desktop_app_info_launch_uris (GAppInfo           *appinfo,
929                                 GList              *uris,
930                                 GAppLaunchContext  *launch_context,
931                                 GError            **error)
932 {
933   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
934   gboolean completed = FALSE;
935   GList *old_uris;
936   GList *launched_files;
937   char **envp;
938   char **argv;
939   int argc;
940   char *display;
941   char *sn_id;
942
943   g_return_val_if_fail (appinfo != NULL, FALSE);
944
945   argv = NULL;
946   envp = NULL;
947       
948   do 
949     {
950       old_uris = uris;
951       if (!expand_application_parameters (info, &uris,
952                                           &argc, &argv, error))
953         goto out;
954       
955       if (info->terminal && !prepend_terminal_to_vector (&argc, &argv))
956         {
957           g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
958                                _("Unable to find terminal required for application"));
959           goto out;
960         }
961
962       sn_id = NULL;
963       if (launch_context)
964         {
965           launched_files = uri_list_segment_to_files (old_uris, uris);
966           
967           display = g_app_launch_context_get_display (launch_context,
968                                                       appinfo,
969                                                       launched_files);
970
971           sn_id = NULL;
972           if (info->startup_notify)
973             sn_id = g_app_launch_context_get_startup_notify_id (launch_context,
974                                                                 appinfo,
975                                                                 launched_files);
976           
977           if (display || sn_id)
978             {
979 #ifdef G_OS_WIN32
980               /* FIXME */
981               envp = g_new0 (char *, 1);
982 #else
983               envp = g_strdupv (environ);
984 #endif
985               
986               if (display)
987                 envp = replace_env_var (envp,
988                                         "DISPLAY",
989                                         display);
990               
991               if (sn_id)
992                 envp = replace_env_var (envp,
993                                         "DESKTOP_STARTUP_ID",
994                                         sn_id);
995             }
996
997           g_free (display);
998           
999           g_list_foreach (launched_files, (GFunc)g_object_unref, NULL);
1000           g_list_free (launched_files);
1001         }
1002       
1003       if (!g_spawn_async (info->path,  /* working directory */
1004                           argv,
1005                           envp,
1006                           G_SPAWN_SEARCH_PATH /* flags */,
1007                           NULL /* child_setup */,
1008                           NULL /* data */,
1009                           NULL /* child_pid */,
1010                           error))
1011         {
1012           if (sn_id)
1013             {
1014               g_app_launch_context_launch_failed (launch_context, sn_id);
1015               g_free (sn_id);
1016             }
1017           goto out;
1018         }
1019
1020       
1021       g_free (sn_id);
1022       
1023       g_strfreev (envp);
1024       g_strfreev (argv);
1025       envp = NULL;
1026       argv = NULL;
1027     }
1028   while (uris != NULL);
1029
1030   completed = TRUE;
1031
1032  out:
1033   g_strfreev (argv);
1034   g_strfreev (envp);
1035
1036   return completed;
1037 }
1038
1039 static gboolean
1040 g_desktop_app_info_supports_uris (GAppInfo *appinfo)
1041 {
1042   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1043  
1044   return info->exec && 
1045     ((strstr (info->exec, "%u") != NULL) ||
1046      (strstr (info->exec, "%U") != NULL));
1047 }
1048
1049 static gboolean
1050 g_desktop_app_info_supports_files (GAppInfo *appinfo)
1051 {
1052   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1053  
1054   return info->exec && 
1055     ((strstr (info->exec, "%f") != NULL) ||
1056      (strstr (info->exec, "%F") != NULL));
1057 }
1058
1059 static gboolean
1060 g_desktop_app_info_launch (GAppInfo           *appinfo,
1061                            GList              *files,
1062                            GAppLaunchContext  *launch_context,
1063                            GError            **error)
1064 {
1065   GList *uris;
1066   char *uri;
1067   gboolean res;
1068
1069   uris = NULL;
1070   while (files)
1071     {
1072       uri = g_file_get_uri (files->data);
1073       uris = g_list_prepend (uris, uri);
1074       files = files->next;
1075     }
1076   
1077   uris = g_list_reverse (uris);
1078   
1079   res = g_desktop_app_info_launch_uris (appinfo, uris, launch_context, error);
1080   
1081   g_list_foreach  (uris, (GFunc)g_free, NULL);
1082   g_list_free (uris);
1083   
1084   return res;
1085 }
1086
1087 G_LOCK_DEFINE_STATIC (g_desktop_env);
1088 static gchar *g_desktop_env = NULL;
1089
1090 /**
1091  * g_desktop_app_info_set_desktop_env:
1092  * @desktop_env: a string specifying what desktop this is
1093  *
1094  * Sets the name of the desktop that the application is running in.
1095  * This is used by g_app_info_should_show() to evaluate the
1096  * <literal>OnlyShowIn</literal> and <literal>NotShowIn</literal>
1097  * desktop entry fields.
1098  *
1099  * The <ulink url="http://standards.freedesktop.org/menu-spec/latest/">Desktop 
1100  * Menu specification</ulink> recognizes the following:
1101  * <simplelist>
1102  *   <member>GNOME</member>
1103  *   <member>KDE</member>
1104  *   <member>ROX</member>
1105  *   <member>XFCE</member>
1106  *   <member>Old</member> 
1107  * </simplelist>
1108  *
1109  * Should be called only once; subsequent calls are ignored.
1110  */
1111 void
1112 g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
1113 {
1114   G_LOCK (g_desktop_env);
1115   if (!g_desktop_env)
1116     g_desktop_env = g_strdup (desktop_env);
1117   G_UNLOCK (g_desktop_env);
1118 }
1119
1120 static gboolean
1121 g_desktop_app_info_should_show (GAppInfo *appinfo)
1122 {
1123   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1124   gboolean found;
1125   const gchar *desktop_env;
1126   int i;
1127
1128   if (info->nodisplay)
1129     return FALSE;
1130
1131   G_LOCK (g_desktop_env);
1132   desktop_env = g_desktop_env;
1133   G_UNLOCK (g_desktop_env);
1134
1135   if (info->only_show_in)
1136     {
1137       if (desktop_env == NULL)
1138         return FALSE;
1139       
1140       found = FALSE;
1141       for (i = 0; info->only_show_in[i] != NULL; i++)
1142         {
1143           if (strcmp (info->only_show_in[i], desktop_env) == 0)
1144             {
1145               found = TRUE;
1146               break;
1147             }
1148         }
1149       if (!found)
1150         return FALSE;
1151     }
1152
1153   if (info->not_show_in && desktop_env)
1154     {
1155       for (i = 0; info->not_show_in[i] != NULL; i++)
1156         {
1157           if (strcmp (info->not_show_in[i], desktop_env) == 0)
1158             return FALSE;
1159         }
1160     }
1161   
1162   return TRUE;
1163 }
1164
1165 typedef enum {
1166   APP_DIR,
1167   MIMETYPE_DIR
1168 } DirType;
1169
1170 static char *
1171 ensure_dir (DirType   type,
1172             GError  **error)
1173 {
1174   char *path, *display_name;
1175   int errsv;
1176
1177   if (type == APP_DIR)
1178     path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
1179   else
1180     path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL);
1181
1182   errno = 0;
1183   if (g_mkdir_with_parents (path, 0700) == 0)
1184     return path;
1185
1186   errsv = errno;
1187   display_name = g_filename_display_name (path);
1188   if (type == APP_DIR)
1189     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
1190                  _("Can't create user application configuration folder %s: %s"),
1191                  display_name, g_strerror (errsv));
1192   else
1193     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
1194                  _("Can't create user MIME configuration folder %s: %s"),
1195                  display_name, g_strerror (errsv));
1196
1197   g_free (display_name);
1198   g_free (path);
1199
1200   return NULL;
1201 }
1202
1203 static gboolean
1204 update_mimeapps_list (const char  *desktop_id, 
1205                       const char  *content_type, 
1206                       gboolean     add_as_default,
1207                       gboolean     add_non_default,
1208                       gboolean     remove, 
1209                       GError     **error)
1210 {
1211   char *dirname, *filename;
1212   GKeyFile *key_file;
1213   gboolean load_succeeded, res;
1214   char **old_list, **list;
1215   GList *system_list, *l;
1216   gsize length, data_size;
1217   char *data;
1218   int i, j, k;
1219   char **content_types;
1220
1221   /* Don't add both at start and end */
1222   g_assert (!(add_as_default && add_non_default));
1223   
1224   dirname = ensure_dir (APP_DIR, error);
1225   if (!dirname)
1226     return FALSE;
1227
1228   filename = g_build_filename (dirname, "mimeapps.list", NULL);
1229   g_free (dirname);
1230
1231   key_file = g_key_file_new ();
1232   load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
1233   if (!load_succeeded || !g_key_file_has_group (key_file, ADDED_ASSOCIATIONS_GROUP))
1234     {
1235       g_key_file_free (key_file);
1236       key_file = g_key_file_new ();
1237     }
1238
1239   if (content_type)
1240     {
1241       content_types = g_new (char *, 2);
1242       content_types[0] = g_strdup (content_type);
1243       content_types[1] = NULL;
1244     }
1245   else
1246     {
1247       content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL);
1248     }
1249
1250   for (k = 0; content_types && content_types[k]; k++)
1251     { 
1252       /* Add to the right place in the list */
1253   
1254       length = 0;
1255       old_list = g_key_file_get_string_list (key_file, ADDED_ASSOCIATIONS_GROUP,
1256                                              content_types[k], &length, NULL);
1257
1258       list = g_new (char *, 1 + length + 1);
1259
1260       i = 0;
1261       if (add_as_default)
1262         list[i++] = g_strdup (desktop_id);
1263       if (old_list)
1264         {
1265           for (j = 0; old_list[j] != NULL; j++)
1266             {
1267               if (g_strcmp0 (old_list[j], desktop_id) != 0)
1268                 list[i++] = g_strdup (old_list[j]);
1269               else if (add_non_default)
1270                 {
1271                   /* If adding as non-default, and its already in,
1272                      don't change order of desktop ids */
1273                   add_non_default = FALSE;
1274                   list[i++] = g_strdup (old_list[j]);
1275                 }
1276             }
1277         }
1278       
1279       if (add_non_default)
1280         {
1281           /* We're adding as non-default, and it wasn't already in the list,
1282              so we add at the end. But to avoid listing the app before the
1283              current system default (thus changing the default) we have to
1284              add the current list of (not yet listed) apps before it. */
1285
1286           list[i] = NULL; /* Terminate current list so we can use it */
1287           system_list =  get_all_desktop_entries_for_mime_type (content_type, list);
1288           
1289           list = g_renew (char *, list, 1 + length + g_list_length (system_list) + 1);
1290           
1291           for (l = system_list; l != NULL; l = l->next)
1292             {
1293               list[i++] = l->data; /* no strdup, taking ownership */
1294               if (g_strcmp0 (l->data, desktop_id) == 0)
1295                 add_non_default = FALSE;
1296             }
1297           g_list_free (system_list);
1298                   
1299           if (add_non_default)
1300             list[i++] = g_strdup (desktop_id);
1301         }
1302       
1303       list[i] = NULL;
1304   
1305       g_strfreev (old_list);
1306
1307       if (list[0] == NULL || desktop_id == NULL)
1308         g_key_file_remove_key (key_file,
1309                                ADDED_ASSOCIATIONS_GROUP,
1310                                content_types[k],
1311                                NULL);
1312       else
1313         g_key_file_set_string_list (key_file,
1314                                     ADDED_ASSOCIATIONS_GROUP,
1315                                     content_types[k],
1316                                     (const char * const *)list, i);
1317    
1318       g_strfreev (list);
1319     }
1320   
1321   if (content_type)
1322     {
1323       /* reuse the list from above */
1324     }
1325   else
1326     {
1327       g_strfreev (content_types);
1328       content_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
1329     }
1330
1331   for (k = 0; content_types && content_types[k]; k++) 
1332     {
1333       /* Remove from removed associations group (unless remove) */
1334   
1335       length = 0;
1336       old_list = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP,
1337                                              content_types[k], &length, NULL);
1338
1339       list = g_new (char *, 1 + length + 1);
1340
1341       i = 0;
1342       if (remove)
1343         list[i++] = g_strdup (desktop_id);
1344       if (old_list)
1345         {
1346           for (j = 0; old_list[j] != NULL; j++)
1347             {
1348               if (g_strcmp0 (old_list[j], desktop_id) != 0)
1349                 list[i++] = g_strdup (old_list[j]);
1350             }
1351         }
1352       list[i] = NULL;
1353   
1354       g_strfreev (old_list);
1355
1356       if (list[0] == NULL || desktop_id == NULL)
1357         g_key_file_remove_key (key_file,
1358                                REMOVED_ASSOCIATIONS_GROUP,
1359                                content_types[k],
1360                                NULL);
1361       else
1362         g_key_file_set_string_list (key_file,
1363                                     REMOVED_ASSOCIATIONS_GROUP,
1364                                     content_types[k],
1365                                     (const char * const *)list, i);
1366
1367       g_strfreev (list);
1368     }
1369   
1370   g_strfreev (content_types);  
1371
1372   data = g_key_file_to_data (key_file, &data_size, error);
1373   g_key_file_free (key_file);
1374   
1375   res = g_file_set_contents (filename, data, data_size, error);
1376
1377   mime_info_cache_reload (NULL);
1378                           
1379   g_free (filename);
1380   g_free (data);
1381   
1382   return res;
1383 }
1384
1385 static gboolean
1386 g_desktop_app_info_set_as_default_for_type (GAppInfo    *appinfo,
1387                                             const char  *content_type,
1388                                             GError     **error)
1389 {
1390   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1391
1392   if (!g_desktop_app_info_ensure_saved (info, error))
1393     return FALSE;  
1394   
1395   return update_mimeapps_list (info->desktop_id, content_type, TRUE, FALSE, FALSE, error);
1396 }
1397
1398 static void
1399 update_program_done (GPid     pid,
1400                      gint     status,
1401                      gpointer data)
1402 {
1403   /* Did the application exit correctly */
1404   if (WIFEXITED (status) &&
1405       WEXITSTATUS (status) == 0)
1406     {
1407       /* Here we could clean out any caches in use */
1408     }
1409 }
1410
1411 static void
1412 run_update_command (char *command,
1413                     char *subdir)
1414 {
1415         char *argv[3] = {
1416                 NULL,
1417                 NULL,
1418                 NULL,
1419         };
1420         GPid pid = 0;
1421         GError *error = NULL;
1422
1423         argv[0] = command;
1424         argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL);
1425
1426         if (g_spawn_async ("/", argv,
1427                            NULL,       /* envp */
1428                            G_SPAWN_SEARCH_PATH |
1429                            G_SPAWN_STDOUT_TO_DEV_NULL |
1430                            G_SPAWN_STDERR_TO_DEV_NULL |
1431                            G_SPAWN_DO_NOT_REAP_CHILD,
1432                            NULL, NULL, /* No setup function */
1433                            &pid,
1434                            &error)) 
1435           g_child_watch_add (pid, update_program_done, NULL);
1436         else
1437           {
1438             /* If we get an error at this point, it's quite likely the user doesn't
1439              * have an installed copy of either 'update-mime-database' or
1440              * 'update-desktop-database'.  I don't think we want to popup an error
1441              * dialog at this point, so we just do a g_warning to give the user a
1442              * chance of debugging it.
1443              */
1444             g_warning ("%s", error->message);
1445           }
1446         
1447         g_free (argv[1]);
1448 }
1449
1450 static gboolean
1451 g_desktop_app_info_set_as_default_for_extension (GAppInfo    *appinfo,
1452                                                  const char  *extension,
1453                                                  GError     **error)
1454 {
1455   char *filename, *basename, *mimetype;
1456   char *dirname;
1457   gboolean res;
1458
1459   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (appinfo), error))
1460     return FALSE;  
1461   
1462   dirname = ensure_dir (MIMETYPE_DIR, error);
1463   if (!dirname)
1464     return FALSE;
1465   
1466   basename = g_strdup_printf ("user-extension-%s.xml", extension);
1467   filename = g_build_filename (dirname, basename, NULL);
1468   g_free (basename);
1469   g_free (dirname);
1470
1471   mimetype = g_strdup_printf ("application/x-extension-%s", extension);
1472   
1473   if (!g_file_test (filename, G_FILE_TEST_EXISTS)) 
1474     {
1475       char *contents;
1476
1477       contents =
1478         g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1479                          "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
1480                          " <mime-type type=\"%s\">\n"
1481                          "  <comment>%s document</comment>\n"
1482                          "  <glob pattern=\"*.%s\"/>\n"
1483                          " </mime-type>\n"
1484                          "</mime-info>\n", mimetype, extension, extension);
1485
1486       g_file_set_contents (filename, contents, -1, NULL);
1487       g_free (contents);
1488
1489       run_update_command ("update-mime-database", "mime");
1490     }
1491   g_free (filename);
1492   
1493   res = g_desktop_app_info_set_as_default_for_type (appinfo,
1494                                                     mimetype,
1495                                                     error);
1496
1497   g_free (mimetype);
1498   
1499   return res;
1500 }
1501
1502 static gboolean
1503 g_desktop_app_info_add_supports_type (GAppInfo    *appinfo,
1504                                       const char  *content_type,
1505                                       GError     **error)
1506 {
1507   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1508
1509   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
1510     return FALSE;  
1511   
1512   return update_mimeapps_list (info->desktop_id, content_type, FALSE, TRUE, FALSE, error);
1513 }
1514
1515 static gboolean
1516 g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo)
1517 {
1518   return TRUE;
1519 }
1520
1521 static gboolean
1522 g_desktop_app_info_remove_supports_type (GAppInfo    *appinfo,
1523                                          const char  *content_type,
1524                                          GError     **error)
1525 {
1526   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1527
1528   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
1529     return FALSE;
1530   
1531   return update_mimeapps_list (info->desktop_id, content_type, FALSE, FALSE, TRUE, error);
1532 }
1533
1534 static gboolean
1535 g_desktop_app_info_ensure_saved (GDesktopAppInfo  *info,
1536                                  GError          **error)
1537 {
1538   GKeyFile *key_file;
1539   char *dirname;
1540   char *filename;
1541   char *data, *desktop_id;
1542   gsize data_size;
1543   int fd;
1544   gboolean res;
1545   
1546   if (info->filename != NULL)
1547     return TRUE;
1548
1549   /* This is only used for object created with
1550    * g_app_info_create_from_commandline. All other
1551    * object should have a filename
1552    */
1553   
1554   dirname = ensure_dir (APP_DIR, error);
1555   if (!dirname)
1556     return FALSE;
1557   
1558   key_file = g_key_file_new ();
1559
1560   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1561                          "Encoding", "UTF-8");
1562   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1563                          G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0");
1564   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1565                          G_KEY_FILE_DESKTOP_KEY_TYPE,
1566                          G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
1567   if (info->terminal) 
1568     g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
1569                             G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE);
1570
1571   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1572                          G_KEY_FILE_DESKTOP_KEY_EXEC, info->exec);
1573
1574   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1575                          G_KEY_FILE_DESKTOP_KEY_NAME, info->name);
1576
1577   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1578                          G_KEY_FILE_DESKTOP_KEY_COMMENT, info->comment);
1579   
1580   g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
1581                           G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
1582
1583   data = g_key_file_to_data (key_file, &data_size, NULL);
1584   g_key_file_free (key_file);
1585
1586   desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", info->name);
1587   filename = g_build_filename (dirname, desktop_id, NULL);
1588   g_free (desktop_id);
1589   g_free (dirname);
1590   
1591   fd = g_mkstemp (filename);
1592   if (fd == -1)
1593     {
1594       char *display_name;
1595
1596       display_name = g_filename_display_name (filename);
1597       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
1598                    _("Can't create user desktop file %s"), display_name);
1599       g_free (display_name);
1600       g_free (filename);
1601       g_free (data);
1602       return FALSE;
1603     }
1604
1605   desktop_id = g_path_get_basename (filename);
1606
1607   close (fd);
1608   
1609   res = g_file_set_contents (filename, data, data_size, error);
1610   if (!res)
1611     {
1612       g_free (desktop_id);
1613       g_free (filename);
1614       return FALSE;
1615     }
1616
1617   info->filename = filename;
1618   info->desktop_id = desktop_id;
1619   
1620   run_update_command ("update-desktop-database", "applications");
1621   
1622   return TRUE;
1623 }
1624
1625 static gboolean
1626 g_desktop_app_info_can_delete (GAppInfo *appinfo)
1627 {
1628   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1629
1630   if (info->filename)
1631     {
1632       if (strstr (info->filename, "/userapp-"))
1633         return g_access (info->filename, W_OK) == 0;
1634     }
1635
1636   return FALSE;
1637 }
1638
1639 static gboolean
1640 g_desktop_app_info_delete (GAppInfo *appinfo)
1641 {
1642   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1643   
1644   if (info->filename)
1645     { 
1646       if (g_remove (info->filename) == 0)
1647         {
1648           update_mimeapps_list (info->desktop_id, NULL, FALSE, FALSE, FALSE, NULL);
1649
1650           g_free (info->filename);
1651           info->filename = NULL;
1652           g_free (info->desktop_id);
1653           info->desktop_id = NULL;
1654
1655           return TRUE;
1656         }
1657     }
1658
1659   return FALSE;
1660 }
1661
1662 /**
1663  * g_app_info_create_from_commandline:
1664  * @commandline: the commandline to use
1665  * @application_name: the application name, or %NULL to use @commandline
1666  * @flags: flags that can specify details of the created #GAppInfo
1667  * @error: a #GError location to store the error occuring, %NULL to ignore.
1668  *
1669  * Creates a new #GAppInfo from the given information.
1670  *
1671  * Returns: new #GAppInfo for given command.
1672  **/
1673 GAppInfo *
1674 g_app_info_create_from_commandline (const char           *commandline,
1675                                     const char           *application_name,
1676                                     GAppInfoCreateFlags   flags,
1677                                     GError              **error)
1678 {
1679   char **split;
1680   char *basename;
1681   GDesktopAppInfo *info;
1682
1683   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
1684
1685   info->filename = NULL;
1686   info->desktop_id = NULL;
1687   
1688   info->terminal = flags & G_APP_INFO_CREATE_NEEDS_TERMINAL;
1689   info->startup_notify = FALSE;
1690   info->hidden = FALSE;
1691   if (flags & G_APP_INFO_CREATE_SUPPORTS_URIS)
1692     info->exec = g_strconcat (commandline, " %u", NULL);
1693   else
1694     info->exec = g_strconcat (commandline, " %f", NULL);
1695   info->nodisplay = TRUE;
1696   info->binary = binary_from_exec (info->exec);
1697   
1698   if (application_name)
1699     info->name = g_strdup (application_name);
1700   else
1701     {
1702       /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */
1703       split = g_strsplit (commandline, " ", 2);
1704       basename = g_path_get_basename (split[0]);
1705       g_strfreev (split);
1706       info->name = basename;
1707       if (info->name == NULL)
1708         info->name = g_strdup ("custom");
1709     }
1710   info->comment = g_strdup_printf (_("Custom definition for %s"), info->name);
1711   
1712   return G_APP_INFO (info);
1713 }
1714
1715 static void
1716 g_desktop_app_info_iface_init (GAppInfoIface *iface)
1717 {
1718   iface->dup = g_desktop_app_info_dup;
1719   iface->equal = g_desktop_app_info_equal;
1720   iface->get_id = g_desktop_app_info_get_id;
1721   iface->get_name = g_desktop_app_info_get_name;
1722   iface->get_description = g_desktop_app_info_get_description;
1723   iface->get_executable = g_desktop_app_info_get_executable;
1724   iface->get_icon = g_desktop_app_info_get_icon;
1725   iface->launch = g_desktop_app_info_launch;
1726   iface->supports_uris = g_desktop_app_info_supports_uris;
1727   iface->supports_files = g_desktop_app_info_supports_files;
1728   iface->launch_uris = g_desktop_app_info_launch_uris;
1729   iface->should_show = g_desktop_app_info_should_show;
1730   iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type;
1731   iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension;
1732   iface->add_supports_type = g_desktop_app_info_add_supports_type;
1733   iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type;
1734   iface->remove_supports_type = g_desktop_app_info_remove_supports_type;
1735   iface->can_delete = g_desktop_app_info_can_delete;
1736   iface->do_delete = g_desktop_app_info_delete;
1737 }
1738
1739 static gboolean
1740 app_info_in_list (GAppInfo *info, 
1741                   GList    *list)
1742 {
1743   while (list != NULL)
1744     {
1745       if (g_app_info_equal (info, list->data))
1746         return TRUE;
1747       list = list->next;
1748     }
1749   return FALSE;
1750 }
1751
1752
1753 /**
1754  * g_app_info_get_all_for_type:
1755  * @content_type: the content type to find a #GAppInfo for
1756  * 
1757  * Gets a list of all #GAppInfo s for a given content type.
1758  *
1759  * Returns: #GList of #GAppInfo s for given @content_type
1760  *    or %NULL on error.
1761  **/
1762 GList *
1763 g_app_info_get_all_for_type (const char *content_type)
1764 {
1765   GList *desktop_entries, *l;
1766   GList *infos;
1767   GDesktopAppInfo *info;
1768
1769   g_return_val_if_fail (content_type != NULL, NULL);
1770   
1771   desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL);
1772
1773   infos = NULL;
1774   for (l = desktop_entries; l != NULL; l = l->next)
1775     {
1776       char *desktop_entry = l->data;
1777
1778       info = g_desktop_app_info_new (desktop_entry);
1779       if (info)
1780         {
1781           if (app_info_in_list (G_APP_INFO (info), infos))
1782             g_object_unref (info);
1783           else
1784             infos = g_list_prepend (infos, info);
1785         }
1786       g_free (desktop_entry);
1787     }
1788
1789   g_list_free (desktop_entries);
1790   
1791   return g_list_reverse (infos);
1792 }
1793
1794 /**
1795  * g_app_info_reset_type_associations:
1796  * @content_type: a content type 
1797  *
1798  * Removes all changes to the type associations done by
1799  * g_app_info_set_as_default_for_type(), 
1800  * g_app_info_set_as_default_for_extension(), 
1801  * g_app_info_add_supports_type() of g_app_info_remove_supports_type().
1802  *
1803  * Since: 2.20
1804  */
1805 void
1806 g_app_info_reset_type_associations (const char *content_type)
1807 {
1808   update_mimeapps_list (NULL, content_type, FALSE, FALSE, FALSE, NULL);
1809 }
1810
1811 /**
1812  * g_app_info_get_default_for_type:
1813  * @content_type: the content type to find a #GAppInfo for
1814  * @must_support_uris: if %TRUE, the #GAppInfo is expected to
1815  *     support URIs
1816  * 
1817  * Gets the #GAppInfo that correspond to a given content type.
1818  *
1819  * Returns: #GAppInfo for given @content_type or %NULL on error.
1820  **/
1821 GAppInfo *
1822 g_app_info_get_default_for_type (const char *content_type,
1823                                  gboolean    must_support_uris)
1824 {
1825   GList *desktop_entries, *l;
1826   GAppInfo *info;
1827
1828   g_return_val_if_fail (content_type != NULL, NULL);
1829   
1830   desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL);
1831
1832   info = NULL;
1833   for (l = desktop_entries; l != NULL; l = l->next)
1834     {
1835       char *desktop_entry = l->data;
1836
1837       info = (GAppInfo *)g_desktop_app_info_new (desktop_entry);
1838       if (info)
1839         {
1840           if (must_support_uris && !g_app_info_supports_uris (info))
1841             {
1842               g_object_unref (info);
1843               info = NULL;
1844             }
1845           else
1846             break;
1847         }
1848     }
1849   
1850   g_list_foreach  (desktop_entries, (GFunc)g_free, NULL);
1851   g_list_free (desktop_entries);
1852   
1853   return info;
1854 }
1855
1856 /**
1857  * g_app_info_get_default_for_uri_scheme:
1858  * @uri_scheme: a string containing a URI scheme.
1859  *
1860  * Gets the default application for launching applications 
1861  * using this URI scheme. A URI scheme is the initial part 
1862  * of the URI, up to but not including the ':', e.g. "http", 
1863  * "ftp" or "sip".
1864  * 
1865  * Returns: #GAppInfo for given @uri_scheme or %NULL on error.
1866  **/
1867 GAppInfo *
1868 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
1869 {
1870   static gsize lookup = 0;
1871   
1872   if (g_once_init_enter (&lookup))
1873     {
1874       gsize setup_value = 1;
1875       GDesktopAppInfoLookup *lookup_instance;
1876       const char *use_this;
1877       GIOExtensionPoint *ep;
1878       GIOExtension *extension;
1879       GList *l;
1880
1881       use_this = g_getenv ("GIO_USE_URI_ASSOCIATION");
1882       
1883       /* Ensure vfs in modules loaded */
1884       _g_io_modules_ensure_loaded ();
1885       
1886       ep = g_io_extension_point_lookup (G_DESKTOP_APP_INFO_LOOKUP_EXTENSION_POINT_NAME);
1887
1888       lookup_instance = NULL;
1889       if (use_this)
1890         {
1891           extension = g_io_extension_point_get_extension_by_name (ep, use_this);
1892           if (extension)
1893             lookup_instance = g_object_new (g_io_extension_get_type (extension), NULL);
1894         }
1895       
1896       if (lookup_instance == NULL)
1897         {
1898           for (l = g_io_extension_point_get_extensions (ep); l != NULL; l = l->next)
1899             {
1900               extension = l->data;
1901               lookup_instance = g_object_new (g_io_extension_get_type (extension), NULL);
1902               if (lookup_instance != NULL)
1903                 break;
1904             }
1905         }
1906
1907       if (lookup_instance != NULL)
1908         setup_value = (gsize)lookup_instance;
1909       
1910       g_once_init_leave (&lookup, setup_value);
1911     }
1912
1913   if (lookup == 1)
1914     return NULL;
1915
1916   return g_desktop_app_info_lookup_get_default_for_uri_scheme (G_DESKTOP_APP_INFO_LOOKUP (lookup),
1917                                                                uri_scheme);
1918 }
1919
1920
1921 static void
1922 get_apps_from_dir (GHashTable *apps, 
1923                    const char *dirname, 
1924                    const char *prefix)
1925 {
1926   GDir *dir;
1927   const char *basename;
1928   char *filename, *subprefix, *desktop_id;
1929   gboolean hidden;
1930   GDesktopAppInfo *appinfo;
1931   
1932   dir = g_dir_open (dirname, 0, NULL);
1933   if (dir)
1934     {
1935       while ((basename = g_dir_read_name (dir)) != NULL)
1936         {
1937           filename = g_build_filename (dirname, basename, NULL);
1938           if (g_str_has_suffix (basename, ".desktop"))
1939             {
1940               desktop_id = g_strconcat (prefix, basename, NULL);
1941
1942               /* Use _extended so we catch NULLs too (hidden) */
1943               if (!g_hash_table_lookup_extended (apps, desktop_id, NULL, NULL))
1944                 {
1945                   appinfo = g_desktop_app_info_new_from_filename (filename);
1946
1947                   if (appinfo && g_desktop_app_info_get_is_hidden (appinfo))
1948                     {
1949                       g_object_unref (appinfo);
1950                       appinfo = NULL;
1951                       hidden = TRUE;
1952                     }
1953                                       
1954                   if (appinfo || hidden)
1955                     {
1956                       g_hash_table_insert (apps, g_strdup (desktop_id), appinfo);
1957
1958                       if (appinfo)
1959                         {
1960                           /* Reuse instead of strdup here */
1961                           appinfo->desktop_id = desktop_id;
1962                           desktop_id = NULL;
1963                         }
1964                     }
1965                 }
1966               g_free (desktop_id);
1967             }
1968           else
1969             {
1970               if (g_file_test (filename, G_FILE_TEST_IS_DIR))
1971                 {
1972                   subprefix = g_strconcat (prefix, basename, "-", NULL);
1973                   get_apps_from_dir (apps, filename, subprefix);
1974                   g_free (subprefix);
1975                 }
1976             }
1977           g_free (filename);
1978         }
1979       g_dir_close (dir);
1980     }
1981 }
1982
1983
1984 /**
1985  * g_app_info_get_all:
1986  *
1987  * Gets a list of all of the applications currently registered 
1988  * on this system.
1989  * 
1990  * For desktop files, this includes applications that have 
1991  * <literal>NoDisplay=true</literal> set or are excluded from 
1992  * display by means of <literal>OnlyShowIn</literal> or
1993  * <literal>NotShowIn</literal>. See g_app_info_should_show().
1994  * The returned list does not include applications which have
1995  * the <literal>Hidden</literal> key set. 
1996  * 
1997  * Returns: a newly allocated #GList of references to #GAppInfo<!---->s.
1998  **/
1999 GList *
2000 g_app_info_get_all (void)
2001 {
2002   const char * const *dirs;
2003   GHashTable *apps;
2004   GHashTableIter iter;
2005   gpointer value;
2006   int i;
2007   GList *infos;
2008
2009   dirs = get_applications_search_path ();
2010
2011   apps = g_hash_table_new_full (g_str_hash, g_str_equal,
2012                                 g_free, NULL);
2013
2014   
2015   for (i = 0; dirs[i] != NULL; i++)
2016     get_apps_from_dir (apps, dirs[i], "");
2017
2018
2019   infos = NULL;
2020   g_hash_table_iter_init (&iter, apps);
2021   while (g_hash_table_iter_next (&iter, NULL, &value))
2022     {
2023       if (value)
2024         infos = g_list_prepend (infos, value);
2025     }
2026
2027   g_hash_table_destroy (apps);
2028
2029   return g_list_reverse (infos);
2030 }
2031
2032 /* Cacheing of mimeinfo.cache and defaults.list files */
2033
2034 typedef struct {
2035   char *path;
2036   GHashTable *mime_info_cache_map;
2037   GHashTable *defaults_list_map;
2038   GHashTable *mimeapps_list_added_map;
2039   GHashTable *mimeapps_list_removed_map;
2040   time_t mime_info_cache_timestamp;
2041   time_t defaults_list_timestamp;
2042   time_t mimeapps_list_timestamp;
2043 } MimeInfoCacheDir;
2044
2045 typedef struct {
2046   GList *dirs;                       /* mimeinfo.cache and defaults.list */
2047   GHashTable *global_defaults_cache; /* global results of defaults.list lookup and validation */
2048   time_t last_stat_time;
2049   guint should_ping_mime_monitor : 1;
2050 } MimeInfoCache;
2051
2052 static MimeInfoCache *mime_info_cache = NULL;
2053 G_LOCK_DEFINE_STATIC (mime_info_cache);
2054
2055 static void mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir  *dir,
2056                                                      const char        *mime_type,
2057                                                      char             **new_desktop_file_ids);
2058
2059 static MimeInfoCache * mime_info_cache_new (void);
2060
2061 static void
2062 destroy_info_cache_value (gpointer  key, 
2063                           GList    *value, 
2064                           gpointer  data)
2065 {
2066   g_list_foreach (value, (GFunc)g_free, NULL);
2067   g_list_free (value);
2068 }
2069
2070 static void
2071 destroy_info_cache_map (GHashTable *info_cache_map)
2072 {
2073   g_hash_table_foreach (info_cache_map, (GHFunc)destroy_info_cache_value, NULL);
2074   g_hash_table_destroy (info_cache_map);
2075 }
2076
2077 static gboolean
2078 mime_info_cache_dir_out_of_date (MimeInfoCacheDir *dir,
2079                                  const char       *cache_file,
2080                                  time_t           *timestamp)
2081 {
2082   struct stat buf;
2083   char *filename;
2084   
2085   filename = g_build_filename (dir->path, cache_file, NULL);
2086   
2087   if (g_stat (filename, &buf) < 0)
2088     {
2089       g_free (filename);
2090       return TRUE;
2091     }
2092   g_free (filename);
2093
2094   if (buf.st_mtime != *timestamp) 
2095     return TRUE;
2096   
2097   return FALSE;
2098 }
2099
2100 /* Call with lock held */
2101 static gboolean
2102 remove_all (gpointer  key,
2103             gpointer  value,
2104             gpointer  user_data)
2105 {
2106   return TRUE;
2107 }
2108
2109
2110 static void
2111 mime_info_cache_blow_global_cache (void)
2112 {
2113   g_hash_table_foreach_remove (mime_info_cache->global_defaults_cache,
2114                                remove_all, NULL);
2115 }
2116
2117 static void
2118 mime_info_cache_dir_init (MimeInfoCacheDir *dir)
2119 {
2120   GError *load_error;
2121   GKeyFile *key_file;
2122   gchar *filename, **mime_types;
2123   int i;
2124   struct stat buf;
2125   
2126   load_error = NULL;
2127   mime_types = NULL;
2128   
2129   if (dir->mime_info_cache_map != NULL &&
2130       !mime_info_cache_dir_out_of_date (dir, "mimeinfo.cache",
2131                                         &dir->mime_info_cache_timestamp))
2132     return;
2133   
2134   if (dir->mime_info_cache_map != NULL)
2135     destroy_info_cache_map (dir->mime_info_cache_map);
2136   
2137   dir->mime_info_cache_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2138                                                     (GDestroyNotify) g_free,
2139                                                     NULL);
2140   
2141   key_file = g_key_file_new ();
2142   
2143   filename = g_build_filename (dir->path, "mimeinfo.cache", NULL);
2144   
2145   if (g_stat (filename, &buf) < 0)
2146     goto error;
2147   
2148   if (dir->mime_info_cache_timestamp > 0) 
2149     mime_info_cache->should_ping_mime_monitor = TRUE;
2150   
2151   dir->mime_info_cache_timestamp = buf.st_mtime;
2152   
2153   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error);
2154   
2155   g_free (filename);
2156   filename = NULL;
2157   
2158   if (load_error != NULL)
2159     goto error;
2160   
2161   mime_types = g_key_file_get_keys (key_file, MIME_CACHE_GROUP,
2162                                     NULL, &load_error);
2163   
2164   if (load_error != NULL)
2165     goto error;
2166   
2167   for (i = 0; mime_types[i] != NULL; i++)
2168     {
2169       gchar **desktop_file_ids;
2170       char *unaliased_type;
2171       desktop_file_ids = g_key_file_get_string_list (key_file,
2172                                                      MIME_CACHE_GROUP,
2173                                                      mime_types[i],
2174                                                      NULL,
2175                                                      NULL);
2176       
2177       if (desktop_file_ids == NULL)
2178         continue;
2179
2180       unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2181       mime_info_cache_dir_add_desktop_entries (dir,
2182                                                unaliased_type,
2183                                                desktop_file_ids);
2184       g_free (unaliased_type);
2185     
2186       g_strfreev (desktop_file_ids);
2187     }
2188   
2189   g_strfreev (mime_types);
2190   g_key_file_free (key_file);
2191   
2192   return;
2193  error:
2194   g_free (filename);
2195   g_key_file_free (key_file);
2196   
2197   if (mime_types != NULL)
2198     g_strfreev (mime_types);
2199   
2200   if (load_error)
2201     g_error_free (load_error);
2202 }
2203
2204 static void
2205 mime_info_cache_dir_init_defaults_list (MimeInfoCacheDir *dir)
2206 {
2207   GKeyFile *key_file;
2208   GError *load_error;
2209   gchar *filename, **mime_types;
2210   char *unaliased_type;
2211   char **desktop_file_ids;
2212   int i;
2213   struct stat buf;
2214
2215   load_error = NULL;
2216   mime_types = NULL;
2217
2218   if (dir->defaults_list_map != NULL &&
2219       !mime_info_cache_dir_out_of_date (dir, "defaults.list",
2220                                         &dir->defaults_list_timestamp))
2221     return;
2222   
2223   if (dir->defaults_list_map != NULL)
2224     g_hash_table_destroy (dir->defaults_list_map);
2225   dir->defaults_list_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2226                                                   g_free, (GDestroyNotify)g_strfreev);
2227   
2228
2229   key_file = g_key_file_new ();
2230   
2231   filename = g_build_filename (dir->path, "defaults.list", NULL);
2232   if (g_stat (filename, &buf) < 0)
2233     goto error;
2234
2235   if (dir->defaults_list_timestamp > 0) 
2236     mime_info_cache->should_ping_mime_monitor = TRUE;
2237
2238   dir->defaults_list_timestamp = buf.st_mtime;
2239
2240   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error);
2241   g_free (filename);
2242   filename = NULL;
2243
2244   if (load_error != NULL)
2245     goto error;
2246
2247   mime_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP,
2248                                     NULL, NULL);
2249   if (mime_types != NULL)
2250     {
2251       for (i = 0; mime_types[i] != NULL; i++)
2252         {
2253           desktop_file_ids = g_key_file_get_string_list (key_file,
2254                                                          DEFAULT_APPLICATIONS_GROUP,
2255                                                          mime_types[i],
2256                                                          NULL,
2257                                                          NULL);
2258           if (desktop_file_ids == NULL)
2259             continue;
2260           
2261           unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2262           g_hash_table_replace (dir->defaults_list_map,
2263                                 unaliased_type,
2264                                 desktop_file_ids);
2265         }
2266       
2267       g_strfreev (mime_types);
2268     }
2269
2270   g_key_file_free (key_file);
2271   return;
2272   
2273  error:
2274   g_free (filename);
2275   g_key_file_free (key_file);
2276   
2277   if (mime_types != NULL)
2278     g_strfreev (mime_types);
2279   
2280   if (load_error)
2281     g_error_free (load_error);
2282 }
2283
2284 static void
2285 mime_info_cache_dir_init_mimeapps_list (MimeInfoCacheDir *dir)
2286 {
2287   GKeyFile *key_file;
2288   GError *load_error;
2289   gchar *filename, **mime_types;
2290   char *unaliased_type;
2291   char **desktop_file_ids;
2292   int i;
2293   struct stat buf;
2294
2295   load_error = NULL;
2296   mime_types = NULL;
2297
2298   if (dir->mimeapps_list_added_map != NULL &&
2299       !mime_info_cache_dir_out_of_date (dir, "mimeapps.list",
2300                                         &dir->mimeapps_list_timestamp))
2301     return;
2302   
2303   if (dir->mimeapps_list_added_map != NULL)
2304     g_hash_table_destroy (dir->mimeapps_list_added_map);
2305   dir->mimeapps_list_added_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2306                                                         g_free, (GDestroyNotify)g_strfreev);
2307   
2308   if (dir->mimeapps_list_removed_map != NULL)
2309     g_hash_table_destroy (dir->mimeapps_list_removed_map);
2310   dir->mimeapps_list_removed_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2311                                                           g_free, (GDestroyNotify)g_strfreev);
2312
2313   key_file = g_key_file_new ();
2314   
2315   filename = g_build_filename (dir->path, "mimeapps.list", NULL);
2316   if (g_stat (filename, &buf) < 0)
2317     goto error;
2318
2319   if (dir->mimeapps_list_timestamp > 0) 
2320     mime_info_cache->should_ping_mime_monitor = TRUE;
2321
2322   dir->mimeapps_list_timestamp = buf.st_mtime;
2323
2324   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error);
2325   g_free (filename);
2326   filename = NULL;
2327
2328   if (load_error != NULL)
2329     goto error;
2330
2331   mime_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP,
2332                                     NULL, NULL);
2333   if (mime_types != NULL)
2334     {
2335       for (i = 0; mime_types[i] != NULL; i++)
2336         {
2337           desktop_file_ids = g_key_file_get_string_list (key_file,
2338                                                          ADDED_ASSOCIATIONS_GROUP,
2339                                                          mime_types[i],
2340                                                          NULL,
2341                                                          NULL);
2342           if (desktop_file_ids == NULL)
2343             continue;
2344           
2345           unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2346           g_hash_table_replace (dir->mimeapps_list_added_map,
2347                                 unaliased_type,
2348                                 desktop_file_ids);
2349         }
2350       
2351       g_strfreev (mime_types);
2352     }
2353
2354   mime_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP,
2355                                     NULL, NULL);
2356   if (mime_types != NULL)
2357     {
2358       for (i = 0; mime_types[i] != NULL; i++)
2359         {
2360           desktop_file_ids = g_key_file_get_string_list (key_file,
2361                                                          REMOVED_ASSOCIATIONS_GROUP,
2362                                                          mime_types[i],
2363                                                          NULL,
2364                                                          NULL);
2365           if (desktop_file_ids == NULL)
2366             continue;
2367           
2368           unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2369           g_hash_table_replace (dir->mimeapps_list_removed_map,
2370                                 unaliased_type,
2371                                 desktop_file_ids);
2372         }
2373       
2374       g_strfreev (mime_types);
2375     }
2376
2377   g_key_file_free (key_file);
2378   return;
2379   
2380  error:
2381   g_free (filename);
2382   g_key_file_free (key_file);
2383   
2384   if (mime_types != NULL)
2385     g_strfreev (mime_types);
2386   
2387   if (load_error)
2388     g_error_free (load_error);
2389 }
2390
2391 static MimeInfoCacheDir *
2392 mime_info_cache_dir_new (const char *path)
2393 {
2394   MimeInfoCacheDir *dir;
2395
2396   dir = g_new0 (MimeInfoCacheDir, 1);
2397   dir->path = g_strdup (path);
2398   
2399   return dir;
2400 }
2401
2402 static void
2403 mime_info_cache_dir_free (MimeInfoCacheDir *dir)
2404 {
2405   if (dir == NULL)
2406     return;
2407   
2408   if (dir->mime_info_cache_map != NULL)
2409     {
2410       destroy_info_cache_map (dir->mime_info_cache_map);
2411       dir->mime_info_cache_map = NULL;
2412       
2413   }
2414   
2415   if (dir->defaults_list_map != NULL)
2416     {
2417       g_hash_table_destroy (dir->defaults_list_map);
2418       dir->defaults_list_map = NULL;
2419     }
2420   
2421   if (dir->mimeapps_list_added_map != NULL)
2422     {
2423       g_hash_table_destroy (dir->mimeapps_list_added_map);
2424       dir->mimeapps_list_added_map = NULL;
2425     }
2426   
2427   if (dir->mimeapps_list_removed_map != NULL)
2428     {
2429       g_hash_table_destroy (dir->mimeapps_list_removed_map);
2430       dir->mimeapps_list_removed_map = NULL;
2431     }
2432   
2433   g_free (dir);
2434 }
2435
2436 static void
2437 mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir  *dir,
2438                                          const char        *mime_type,
2439                                          char             **new_desktop_file_ids)
2440 {
2441   GList *desktop_file_ids;
2442   int i;
2443   
2444   desktop_file_ids = g_hash_table_lookup (dir->mime_info_cache_map,
2445                                           mime_type);
2446   
2447   for (i = 0; new_desktop_file_ids[i] != NULL; i++)
2448     {
2449       if (!g_list_find (desktop_file_ids, new_desktop_file_ids[i]))
2450         desktop_file_ids = g_list_append (desktop_file_ids,
2451                                           g_strdup (new_desktop_file_ids[i]));
2452     }
2453   
2454   g_hash_table_insert (dir->mime_info_cache_map, g_strdup (mime_type), desktop_file_ids);
2455 }
2456
2457 static void
2458 mime_info_cache_init_dir_lists (void)
2459 {
2460   const char * const *dirs;
2461   int i;
2462   
2463   mime_info_cache = mime_info_cache_new ();
2464   
2465   dirs = get_applications_search_path ();
2466   
2467   for (i = 0; dirs[i] != NULL; i++)
2468     {
2469       MimeInfoCacheDir *dir;
2470       
2471       dir = mime_info_cache_dir_new (dirs[i]);
2472       
2473       if (dir != NULL)
2474         {
2475           mime_info_cache_dir_init (dir);
2476           mime_info_cache_dir_init_defaults_list (dir);
2477           mime_info_cache_dir_init_mimeapps_list (dir);
2478           
2479           mime_info_cache->dirs = g_list_append (mime_info_cache->dirs, dir);
2480         }
2481     }
2482 }
2483
2484 static void
2485 mime_info_cache_update_dir_lists (void)
2486 {
2487   GList *tmp;
2488   
2489   tmp = mime_info_cache->dirs;
2490   
2491   while (tmp != NULL)
2492     {
2493       MimeInfoCacheDir *dir = (MimeInfoCacheDir *) tmp->data;
2494
2495       /* No need to do this if we had file monitors... */
2496       mime_info_cache_blow_global_cache ();
2497       mime_info_cache_dir_init (dir);
2498       mime_info_cache_dir_init_defaults_list (dir);
2499       mime_info_cache_dir_init_mimeapps_list (dir);
2500       
2501       tmp = tmp->next;
2502     }
2503 }
2504
2505 static void
2506 mime_info_cache_init (void)
2507 {
2508   G_LOCK (mime_info_cache);
2509   if (mime_info_cache == NULL)
2510     mime_info_cache_init_dir_lists ();
2511   else
2512     {
2513       time_t now;
2514       
2515       time (&now);
2516       if (now >= mime_info_cache->last_stat_time + 10)
2517         {
2518           mime_info_cache_update_dir_lists ();
2519           mime_info_cache->last_stat_time = now;
2520         }
2521     }
2522   
2523   if (mime_info_cache->should_ping_mime_monitor)
2524     {
2525       /* g_idle_add (emit_mime_changed, NULL); */
2526       mime_info_cache->should_ping_mime_monitor = FALSE;
2527     }
2528   
2529   G_UNLOCK (mime_info_cache);
2530 }
2531
2532 static MimeInfoCache *
2533 mime_info_cache_new (void)
2534 {
2535   MimeInfoCache *cache;
2536   
2537   cache = g_new0 (MimeInfoCache, 1);
2538   
2539   cache->global_defaults_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
2540                                                         (GDestroyNotify) g_free,
2541                                                         (GDestroyNotify) g_free);
2542   return cache;
2543 }
2544
2545 static void
2546 mime_info_cache_free (MimeInfoCache *cache)
2547 {
2548   if (cache == NULL)
2549     return;
2550   
2551   g_list_foreach (cache->dirs,
2552                   (GFunc) mime_info_cache_dir_free,
2553                   NULL);
2554   g_list_free (cache->dirs);
2555   g_hash_table_destroy (cache->global_defaults_cache);
2556   g_free (cache);
2557 }
2558
2559 /**
2560  * mime_info_cache_reload:
2561  * @dir: directory path which needs reloading.
2562  * 
2563  * Reload the mime information for the @dir.
2564  */
2565 static void
2566 mime_info_cache_reload (const char *dir)
2567 {
2568   /* FIXME: just reload the dir that needs reloading,
2569    * don't blow the whole cache
2570    */
2571   if (mime_info_cache != NULL)
2572     {
2573       G_LOCK (mime_info_cache);
2574       mime_info_cache_free (mime_info_cache);
2575       mime_info_cache = NULL;
2576       G_UNLOCK (mime_info_cache);
2577     }
2578 }
2579
2580 static GList *
2581 append_desktop_entry (GList      *list, 
2582                       const char *desktop_entry,
2583                       GList      *removed_entries)
2584 {
2585   /* Add if not already in list, and valid */
2586   if (!g_list_find_custom (list, desktop_entry, (GCompareFunc) strcmp) &&
2587       !g_list_find_custom (removed_entries, desktop_entry, (GCompareFunc) strcmp))
2588     list = g_list_prepend (list, g_strdup (desktop_entry));
2589   
2590   return list;
2591 }
2592
2593 /**
2594  * get_all_desktop_entries_for_mime_type:
2595  * @mime_type: a mime type.
2596  * @except: NULL or a strv list
2597  *
2598  * Returns all the desktop ids for @mime_type. The desktop files
2599  * are listed in an order so that default applications are listed before
2600  * non-default ones, and handlers for inherited mimetypes are listed
2601  * after the base ones.
2602  *
2603  * Optionally doesn't list the desktop ids given in the @except 
2604  *
2605  * Return value: a #GList containing the desktop ids which claim
2606  *    to handle @mime_type.
2607  */
2608 static GList *
2609 get_all_desktop_entries_for_mime_type (const char *base_mime_type,
2610                                        const char **except)
2611 {
2612   GList *desktop_entries, *removed_entries, *list, *dir_list, *tmp;
2613   MimeInfoCacheDir *dir;
2614   char *mime_type;
2615   char **mime_types;
2616   char **default_entries;
2617   char **removed_associations;
2618   int i, j, k;
2619   GPtrArray *array;
2620   char **anc;
2621   
2622   mime_info_cache_init ();
2623
2624   /* collect all ancestors */
2625   mime_types = _g_unix_content_type_get_parents (base_mime_type);
2626   array = g_ptr_array_new ();
2627   for (i = 0; mime_types[i]; i++)
2628     g_ptr_array_add (array, mime_types[i]);
2629   g_free (mime_types);
2630   for (i = 0; i < array->len; i++)
2631     {
2632       anc = _g_unix_content_type_get_parents (g_ptr_array_index (array, i));
2633       for (j = 0; anc[j]; j++)
2634         {
2635           for (k = 0; k < array->len; k++)
2636             {
2637               if (strcmp (anc[j], g_ptr_array_index (array, k)) == 0)
2638                 break;
2639             }
2640           if (k == array->len) /* not found */
2641             g_ptr_array_add (array, g_strdup (anc[j]));
2642         }
2643       g_strfreev (anc);
2644     }
2645   g_ptr_array_add (array, NULL);
2646   mime_types = (char **)g_ptr_array_free (array, FALSE);
2647
2648   G_LOCK (mime_info_cache);
2649   
2650   removed_entries = NULL;
2651   desktop_entries = NULL;
2652
2653   for (i = 0; except != NULL && except[i] != NULL; i++)
2654     removed_entries = g_list_prepend (removed_entries, g_strdup (except[i]));
2655   
2656   for (i = 0; mime_types[i] != NULL; i++)
2657     {
2658       mime_type = mime_types[i];
2659
2660       /* Go through all apps listed as defaults */
2661       for (dir_list = mime_info_cache->dirs;
2662            dir_list != NULL;
2663            dir_list = dir_list->next)
2664         {
2665           dir = dir_list->data;
2666
2667           /* First added associations from mimeapps.list */
2668           default_entries = g_hash_table_lookup (dir->mimeapps_list_added_map, mime_type);
2669           for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++)
2670             desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries);
2671
2672           /* Then removed associations from mimeapps.list */
2673           removed_associations = g_hash_table_lookup (dir->mimeapps_list_removed_map, mime_type);
2674           for (j = 0; removed_associations != NULL && removed_associations[j] != NULL; j++)
2675             removed_entries = append_desktop_entry (removed_entries, removed_associations[j], NULL);
2676
2677           /* Then system defaults (or old per-user config) (using removed associations from this dir or earlier) */
2678           default_entries = g_hash_table_lookup (dir->defaults_list_map, mime_type);
2679           for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++)
2680             desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries);
2681         }
2682
2683       /* Go through all entries that support the mimetype */
2684       for (dir_list = mime_info_cache->dirs;
2685            dir_list != NULL;
2686            dir_list = dir_list->next) 
2687         {
2688           dir = dir_list->data;
2689         
2690           list = g_hash_table_lookup (dir->mime_info_cache_map, mime_type);
2691           for (tmp = list; tmp != NULL; tmp = tmp->next)
2692             desktop_entries = append_desktop_entry (desktop_entries, tmp->data, removed_entries);
2693         }
2694     }
2695   
2696   G_UNLOCK (mime_info_cache);
2697
2698   g_strfreev (mime_types);
2699
2700   g_list_foreach (removed_entries, (GFunc)g_free, NULL);
2701   g_list_free (removed_entries);
2702   
2703   desktop_entries = g_list_reverse (desktop_entries);
2704   
2705   return desktop_entries;
2706 }
2707
2708 /* GDesktopAppInfoLookup interface: */
2709
2710 static void g_desktop_app_info_lookup_base_init (gpointer g_class);
2711 static void g_desktop_app_info_lookup_class_init (gpointer g_class,
2712                                                   gpointer class_data);
2713
2714 GType
2715 g_desktop_app_info_lookup_get_type (void)
2716 {
2717   static volatile gsize g_define_type_id__volatile = 0;
2718
2719   if (g_once_init_enter (&g_define_type_id__volatile))
2720     {
2721       const GTypeInfo desktop_app_info_lookup_info =
2722       {
2723         sizeof (GDesktopAppInfoLookupIface), /* class_size */
2724         g_desktop_app_info_lookup_base_init,   /* base_init */
2725         NULL,           /* base_finalize */
2726         g_desktop_app_info_lookup_class_init,
2727         NULL,           /* class_finalize */
2728         NULL,           /* class_data */
2729         0,
2730         0,              /* n_preallocs */
2731         NULL
2732       };
2733       GType g_define_type_id =
2734         g_type_register_static (G_TYPE_INTERFACE, I_("GDesktopAppInfoLookup"),
2735                                 &desktop_app_info_lookup_info, 0);
2736
2737       g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_OBJECT);
2738
2739       g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
2740     }
2741
2742   return g_define_type_id__volatile;
2743 }
2744
2745 static void
2746 g_desktop_app_info_lookup_class_init (gpointer g_class,
2747                                       gpointer class_data)
2748 {
2749 }
2750
2751 static void
2752 g_desktop_app_info_lookup_base_init (gpointer g_class)
2753 {
2754 }
2755
2756 /**
2757  * g_desktop_app_info_lookup_get_default_for_uri_scheme:
2758  * @lookup: a #GDesktopAppInfoLookup
2759  * @uri_scheme: a string containing a URI scheme.
2760  *
2761  * Gets the default application for launching applications 
2762  * using this URI scheme for a particular GDesktopAppInfoLookup
2763  * implementation.
2764  *
2765  * The GDesktopAppInfoLookup interface and this function is used
2766  * to implement g_app_info_get_default_for_uri_scheme() backends
2767  * in a GIO module. There is no reason for applications to use it
2768  * directly. Applications should use g_app_info_get_default_for_uri_scheme().
2769  * 
2770  * Returns: #GAppInfo for given @uri_scheme or %NULL on error.
2771  */
2772 GAppInfo *
2773 g_desktop_app_info_lookup_get_default_for_uri_scheme (GDesktopAppInfoLookup *lookup,
2774                                                       const char            *uri_scheme)
2775 {
2776   GDesktopAppInfoLookupIface *iface;
2777   
2778   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO_LOOKUP (lookup), NULL);
2779
2780   iface = G_DESKTOP_APP_INFO_LOOKUP_GET_IFACE (lookup);
2781
2782   return (* iface->get_default_for_uri_scheme) (lookup, uri_scheme);
2783 }
2784
2785 #define __G_DESKTOP_APP_INFO_C__
2786 #include "gioaliasdef.c"