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
48typedef struct XdgDirTimeList XdgDirTimeList;
49typedef struct XdgCallbackList XdgCallbackList;
50
51static int need_reread = TRUE;
52static time_t last_stat_time = 0;
53
54static XdgGlobHash *global_hash = NULL;
55static XdgMimeMagic *global_magic = NULL;
56static XdgAliasList *alias_list = NULL;
57static XdgParentList *parent_list = NULL;
58static XdgDirTimeList *dir_time_list = NULL;
59static XdgCallbackList *callback_list = NULL;
60static XdgIconList *icon_list = NULL;
61static XdgIconList *generic_icon_list = NULL;
62
63XdgMimeCache **_caches = NULL;
64static int n_caches = 0;
65
66const char xdg_mime_type_unknown[] = "application/octet-stream";
67
68
69enum
70{
71  XDG_CHECKED_UNCHECKED,
72  XDG_CHECKED_VALID,
73  XDG_CHECKED_INVALID
74};
75
76struct XdgDirTimeList
77{
78  time_t mtime;
79  char *directory_name;
80  int checked;
81  XdgDirTimeList *next;
82};
83
84struct 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 */
96typedef int (*XdgDirectoryFunc) (const char *directory,
97				 void       *user_data);
98
99static void
100xdg_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
122static void
123xdg_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
136static int
137xdg_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 */
223static void
224xdg_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 */
304static int
305xdg_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
339static int
340xdg_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. */
390static int
391xdg_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 */
418static int
419xdg_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 */
439static void
440xdg_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
463const char *
464xdg_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
483const char *
484xdg_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
562const char *
563xdg_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
578int
579xdg_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
591int
592xdg_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
599void
600xdg_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
663int
664xdg_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
674const 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
688const char *
689xdg_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
696int
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
711int
712xdg_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
720int
721xdg_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
735static int
736xdg_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
751int
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
792int
793xdg_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
801char **
802xdg_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
825const char **
826xdg_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
837void
838xdg_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 */
857int
858xdg_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
881void
882xdg_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
906const char *
907xdg_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
917const char *
918xdg_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