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