Home | History | Annotate | Download | only in xdgmime
      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 (at) 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);
    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);
    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 }
    927