Initial commit
[platform/upstream/glib2.0.git] / gio / xdgmime / xdgmime.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmime.c: XDG Mime Spec mime resolver.  Based on version 0.11 of the spec.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  * 
6  * Copyright (C) 2003,2004  Red Hat, Inc.
7  * Copyright (C) 2003,2004  Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Licensed under the Academic Free License version 2.0
10  * Or under the following terms:
11  * 
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include "xdgmime.h"
33 #include "xdgmimeint.h"
34 #include "xdgmimeglob.h"
35 #include "xdgmimemagic.h"
36 #include "xdgmimealias.h"
37 #include "xdgmimeicon.h"
38 #include "xdgmimeparent.h"
39 #include "xdgmimecache.h"
40 #include <stdio.h>
41 #include <string.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <sys/time.h>
45 #include <unistd.h>
46 #include <assert.h>
47
48 typedef struct XdgDirTimeList XdgDirTimeList;
49 typedef struct XdgCallbackList XdgCallbackList;
50
51 static int need_reread = TRUE;
52 static time_t last_stat_time = 0;
53
54 static XdgGlobHash *global_hash = NULL;
55 static XdgMimeMagic *global_magic = NULL;
56 static XdgAliasList *alias_list = NULL;
57 static XdgParentList *parent_list = NULL;
58 static XdgDirTimeList *dir_time_list = NULL;
59 static XdgCallbackList *callback_list = NULL;
60 static XdgIconList *icon_list = NULL;
61 static XdgIconList *generic_icon_list = NULL;
62
63 XdgMimeCache **_caches = NULL;
64 static int n_caches = 0;
65
66 const char xdg_mime_type_unknown[] = "application/octet-stream";
67
68
69 enum
70 {
71   XDG_CHECKED_UNCHECKED,
72   XDG_CHECKED_VALID,
73   XDG_CHECKED_INVALID
74 };
75
76 struct XdgDirTimeList
77 {
78   time_t mtime;
79   char *directory_name;
80   int checked;
81   XdgDirTimeList *next;
82 };
83
84 struct XdgCallbackList
85 {
86   XdgCallbackList *next;
87   XdgCallbackList *prev;
88   int              callback_id;
89   XdgMimeCallback  callback;
90   void            *data;
91   XdgMimeDestroy   destroy;
92 };
93
94 /* Function called by xdg_run_command_on_dirs.  If it returns TRUE, further
95  * directories aren't looked at */
96 typedef int (*XdgDirectoryFunc) (const char *directory,
97                                  void       *user_data);
98
99 static void
100 xdg_dir_time_list_add (char   *file_name, 
101                        time_t  mtime)
102 {
103   XdgDirTimeList *list;
104
105   for (list = dir_time_list; list; list = list->next) 
106     {
107       if (strcmp (list->directory_name, file_name) == 0)
108         {
109           free (file_name);
110           return;
111         }
112     }
113   
114   list = calloc (1, sizeof (XdgDirTimeList));
115   list->checked = XDG_CHECKED_UNCHECKED;
116   list->directory_name = file_name;
117   list->mtime = mtime;
118   list->next = dir_time_list;
119   dir_time_list = list;
120 }
121  
122 static void
123 xdg_dir_time_list_free (XdgDirTimeList *list)
124 {
125   XdgDirTimeList *next;
126
127   while (list)
128     {
129       next = list->next;
130       free (list->directory_name);
131       free (list);
132       list = next;
133     }
134 }
135
136 static int
137 xdg_mime_init_from_directory (const char *directory)
138 {
139   char *file_name;
140   struct stat st;
141
142   assert (directory != NULL);
143
144   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
145   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
146   if (stat (file_name, &st) == 0)
147     {
148       XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name);
149
150       if (cache != NULL)
151         {
152           xdg_dir_time_list_add (file_name, st.st_mtime);
153
154           _caches = realloc (_caches, sizeof (XdgMimeCache *) * (n_caches + 2));
155           _caches[n_caches] = cache;
156           _caches[n_caches + 1] = NULL;
157           n_caches++;
158
159           return FALSE;
160         }
161     }
162   free (file_name);
163
164   file_name = malloc (strlen (directory) + strlen ("/mime/globs2") + 1);
165   strcpy (file_name, directory); strcat (file_name, "/mime/globs2");
166   if (stat (file_name, &st) == 0)
167     {
168       _xdg_mime_glob_read_from_file (global_hash, file_name, TRUE);
169       xdg_dir_time_list_add (file_name, st.st_mtime);
170     }
171   else
172     {
173       free (file_name);
174       file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
175       strcpy (file_name, directory); strcat (file_name, "/mime/globs");
176       if (stat (file_name, &st) == 0)
177         {
178           _xdg_mime_glob_read_from_file (global_hash, file_name, FALSE);
179           xdg_dir_time_list_add (file_name, st.st_mtime);
180         }
181       else
182         {
183           free (file_name);
184         }
185     }
186
187   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
188   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
189   if (stat (file_name, &st) == 0)
190     {
191       _xdg_mime_magic_read_from_file (global_magic, file_name);
192       xdg_dir_time_list_add (file_name, st.st_mtime);
193     }
194   else
195     {
196       free (file_name);
197     }
198
199   file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1);
200   strcpy (file_name, directory); strcat (file_name, "/mime/aliases");
201   _xdg_mime_alias_read_from_file (alias_list, file_name);
202   free (file_name);
203
204   file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1);
205   strcpy (file_name, directory); strcat (file_name, "/mime/subclasses");
206   _xdg_mime_parent_read_from_file (parent_list, file_name);
207   free (file_name);
208
209   file_name = malloc (strlen (directory) + strlen ("/mime/icons") + 1);
210   strcpy (file_name, directory); strcat (file_name, "/mime/icons");
211   _xdg_mime_icon_read_from_file (icon_list, file_name);
212   free (file_name);
213
214   file_name = malloc (strlen (directory) + strlen ("/mime/generic-icons") + 1);
215   strcpy (file_name, directory); strcat (file_name, "/mime/generic-icons");
216   _xdg_mime_icon_read_from_file (generic_icon_list, file_name);
217   free (file_name);
218
219   return FALSE; /* Keep processing */
220 }
221
222 /* Runs a command on all the directories in the search path */
223 static void
224 xdg_run_command_on_dirs (XdgDirectoryFunc  func,
225                          void             *user_data)
226 {
227   const char *xdg_data_home;
228   const char *xdg_data_dirs;
229   const char *ptr;
230
231   xdg_data_home = getenv ("XDG_DATA_HOME");
232   if (xdg_data_home)
233     {
234       if ((func) (xdg_data_home, user_data))
235         return;
236     }
237   else
238     {
239       const char *home;
240
241       home = getenv ("HOME");
242       if (home != NULL)
243         {
244           char *guessed_xdg_home;
245           int stop_processing;
246
247           guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1);
248           strcpy (guessed_xdg_home, home);
249           strcat (guessed_xdg_home, "/.local/share/");
250           stop_processing = (func) (guessed_xdg_home, user_data);
251           free (guessed_xdg_home);
252
253           if (stop_processing)
254             return;
255         }
256     }
257
258   xdg_data_dirs = getenv ("XDG_DATA_DIRS");
259   if (xdg_data_dirs == NULL)
260     xdg_data_dirs = "/usr/local/share/:/usr/share/";
261
262   ptr = xdg_data_dirs;
263
264   while (*ptr != '\000')
265     {
266       const char *end_ptr;
267       char *dir;
268       int len;
269       int stop_processing;
270
271       end_ptr = ptr;
272       while (*end_ptr != ':' && *end_ptr != '\000')
273         end_ptr ++;
274
275       if (end_ptr == ptr)
276         {
277           ptr++;
278           continue;
279         }
280
281       if (*end_ptr == ':')
282         len = end_ptr - ptr;
283       else
284         len = end_ptr - ptr + 1;
285       dir = malloc (len + 1);
286       strncpy (dir, ptr, len);
287       dir[len] = '\0';
288       stop_processing = (func) (dir, user_data);
289       free (dir);
290
291       if (stop_processing)
292         return;
293
294       ptr = end_ptr;
295     }
296 }
297
298 /* Checks file_path to make sure it has the same mtime as last time it was
299  * checked.  If it has a different mtime, or if the file doesn't exist, it
300  * returns FALSE.
301  *
302  * FIXME: This doesn't protect against permission changes.
303  */
304 static int
305 xdg_check_file (const char *file_path,
306                 int        *exists)
307 {
308   struct stat st;
309
310   /* If the file exists */
311   if (stat (file_path, &st) == 0)
312     {
313       XdgDirTimeList *list;
314
315       if (exists)
316         *exists = TRUE;
317
318       for (list = dir_time_list; list; list = list->next)
319         {
320           if (! strcmp (list->directory_name, file_path))
321             {
322               if (st.st_mtime == list->mtime)
323                 list->checked = XDG_CHECKED_VALID;
324               else 
325                 list->checked = XDG_CHECKED_INVALID;
326
327               return (list->checked != XDG_CHECKED_VALID);
328             }
329         }
330       return TRUE;
331     }
332
333   if (exists)
334     *exists = FALSE;
335
336   return FALSE;
337 }
338
339 static int
340 xdg_check_dir (const char *directory,
341                int        *invalid_dir_list)
342 {
343   int invalid, exists;
344   char *file_name;
345
346   assert (directory != NULL);
347
348   /* Check the mime.cache file */
349   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
350   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
351   invalid = xdg_check_file (file_name, &exists);
352   free (file_name);
353   if (invalid)
354     {
355       *invalid_dir_list = TRUE;
356       return TRUE;
357     }
358   else if (exists)
359     {
360       return FALSE;
361     }
362
363   /* Check the globs file */
364   file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
365   strcpy (file_name, directory); strcat (file_name, "/mime/globs");
366   invalid = xdg_check_file (file_name, NULL);
367   free (file_name);
368   if (invalid)
369     {
370       *invalid_dir_list = TRUE;
371       return TRUE;
372     }
373
374   /* Check the magic file */
375   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
376   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
377   invalid = xdg_check_file (file_name, NULL);
378   free (file_name);
379   if (invalid)
380     {
381       *invalid_dir_list = TRUE;
382       return TRUE;
383     }
384
385   return FALSE; /* Keep processing */
386 }
387
388 /* Walks through all the mime files stat()ing them to see if they've changed.
389  * Returns TRUE if they have. */
390 static int
391 xdg_check_dirs (void)
392 {
393   XdgDirTimeList *list;
394   int invalid_dir_list = FALSE;
395
396   for (list = dir_time_list; list; list = list->next)
397     list->checked = XDG_CHECKED_UNCHECKED;
398
399   xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir,
400                            &invalid_dir_list);
401
402   if (invalid_dir_list)
403     return TRUE;
404
405   for (list = dir_time_list; list; list = list->next)
406     {
407       if (list->checked != XDG_CHECKED_VALID)
408         return TRUE;
409     }
410
411   return FALSE;
412 }
413
414 /* We want to avoid stat()ing on every single mime call, so we only look for
415  * newer files every 5 seconds.  This will return TRUE if we need to reread the
416  * mime data from disk.
417  */
418 static int
419 xdg_check_time_and_dirs (void)
420 {
421   struct timeval tv;
422   time_t current_time;
423   int retval = FALSE;
424
425   gettimeofday (&tv, NULL);
426   current_time = tv.tv_sec;
427
428   if (current_time >= last_stat_time + 5)
429     {
430       retval = xdg_check_dirs ();
431       last_stat_time = current_time;
432     }
433
434   return retval;
435 }
436
437 /* Called in every public function.  It reloads the hash function if need be.
438  */
439 static void
440 xdg_mime_init (void)
441 {
442   if (xdg_check_time_and_dirs ())
443     {
444       xdg_mime_shutdown ();
445     }
446
447   if (need_reread)
448     {
449       global_hash = _xdg_glob_hash_new ();
450       global_magic = _xdg_mime_magic_new ();
451       alias_list = _xdg_mime_alias_list_new ();
452       parent_list = _xdg_mime_parent_list_new ();
453       icon_list = _xdg_mime_icon_list_new ();
454       generic_icon_list = _xdg_mime_icon_list_new ();
455
456       xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory,
457                                NULL);
458
459       need_reread = FALSE;
460     }
461 }
462
463 const char *
464 xdg_mime_get_mime_type_for_data (const void *data,
465                                  size_t      len,
466                                  int        *result_prio)
467 {
468   const char *mime_type;
469
470   xdg_mime_init ();
471
472   if (_caches)
473     return _xdg_mime_cache_get_mime_type_for_data (data, len, result_prio);
474
475   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, result_prio, NULL, 0);
476
477   if (mime_type)
478     return mime_type;
479
480   return XDG_MIME_TYPE_UNKNOWN;
481 }
482
483 const char *
484 xdg_mime_get_mime_type_for_file (const char  *file_name,
485                                  struct stat *statbuf)
486 {
487   const char *mime_type;
488   /* currently, only a few globs occur twice, and none
489    * more often, so 5 seems plenty.
490    */
491   const char *mime_types[5];
492   FILE *file;
493   unsigned char *data;
494   int max_extent;
495   int bytes_read;
496   struct stat buf;
497   const char *base_name;
498   int n;
499
500   if (file_name == NULL)
501     return NULL;
502   if (! _xdg_utf8_validate (file_name))
503     return NULL;
504
505   xdg_mime_init ();
506
507   if (_caches)
508     return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf);
509
510   base_name = _xdg_get_base_name (file_name);
511   n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 5);
512
513   if (n == 1)
514     return mime_types[0];
515
516   if (!statbuf)
517     {
518       if (stat (file_name, &buf) != 0)
519         return XDG_MIME_TYPE_UNKNOWN;
520
521       statbuf = &buf;
522     }
523
524   if (!S_ISREG (statbuf->st_mode))
525     return XDG_MIME_TYPE_UNKNOWN;
526
527   /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
528    * be large and need getting from a stream instead of just reading it all
529    * in. */
530   max_extent = _xdg_mime_magic_get_buffer_extents (global_magic);
531   data = malloc (max_extent);
532   if (data == NULL)
533     return XDG_MIME_TYPE_UNKNOWN;
534         
535   file = fopen (file_name, "r");
536   if (file == NULL)
537     {
538       free (data);
539       return XDG_MIME_TYPE_UNKNOWN;
540     }
541
542   bytes_read = fread (data, 1, max_extent, file);
543   if (ferror (file))
544     {
545       free (data);
546       fclose (file);
547       return XDG_MIME_TYPE_UNKNOWN;
548     }
549
550   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL,
551                                            mime_types, n);
552
553   free (data);
554   fclose (file);
555
556   if (mime_type)
557     return mime_type;
558
559   return XDG_MIME_TYPE_UNKNOWN;
560 }
561
562 const char *
563 xdg_mime_get_mime_type_from_file_name (const char *file_name)
564 {
565   const char *mime_type;
566
567   xdg_mime_init ();
568
569   if (_caches)
570     return _xdg_mime_cache_get_mime_type_from_file_name (file_name);
571
572   if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, &mime_type, 1))
573     return mime_type;
574   else
575     return XDG_MIME_TYPE_UNKNOWN;
576 }
577
578 int
579 xdg_mime_get_mime_types_from_file_name (const char *file_name,
580                                         const char  *mime_types[],
581                                         int          n_mime_types)
582 {
583   xdg_mime_init ();
584   
585   if (_caches)
586     return _xdg_mime_cache_get_mime_types_from_file_name (file_name, mime_types, n_mime_types);
587   
588   return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types);
589 }
590
591 int
592 xdg_mime_is_valid_mime_type (const char *mime_type)
593 {
594   /* FIXME: We should make this a better test
595    */
596   return _xdg_utf8_validate (mime_type);
597 }
598
599 void
600 xdg_mime_shutdown (void)
601 {
602   XdgCallbackList *list;
603
604   /* FIXME: Need to make this (and the whole library) thread safe */
605   if (dir_time_list)
606     {
607       xdg_dir_time_list_free (dir_time_list);
608       dir_time_list = NULL;
609     }
610         
611   if (global_hash)
612     {
613       _xdg_glob_hash_free (global_hash);
614       global_hash = NULL;
615     }
616   if (global_magic)
617     {
618       _xdg_mime_magic_free (global_magic);
619       global_magic = NULL;
620     }
621
622   if (alias_list)
623     {
624       _xdg_mime_alias_list_free (alias_list);
625       alias_list = NULL;
626     }
627
628   if (parent_list)
629     {
630       _xdg_mime_parent_list_free (parent_list);
631       parent_list = NULL;
632     }
633
634   if (icon_list)
635     {
636       _xdg_mime_icon_list_free (icon_list);
637       icon_list = NULL;
638     }
639
640   if (generic_icon_list)
641     {
642       _xdg_mime_icon_list_free (generic_icon_list);
643       generic_icon_list = NULL;
644     }
645   
646   if (_caches)
647     {
648       int i;
649
650       for (i = 0; i < n_caches; i++)
651         _xdg_mime_cache_unref (_caches[i]);
652       free (_caches);
653       _caches = NULL;
654       n_caches = 0;
655     }
656
657   for (list = callback_list; list; list = list->next)
658     (list->callback) (list->data);
659
660   need_reread = TRUE;
661 }
662
663 int
664 xdg_mime_get_max_buffer_extents (void)
665 {
666   xdg_mime_init ();
667   
668   if (_caches)
669     return _xdg_mime_cache_get_max_buffer_extents ();
670
671   return _xdg_mime_magic_get_buffer_extents (global_magic);
672 }
673
674 const char *
675 _xdg_mime_unalias_mime_type (const char *mime_type)
676 {
677   const char *lookup;
678
679   if (_caches)
680     return _xdg_mime_cache_unalias_mime_type (mime_type);
681
682   if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL)
683     return lookup;
684
685   return mime_type;
686 }
687
688 const char *
689 xdg_mime_unalias_mime_type (const char *mime_type)
690 {
691   xdg_mime_init ();
692
693   return _xdg_mime_unalias_mime_type (mime_type);
694 }
695
696 int
697 _xdg_mime_mime_type_equal (const char *mime_a,
698                            const char *mime_b)
699 {
700   const char *unalias_a, *unalias_b;
701
702   unalias_a = _xdg_mime_unalias_mime_type (mime_a);
703   unalias_b = _xdg_mime_unalias_mime_type (mime_b);
704
705   if (strcmp (unalias_a, unalias_b) == 0)
706     return 1;
707
708   return 0;
709 }
710
711 int
712 xdg_mime_mime_type_equal (const char *mime_a,
713                           const char *mime_b)
714 {
715   xdg_mime_init ();
716
717   return _xdg_mime_mime_type_equal (mime_a, mime_b);
718 }
719
720 int
721 xdg_mime_media_type_equal (const char *mime_a,
722                            const char *mime_b)
723 {
724   char *sep;
725
726   sep = strchr (mime_a, '/');
727   
728   if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0)
729     return 1;
730
731   return 0;
732 }
733
734 #if 1
735 static int
736 xdg_mime_is_super_type (const char *mime)
737 {
738   int length;
739   const char *type;
740
741   length = strlen (mime);
742   type = &(mime[length - 2]);
743
744   if (strcmp (type, "/*") == 0)
745     return 1;
746
747   return 0;
748 }
749 #endif
750
751 int
752 _xdg_mime_mime_type_subclass (const char *mime,
753                               const char *base)
754 {
755   const char *umime, *ubase;
756   const char **parents;
757
758   if (_caches)
759     return _xdg_mime_cache_mime_type_subclass (mime, base);
760
761   umime = _xdg_mime_unalias_mime_type (mime);
762   ubase = _xdg_mime_unalias_mime_type (base);
763
764   if (strcmp (umime, ubase) == 0)
765     return 1;
766
767 #if 1  
768   /* Handle supertypes */
769   if (xdg_mime_is_super_type (ubase) &&
770       xdg_mime_media_type_equal (umime, ubase))
771     return 1;
772 #endif
773
774   /*  Handle special cases text/plain and application/octet-stream */
775   if (strcmp (ubase, "text/plain") == 0 && 
776       strncmp (umime, "text/", 5) == 0)
777     return 1;
778
779   if (strcmp (ubase, "application/octet-stream") == 0)
780     return 1;
781   
782   parents = _xdg_mime_parent_list_lookup (parent_list, umime);
783   for (; parents && *parents; parents++)
784     {
785       if (_xdg_mime_mime_type_subclass (*parents, ubase))
786         return 1;
787     }
788
789   return 0;
790 }
791
792 int
793 xdg_mime_mime_type_subclass (const char *mime,
794                              const char *base)
795 {
796   xdg_mime_init ();
797
798   return _xdg_mime_mime_type_subclass (mime, base);
799 }
800
801 char **
802 xdg_mime_list_mime_parents (const char *mime)
803 {
804   const char **parents;
805   char **result;
806   int i, n;
807
808   if (_caches)
809     return _xdg_mime_cache_list_mime_parents (mime);
810
811   parents = xdg_mime_get_mime_parents (mime);
812
813   if (!parents)
814     return NULL;
815
816   for (i = 0; parents[i]; i++) ;
817   
818   n = (i + 1) * sizeof (char *);
819   result = (char **) malloc (n);
820   memcpy (result, parents, n);
821
822   return result;
823 }
824
825 const char **
826 xdg_mime_get_mime_parents (const char *mime)
827 {
828   const char *umime;
829
830   xdg_mime_init ();
831
832   umime = _xdg_mime_unalias_mime_type (mime);
833
834   return _xdg_mime_parent_list_lookup (parent_list, umime);
835 }
836
837 void 
838 xdg_mime_dump (void)
839 {
840   xdg_mime_init();
841
842   printf ("*** ALIASES ***\n\n");
843   _xdg_mime_alias_list_dump (alias_list);
844   printf ("\n*** PARENTS ***\n\n");
845   _xdg_mime_parent_list_dump (parent_list);
846   printf ("\n*** CACHE ***\n\n");
847   _xdg_glob_hash_dump (global_hash);
848   printf ("\n*** GLOBS ***\n\n");
849   _xdg_glob_hash_dump (global_hash);
850   printf ("\n*** GLOBS REVERSE TREE ***\n\n");
851   _xdg_mime_cache_glob_dump ();
852 }
853
854
855 /* Registers a function to be called every time the mime database reloads its files
856  */
857 int
858 xdg_mime_register_reload_callback (XdgMimeCallback  callback,
859                                    void            *data,
860                                    XdgMimeDestroy   destroy)
861 {
862   XdgCallbackList *list_el;
863   static int callback_id = 1;
864
865   /* Make a new list element */
866   list_el = calloc (1, sizeof (XdgCallbackList));
867   list_el->callback_id = callback_id;
868   list_el->callback = callback;
869   list_el->data = data;
870   list_el->destroy = destroy;
871   list_el->next = callback_list;
872   if (list_el->next)
873     list_el->next->prev = list_el;
874
875   callback_list = list_el;
876   callback_id ++;
877
878   return callback_id - 1;
879 }
880
881 void
882 xdg_mime_remove_callback (int callback_id)
883 {
884   XdgCallbackList *list;
885
886   for (list = callback_list; list; list = list->next)
887     {
888       if (list->callback_id == callback_id)
889         {
890           if (list->next)
891             list->next = list->prev;
892
893           if (list->prev)
894             list->prev->next = list->next;
895           else
896             callback_list = list->next;
897
898           /* invoke the destroy handler */
899           (list->destroy) (list->data);
900           free (list);
901           return;
902         }
903     }
904 }
905
906 const char *
907 xdg_mime_get_icon (const char *mime)
908 {
909   xdg_mime_init ();
910   
911   if (_caches)
912     return _xdg_mime_cache_get_icon (mime);
913
914   return _xdg_mime_icon_list_lookup (icon_list, mime);
915 }
916
917 const char *
918 xdg_mime_get_generic_icon (const char *mime)
919 {
920   xdg_mime_init ();
921   
922   if (_caches)
923     return _xdg_mime_cache_get_generic_icon (mime);
924
925   return _xdg_mime_icon_list_lookup (generic_icon_list, mime);
926 }