ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / libgnomevfs / gnome-vfs-mime.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2
3 /*
4  * Copyright (C) 1998 Miguel de Icaza
5  * Copyright (C) 1997 Paolo Molaro
6  * Copyright (C) 2000, 2001 Eazel, Inc.
7  * All rights reserved.
8  *
9  * This file is part of the Gnome Library.
10  *
11  * The Gnome Library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or (at your option) any later version.
15  *
16  * The Gnome Library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public
22  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
23  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24  * Boston, MA 02111-1307, USA.
25  */
26
27 #include <config.h>
28 #include "gnome-vfs-mime.h"
29
30 #include "gnome-vfs-mime-private.h"
31 #include "gnome-vfs-mime-sniff-buffer-private.h"
32 #include "gnome-vfs-mime-utils.h"
33 #include "gnome-vfs-module-shared.h"
34 #include "gnome-vfs-ops.h"
35 #include "gnome-vfs-result.h"
36 #include "gnome-vfs-uri.h"
37 #include <dirent.h>
38 #include <regex.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <time.h>
42
43 static gboolean module_inited = FALSE;
44
45 static GHashTable *mime_extensions [2] = { NULL, NULL };
46 static GList      *mime_regexs     [2] = { NULL, NULL };
47
48 #define DEFAULT_DATE_TRACKER_INTERVAL   5       /* in milliseconds */
49
50 typedef struct {
51         char *mime_type;
52         regex_t regex;
53 } RegexMimePair;
54
55 typedef struct {
56         char *dirname;
57         unsigned int valid : 1;
58         unsigned int system_dir : 1;
59 } mime_dir_source_t;
60
61 typedef struct {
62         char *file_path;
63         time_t mtime;
64 } FileDateRecord;
65
66 struct FileDateTracker {
67         time_t last_checked;
68         guint check_interval;
69         GHashTable *records;
70 };
71
72 /* These ones are used to automatically reload mime-types on demand */
73 static mime_dir_source_t gnome_mime_dir, user_mime_dir;
74 static FileDateTracker *mime_data_date_tracker;
75
76 #ifdef G_THREADS_ENABLED
77
78 /* We lock this mutex whenever we modify global state in this module.  */
79 G_LOCK_DEFINE_STATIC (mime_mutex);
80
81 #endif /* G_LOCK_DEFINE_STATIC */
82
83
84 static char *
85 get_priority (char *def, int *priority)
86 {
87         *priority = 0;
88
89         if (*def == ',') {
90                 def++;
91                 if (*def == '1') {
92                         *priority = 0;
93                         def++;
94                 } else if (*def == '2') {
95                         *priority = 1;
96                         def++;
97                 }
98         }
99
100         while (*def && *def == ':')
101                 def++;
102
103         return def;
104 }
105
106 static int
107 list_find_type (gconstpointer value, gconstpointer type)
108 {
109         return g_ascii_strcasecmp((const char *) value, (const char *) type);
110 }
111
112 static void
113 add_to_key (char *mime_type, char *def)
114 {
115         int priority = 1;
116         char *s, *p, *ext;
117         GList *list = NULL;
118
119         if (strncmp (def, "ext", 3) == 0){
120                 char *tokp;
121
122                 def += 3;
123                 def = get_priority (def, &priority);
124                 s = p = g_strdup (def);
125
126                 while ((ext = strtok_r (s, " \t\n\r,", &tokp)) != NULL) {
127                         gboolean found;
128                         gpointer orig_key;
129
130                         found = g_hash_table_lookup_extended (mime_extensions [priority], ext,
131                                                               &orig_key, (gpointer *)&list);
132                         if (!found) {
133                                 orig_key = NULL;
134                                 list = NULL;
135                         }
136                         
137                         if (!g_list_find_custom (list, mime_type, list_find_type)) {
138                                 list = g_list_prepend (list, g_strdup (mime_type));
139                                 g_hash_table_insert (mime_extensions [priority],
140                                                      found ? orig_key : g_strdup (ext), list);
141                         }
142                         s = NULL;
143                 }
144                 g_free (p);
145         }
146
147         if (strncmp (def, "regex", 5) == 0) {
148                 RegexMimePair *mp;
149                 def += 5;
150                 def = get_priority (def, &priority);
151
152                 while (g_ascii_isspace (*def)) {
153                         def++;
154                 }
155
156                 if (*def == '\0') {
157                         return;
158                 }
159
160                 /* This was g_new instead of g_new0, but there seems
161                  * to be a bug in the Solaris? version of regcomp that
162                  * requires an initialized regex or it will crash.
163                  */
164                 mp = g_new0 (RegexMimePair, 1);
165                 if (regcomp (&mp->regex, def, REG_EXTENDED | REG_NOSUB)) {
166                         g_free (mp);
167                         return;
168                 }
169                 mp->mime_type = g_strdup (mime_type);
170
171                 mime_regexs [priority] = g_list_prepend (mime_regexs [priority], mp);
172         }
173 }
174
175 static void
176 mime_fill_from_file (const char *filename)
177 {
178         FILE *file;
179         char buf [1024];
180         char *current_key;
181
182         g_assert (filename != NULL);
183
184         _gnome_vfs_file_date_tracker_start_tracking_file (mime_data_date_tracker, filename);
185         file = fopen (filename, "r");
186
187         if (file == NULL) {
188                 return;
189         }
190         
191         current_key = NULL;
192         while (fgets (buf, sizeof (buf), file) != NULL) {
193                 char *p;
194
195                 if (buf [0] == '#') {
196                         continue;
197                 }
198
199                 /* Trim trailing spaces */
200                 for (p = buf + strlen (buf) - 1; p >= buf; p--) {
201                         if (!g_ascii_isspace (*p)) {
202                                 break;
203                         }
204                         *p = 0;
205                 }
206
207                 if (buf [0] == '\0') {
208                         continue;
209                 }
210
211                 if (buf [0] == '\t' || buf [0] == ' '){
212                         if (current_key){
213                                 char *p = buf;
214
215                                 while (g_ascii_isspace (*p))
216                                         p++;
217
218                                 if (*p == 0)
219                                         continue;
220
221                                 add_to_key (current_key, p);
222                         }
223                 } else {
224                         g_free (current_key);
225
226                         current_key = g_strdup (buf);
227                         if (current_key [strlen (current_key)-1] == ':')
228                                 current_key [strlen (current_key)-1] = 0;
229                 }
230         }
231
232         g_free (current_key);
233
234         fclose (file);
235 }
236
237 static void
238 mime_load (mime_dir_source_t *source)
239 {
240         DIR *dir;
241         struct dirent *dent;
242         const int extlen = sizeof (".mime") - 1;
243         char *filename;
244         struct stat s;
245
246         g_return_if_fail (source != NULL);
247         g_return_if_fail (source->dirname != NULL);
248
249         source->valid = (stat (source->dirname, &s) != -1);
250
251         if (source->system_dir) {
252                 filename = g_strconcat (source->dirname, "/gnome-vfs.mime", NULL);
253                 mime_fill_from_file (filename);
254                 g_free (filename);
255         }
256
257         dir = opendir (source->dirname);
258
259         while (dir != NULL) {
260                 int len;
261                 
262                 dent = readdir (dir);
263                 if (dent == NULL) {
264                         break;
265                 }
266                 
267                 len = strlen (dent->d_name);
268
269                 if (len <= extlen) {
270                         continue;
271                 }
272                 
273                 if (strcmp (dent->d_name + len - extlen, ".mime") != 0) {
274                         continue;
275                 }
276
277                 if (source->system_dir && strcmp (dent->d_name, "gnome-vfs.mime") == 0) {
278                         continue;
279                 }
280
281                 if (source->system_dir && strcmp (dent->d_name, "gnome.mime") == 0) {
282                         /* Ignore the obsolete "official" one so it doesn't override
283                          * the new official one.
284                          */
285                         continue;
286                 }
287
288                 if (!source->system_dir && strcmp (dent->d_name, "user.mime") == 0) {
289                         continue;
290                 }
291
292                 filename = g_strconcat (source->dirname, "/", dent->d_name, NULL);
293
294                 mime_fill_from_file (filename);
295                 g_free (filename);
296         }
297         if (dir != NULL)
298         closedir (dir);
299
300         if (!source->system_dir) {
301                 filename = g_strconcat (source->dirname, "/user.mime", NULL);
302                 mime_fill_from_file (filename);
303                 g_free (filename);
304         }
305
306         _gnome_vfs_file_date_tracker_start_tracking_file (mime_data_date_tracker, source->dirname);
307 }
308
309 static gboolean
310 remove_one_mime_hash_entry (gpointer key, gpointer value, gpointer user_data)
311 {
312         g_free (key);
313         g_list_foreach (value, (GFunc) g_free, NULL);
314         g_list_free (value);
315
316         return TRUE;
317 }
318
319 static void
320 mime_extensions_empty (void)
321 {
322         GList *p;
323         int i;
324         for (i = 0; i < 2; i++) {
325                 if (mime_extensions [i] != NULL) {
326                         g_hash_table_foreach_remove (mime_extensions [i], 
327                                                      remove_one_mime_hash_entry, NULL);
328                 }
329
330                 for (p = mime_regexs [i]; p != NULL; p = p->next){
331                         RegexMimePair *mp = p->data;
332
333                         g_free (mp->mime_type);
334                         regfree (&mp->regex);
335                         g_free (mp);
336                 }
337                 g_list_free (mime_regexs [i]);
338                 mime_regexs [i] = NULL;
339         }
340 }
341
342 static void
343 maybe_reload (void)
344 {
345         if (!_gnome_vfs_file_date_tracker_date_has_changed (mime_data_date_tracker)) {
346                 return;
347         }
348
349         mime_extensions_empty ();
350
351         mime_load (&gnome_mime_dir);
352         mime_load (&user_mime_dir);
353 }
354
355 static void
356 mime_init (void)
357 {
358         mime_extensions [0] = g_hash_table_new (g_str_hash, g_str_equal);
359         mime_extensions [1] = g_hash_table_new (g_str_hash, g_str_equal);
360
361         mime_data_date_tracker = _gnome_vfs_file_date_tracker_new ();
362         
363         gnome_mime_dir.dirname = g_strdup (DATADIR "/mime-info");
364         gnome_mime_dir.system_dir = TRUE;
365
366         user_mime_dir.dirname = g_strconcat (g_get_home_dir (), "/.gnome/mime-info", NULL);
367         user_mime_dir.system_dir = FALSE;
368
369         mime_load (&gnome_mime_dir);
370         mime_load (&user_mime_dir);
371
372         module_inited = TRUE;
373 }
374
375 /**
376  * gnome_vfs_mime_shutdown:
377  *
378  * Unload the MIME database from memory.
379  **/
380
381 void
382 gnome_vfs_mime_shutdown (void)
383 {
384         _gnome_vfs_mime_info_shutdown ();
385         _gnome_vfs_mime_clear_magic_table ();
386
387         if (!module_inited)
388                 return;
389
390         mime_extensions_empty ();
391         
392         g_hash_table_destroy (mime_extensions[0]);
393         g_hash_table_destroy (mime_extensions[1]);
394
395         _gnome_vfs_file_date_tracker_free (mime_data_date_tracker);
396         
397         g_free (gnome_mime_dir.dirname);
398         g_free (user_mime_dir.dirname);
399 }
400
401 /**
402  * gnome_vfs_mime_type_from_name_or_default:
403  * @filename: A filename (the file does not necesarily exist).
404  * @defaultv: A default value to be returned if no match is found
405  *
406  * This routine tries to determine the mime-type of the filename
407  * only by looking at the filename from the GNOME database of mime-types.
408  *
409  * Returns the mime-type of the @filename.  If no value could be
410  * determined, it will return @defaultv.
411  */
412 const char *
413 gnome_vfs_mime_type_from_name_or_default (const char *filename, const char *defaultv)
414 {
415         const gchar *ext;
416         char *upext;
417         int priority;
418         const char *result = defaultv;
419
420         if (filename == NULL) {
421                 return result;
422         }
423
424         G_LOCK (mime_mutex);
425
426         ext = strrchr (filename, '.');
427         if (ext != NULL) {
428                 ++ext;
429         }
430         
431         if (!module_inited) {
432                 mime_init ();
433         }
434
435         maybe_reload ();
436
437         for (priority = 1; priority >= 0; priority--){
438                 GList *l;
439                 GList *list = NULL ;
440                 
441                 if (ext != NULL) {
442                         
443                         list = g_hash_table_lookup (mime_extensions [priority], ext);
444                         if (list != NULL) {
445                                 list = g_list_first( list );
446                                 result = (const char *) list->data;
447                                 break;
448                         }
449
450                         /* Search for UPPER case extension */
451                         upext = g_ascii_strup (ext, -1);
452                         list = g_hash_table_lookup (mime_extensions [priority], upext);
453                         g_free (upext);
454                         if (list != NULL) {
455                                 list = g_list_first (list);
456                                 result = (const char *) list->data;
457                                 break;
458                         }
459
460                         /* Final check for lower case */
461                         upext = g_ascii_strdown (ext, -1);
462                         list = g_hash_table_lookup (mime_extensions [priority], upext);
463                         g_free (upext);
464                         if (list != NULL) {
465                                 list = g_list_first (list);
466                                 result = (const char *) list->data;
467                                 break;
468                         }
469                 }
470
471                 for (l = mime_regexs [priority]; l; l = l->next){
472                         RegexMimePair *mp = l->data;
473
474                         if (regexec (&mp->regex, filename, 0, 0, 0) == 0) {
475                                 result = mp->mime_type;
476                                 G_UNLOCK (mime_mutex);
477                                 return result;
478                         }
479                 }
480         }
481
482         G_UNLOCK (mime_mutex);
483         return result;
484 }
485
486 /**
487  * gnome_vfs_mime_type_from_name:
488  * @filename: A filename (the file does not necessarily exist).
489  *
490  * Determined the mime type for @filename.
491  *
492  * Returns the mime-type for this filename.
493  */
494 const char *
495 gnome_vfs_mime_type_from_name (const gchar * filename)
496 {
497         return gnome_vfs_mime_type_from_name_or_default (filename, GNOME_VFS_MIME_TYPE_UNKNOWN);
498 }
499
500 static const char *
501 gnome_vfs_get_mime_type_from_uri_internal (GnomeVFSURI *uri)
502 {
503         char *base_name;
504         const char *mime_type;
505
506         /* Return a mime type based on the file extension or NULL if no match. */
507         base_name = gnome_vfs_uri_extract_short_path_name (uri);
508         if (base_name == NULL) {
509                 return NULL;
510         }
511
512         mime_type = gnome_vfs_mime_type_from_name_or_default (base_name, NULL);
513         g_free (base_name);
514         return mime_type;
515 }
516
517 const char *
518 _gnome_vfs_get_mime_type_internal (GnomeVFSMimeSniffBuffer *buffer, const char *file_name)
519 {
520         const char *result;
521
522         result = NULL;
523         
524         if (buffer != NULL) {
525                 result = _gnome_vfs_mime_get_type_from_magic_table (buffer);
526                 
527                 if (result != NULL) {
528                         if (strcmp (result, "application/x-gzip") == 0) {
529                 
530                                 /* So many file types come compressed by gzip 
531                                  * that extensions are more reliable than magic
532                                  * typing. If the file has a suffix, then use 
533                                  * the type from the suffix.
534                                  *
535                                  * FIXME bugzilla.gnome.org 46867:
536                                  * Allow specific mime types to override 
537                                  * magic detection
538                          */
539                         if (file_name != NULL) {
540                                 result = gnome_vfs_mime_type_from_name_or_default (file_name, NULL);
541                         }
542                         
543                         if (result != NULL) {
544                                 return result;
545                         }
546                                 /* Didn't find an extension match,
547                                  * assume gzip. */
548                         return "application/x-gzip";
549                 }
550                         return result;
551                 }
552                 
553                 if (result == NULL) {
554                         if (_gnome_vfs_sniff_buffer_looks_like_text (buffer)) {
555                                 /* Text file -- treat extensions as a more 
556                                  * accurate source of type information.
557                                  */
558                                 if (file_name != NULL) {
559                                         result = gnome_vfs_mime_type_from_name_or_default (file_name, NULL);
560                                 }
561         
562                                 if (result != NULL) {
563                                         return result;
564                                 }
565
566                                 /* Didn't find an extension match, assume plain text. */
567                                 return "text/plain";
568
569                         } else if (_gnome_vfs_sniff_buffer_looks_like_mp3 (buffer)) {
570                                 return "audio/mpeg";
571                         }
572                 }
573         }
574         
575         if (result == NULL && file_name != NULL) {
576                 /* No type recognized -- fall back on extensions. */
577                 result = gnome_vfs_mime_type_from_name_or_default (file_name, NULL);
578         }
579         
580         if (result == NULL) {
581                 result = GNOME_VFS_MIME_TYPE_UNKNOWN;
582         }
583         
584         return result;
585 }
586
587 /**
588  * gnome_vfs_get_mime_type_common:
589  * @uri: a real file or a non-existent uri.
590  *
591  * Tries to guess the mime type of the file represented by @uir.
592  * Favors using the file data to the @uri extension.
593  * Handles passing @uri of a non-existent file by falling back
594  * on returning a type based on the extension.
595  *
596  * FIXME: This function will not necessarily return the same mime type as doing a
597  * get file info on the text uri.
598  *
599  * Returns: the mime-type for this uri.
600  * 
601  */
602 const char *
603 gnome_vfs_get_mime_type_common (GnomeVFSURI *uri)
604 {
605         const char *result;
606         char *base_name;
607         GnomeVFSMimeSniffBuffer *buffer;
608         GnomeVFSHandle *handle;
609         GnomeVFSResult error;
610
611         /* Check for special stat-defined file types first. */
612         result = gnome_vfs_get_special_mime_type (uri);
613         if (result != NULL) {
614                 return result;
615         }
616
617         error = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_READ);
618
619         if (error != GNOME_VFS_OK) {
620                 /* file may not exist, return type based on name only */
621                 return gnome_vfs_get_mime_type_from_uri_internal (uri);
622         }
623         
624         buffer = _gnome_vfs_mime_sniff_buffer_new_from_handle (handle);
625
626         base_name = gnome_vfs_uri_extract_short_path_name (uri);
627
628         result = _gnome_vfs_get_mime_type_internal (buffer, base_name);
629         g_free (base_name);
630
631         gnome_vfs_mime_sniff_buffer_free (buffer);
632         gnome_vfs_close (handle);
633
634         return result;
635 }
636
637 static GnomeVFSResult
638 file_seek_binder (gpointer context, GnomeVFSSeekPosition whence, 
639                   GnomeVFSFileOffset offset)
640 {
641         FILE *file = (FILE *)context;
642         int result;
643         result = fseek (file, offset, whence);
644         if (result < 0) {
645                 return gnome_vfs_result_from_errno ();
646         }
647         return GNOME_VFS_OK;
648 }
649
650 static GnomeVFSResult
651 file_read_binder (gpointer context, gpointer buffer, 
652                   GnomeVFSFileSize bytes, GnomeVFSFileSize *bytes_read)
653 {
654         FILE *file = (FILE *)context;   
655         *bytes_read = fread (buffer, 1, bytes, file);
656         if (*bytes_read < 0) {
657                 *bytes_read = 0;
658                 return gnome_vfs_result_from_errno ();
659         }
660
661         return GNOME_VFS_OK;
662 }
663
664 /**
665  * gnome_vfs_get_file_mime_type:
666  * @path: a path of a file.
667  * @optional_stat_info: optional stat buffer.
668  * @suffix_only: whether or not to do a magic-based lookup.
669  *
670  * Tries to guess the mime type of the file represented by @path.
671  * If @suffix_only is false, uses the mime-magic based lookup first.
672  * Handles passing @path of a non-existent file by falling back
673  * on returning a type based on the extension.
674  *
675  * Returns: the mime-type for this path
676  */
677 const char *
678 gnome_vfs_get_file_mime_type (const char *path, const struct stat *optional_stat_info,
679         gboolean suffix_only)
680 {
681         const char *result;
682         GnomeVFSMimeSniffBuffer *buffer;
683         struct stat tmp_stat_buffer;
684         FILE *file;
685
686         file = NULL;
687         result = NULL;
688
689         /* get the stat info if needed */
690         if (optional_stat_info == NULL && stat (path, &tmp_stat_buffer) == 0) {
691                 optional_stat_info = &tmp_stat_buffer;
692         }
693
694         /* single out special file types */
695         if (optional_stat_info && !S_ISREG(optional_stat_info->st_mode)) {
696                 if (S_ISDIR(optional_stat_info->st_mode)) {
697                         return "x-directory/normal";
698                 } else if (S_ISCHR(optional_stat_info->st_mode)) {
699                         return "x-special/device-char";
700                 } else if (S_ISBLK(optional_stat_info->st_mode)) {
701                         return "x-special/device-block";
702                 } else if (S_ISFIFO(optional_stat_info->st_mode)) {
703                         return "x-special/fifo";
704                 } else if (S_ISSOCK(optional_stat_info->st_mode)) {
705                         return "x-special/socket";
706                 } else {
707                         /* unknown entry type, return generic file type */
708                         return GNOME_VFS_MIME_TYPE_UNKNOWN;
709                 }
710         }
711
712         if (!suffix_only) {
713                 file = fopen(path, "r");
714         }
715
716         if (file != NULL) {
717                 buffer = _gnome_vfs_mime_sniff_buffer_new_generic
718                         (file_seek_binder, file_read_binder, file);
719
720                 result = _gnome_vfs_get_mime_type_internal (buffer, path);
721                 gnome_vfs_mime_sniff_buffer_free (buffer);
722                 fclose (file);
723         } else {
724                 result = _gnome_vfs_get_mime_type_internal (NULL, path);
725         }
726
727         
728         g_assert (result != NULL);
729         return result;
730 }
731
732 /**
733  * gnome_vfs_get_mime_type_from_uri:
734  * @uri: A file uri.
735  *
736  * Tries to guess the mime type of the file @uri by
737  * checking the file name extension. Works on non-existent
738  * files.
739  *
740  * Returns the mime-type for this filename.
741  */
742 const char *
743 gnome_vfs_get_mime_type_from_uri (GnomeVFSURI *uri)
744 {
745         const char *result;
746
747         result = gnome_vfs_get_mime_type_from_uri_internal (uri);
748         if (result == NULL) {
749                 /* no type, return generic file type */
750                 result = GNOME_VFS_MIME_TYPE_UNKNOWN;
751         }
752
753         return result;
754 }
755
756 /**
757  * gnome_vfs_get_mime_type_from_file_data:
758  * @uri: A file uri.
759  *
760  * Tries to guess the mime type of the file @uri by
761  * checking the file data using the magic patterns. Does not handle text files properly
762  *
763  * Returns the mime-type for this filename.
764  */
765 const char *
766 gnome_vfs_get_mime_type_from_file_data (GnomeVFSURI *uri)
767 {
768         const char *result;
769         GnomeVFSMimeSniffBuffer *buffer;
770         GnomeVFSHandle *handle;
771         GnomeVFSResult error;
772
773         error = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_READ);
774
775         if (error != GNOME_VFS_OK) {
776                 return GNOME_VFS_MIME_TYPE_UNKNOWN;
777         }
778         
779         buffer = _gnome_vfs_mime_sniff_buffer_new_from_handle (handle);
780         result = _gnome_vfs_get_mime_type_internal (buffer, NULL);      
781         gnome_vfs_mime_sniff_buffer_free (buffer);
782         gnome_vfs_close (handle);
783
784         return result;
785 }
786
787 /**
788  * gnome_vfs_get_mime_type_for_data:
789  * @data: A pointer to data in memory.
790  * @data_size: Size of the data.
791  *
792  * Tries to guess the mime type of the data in @data
793  * using the magic patterns.
794  *
795  * Returns the mime-type for this filename.
796  */
797 const char *
798 gnome_vfs_get_mime_type_for_data (gconstpointer data, int data_size)
799 {
800         const char *result;
801         GnomeVFSMimeSniffBuffer *buffer;
802
803         buffer = gnome_vfs_mime_sniff_buffer_new_from_existing_data
804                 (data, data_size);
805         result = _gnome_vfs_get_mime_type_internal (buffer, NULL);      
806
807         gnome_vfs_mime_sniff_buffer_free (buffer);
808
809         return result;
810 }
811
812 gboolean
813 gnome_vfs_mime_type_is_supertype (const char *mime_type)
814 {
815         int length;
816
817         if (mime_type == NULL) {
818                 return FALSE;
819         }
820
821         length = strlen (mime_type);
822
823         return length > 2
824                && mime_type[length - 2] == '/' 
825                && mime_type[length - 1] == '*';
826 }
827
828 static char *
829 extract_prefix_add_suffix (const char *string, const char *separator, const char *suffix)
830 {
831         const char *separator_position;
832         int prefix_length;
833         char *result;
834
835         separator_position = strstr (string, separator);
836         prefix_length = separator_position == NULL
837                 ? strlen (string)
838                 : separator_position - string;
839
840         result = g_malloc (prefix_length + strlen (suffix) + 1);
841         
842         strncpy (result, string, prefix_length);
843         result[prefix_length] = '\0';
844
845         strcat (result, suffix);
846
847         return result;
848 }
849
850 /* Returns the supertype for a mime type. Note that if called
851  * on a supertype it will return a copy of the supertype.
852  */
853 char *
854 gnome_vfs_get_supertype_from_mime_type (const char *mime_type)
855 {
856         if (mime_type == NULL) {
857                 return NULL;
858         }
859         return extract_prefix_add_suffix (mime_type, "/", "/*");
860 }
861
862 static void
863 file_date_record_update_mtime (FileDateRecord *record)
864 {
865         struct stat s;
866         record->mtime = (stat (record->file_path, &s) != -1) ? s.st_mtime : 0;
867 }
868
869 static FileDateRecord *
870 file_date_record_new (const char *file_path) {
871         FileDateRecord *record;
872
873         record = g_new0 (FileDateRecord, 1);
874         record->file_path = g_strdup (file_path);
875
876         file_date_record_update_mtime (record);
877
878         return record;
879 }
880
881 static void
882 file_date_record_free (FileDateRecord *record)
883 {
884         g_free (record->file_path);
885         g_free (record);
886 }
887
888 FileDateTracker *
889 _gnome_vfs_file_date_tracker_new (void)
890 {
891         FileDateTracker *tracker;
892
893         tracker = g_new0 (FileDateTracker, 1);
894         tracker->check_interval = DEFAULT_DATE_TRACKER_INTERVAL;
895         tracker->records = g_hash_table_new (g_str_hash, g_str_equal);
896
897         return tracker;
898 }
899
900 static gboolean
901 release_key_and_value (gpointer key, gpointer value, gpointer user_data)
902 {
903         g_free (key);
904         file_date_record_free (value);
905
906         return TRUE;
907 }
908
909 void
910 _gnome_vfs_file_date_tracker_free (FileDateTracker *tracker)
911 {
912         g_hash_table_foreach_remove (tracker->records, release_key_and_value, NULL);
913         g_hash_table_destroy (tracker->records);
914         g_free (tracker);
915 }
916
917 /*
918  * Record the current mod date for a specified file, so that we can check
919  * later whether it has changed.
920  */
921 void
922 _gnome_vfs_file_date_tracker_start_tracking_file (FileDateTracker *tracker, 
923                                                  const char *local_file_path)
924 {
925         FileDateRecord *record;
926
927         record = g_hash_table_lookup (tracker->records, local_file_path);
928         if (record != NULL) {
929                 file_date_record_update_mtime (record);
930         } else {
931                 g_hash_table_insert (tracker->records, 
932                                      g_strdup (local_file_path), 
933                                      file_date_record_new (local_file_path));
934         }
935 }
936
937 static void 
938 check_and_update_one (gpointer key, gpointer value, gpointer user_data)
939 {
940         FileDateRecord *record;
941         gboolean *return_has_changed;
942         struct stat s;
943
944         g_assert (key != NULL);
945         g_assert (value != NULL);
946         g_assert (user_data != NULL);
947
948         record = (FileDateRecord *)value;
949         return_has_changed = (gboolean *)user_data;
950
951         if (stat (record->file_path, &s) != -1) {
952                 if (s.st_mtime != record->mtime) {
953                         record->mtime = s.st_mtime;
954                         *return_has_changed = TRUE;
955                 }
956         }
957 }
958
959 gboolean
960 _gnome_vfs_file_date_tracker_date_has_changed (FileDateTracker *tracker)
961 {
962         time_t now;
963         gboolean any_date_changed;
964
965         now = time (NULL);
966
967         /* Note that this might overflow once in a blue moon, but the
968          * only side-effect of that would be a slightly-early check
969          * for changes.
970          */
971         if (tracker->last_checked + tracker->check_interval >= now) {
972                 return FALSE;
973         }
974
975         any_date_changed = FALSE;
976
977         g_hash_table_foreach (tracker->records, check_and_update_one, &any_date_changed);
978
979         tracker->last_checked = now;
980
981         return any_date_changed;
982 }
983
984
985
986
987 /**
988  * gnome_vfs_get_mime_type:
989  * @text_uri: URI of the file for which to get the mime type
990  * 
991  * Determine the mime type of @text_uri. The mime type is determined
992  * in the same way as by gnome_vfs_get_file_info(). This is meant as
993  * a convenience function for times when you only want the mime type.
994  * 
995  * Return value: The mime type, or NULL if there is an error reading 
996  * the file.
997  **/
998 char *
999 gnome_vfs_get_mime_type (const char *text_uri)
1000 {
1001         GnomeVFSFileInfo *info;
1002         char *mime_type;
1003         GnomeVFSResult result;
1004
1005         info = gnome_vfs_file_info_new ();
1006         result = gnome_vfs_get_file_info (text_uri, info,
1007                                           GNOME_VFS_FILE_INFO_GET_MIME_TYPE |
1008                                           GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
1009         if (info->mime_type == NULL || result != GNOME_VFS_OK) {
1010                 mime_type = NULL;
1011         } else {
1012                 mime_type = g_strdup (info->mime_type);
1013         }
1014         gnome_vfs_file_info_unref (info);
1015
1016         return mime_type;
1017 }