diff --git a/ChangeLog b/ChangeLog index 12d9c061693d606dbd345f2bca38f97147c8ba01..9c7bbc3e4170f7eeb59b31f938e1eac91890b5bb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2005-08-06 Benedikt Meurer <benny@xfce.org> + + * thunar-vfs/thunar-vfs-mime-legacy.c: Add support for the various + globs to the legacy mime provider. + * thunar-vfs/thunar-vfs-mime-database.c(thunar_vfs_mime_database_init): + Read atleast 64 bytes from every file to reliably detect text files. + * thunar-vfs/thunar-vfs-mime-database.c: When looking up the MIME info + for a given file, and the magic check doesn't return a match, we'll + return "application/x-executable" for every regular file that is + atleast 1 byte in size and has the executable bit set, as this is + more precise than "application/octet-stream". + 2005-08-06 Benedikt Meurer <benny@xfce.org> * configure.in.in: Use AC_TRY_LINK() to avoid trouble with funky diff --git a/thunar-vfs/thunar-vfs-mime-database.c b/thunar-vfs/thunar-vfs-mime-database.c index 78dd1daea11279868831859a7b1729ed2ede83df..4e4eda0a319c6e8b3e8623b058fa078bb8ab64ca 100644 --- a/thunar-vfs/thunar-vfs-mime-database.c +++ b/thunar-vfs/thunar-vfs-mime-database.c @@ -74,6 +74,9 @@ static void thunar_vfs_mime_database_init (T static void thunar_vfs_mime_database_finalize (ExoObject *object); static ThunarVfsMimeInfo *thunar_vfs_mime_database_get_info_unlocked (ThunarVfsMimeDatabase *database, const gchar *mime_type); +static ThunarVfsMimeInfo *thunar_vfs_mime_database_get_info_for_data_unlocked (ThunarVfsMimeDatabase *database, + gconstpointer data, + gsize length); static ThunarVfsMimeInfo *thunar_vfs_mime_database_get_info_for_name_unlocked (ThunarVfsMimeDatabase *database, const gchar *name); @@ -182,10 +185,11 @@ thunar_vfs_mime_database_init (ThunarVfsMimeDatabase *database) } g_strfreev (basedirs); - /* clamp the max buffer extents to [1..256] to make - * sure we don't try insane values. + /* clamp the max buffer extents to [64..256] to make + * sure we don't try insane values (everything below + * 64 bytes would be useless for the UTF-8 check). */ - database->max_buffer_extents = CLAMP (database->max_buffer_extents, 1, 256); + database->max_buffer_extents = CLAMP (database->max_buffer_extents, 64, 256); /* collect the stop characters */ database->stopchars = g_new (gchar, g_list_length (stopchars) + 1); @@ -309,6 +313,53 @@ again: +static ThunarVfsMimeInfo* +thunar_vfs_mime_database_get_info_for_data_unlocked (ThunarVfsMimeDatabase *database, + gconstpointer data, + gsize length) +{ + ThunarVfsMimeProvider *provider; + ThunarVfsMimeInfo *info = NULL; + const gchar *type_best; + const gchar *type; + GList *lp; + gint prio_best; + gint prio; + + if (G_LIKELY (data != NULL && length > 0)) + { + /* perform a 'best fit' lookup on all active providers */ + for (type_best = NULL, prio_best = -1, lp = database->providers; lp != NULL; lp = lp->next) + { + provider = THUNAR_VFS_MIME_PROVIDER_DATA (lp->data)->provider; + if (G_LIKELY (provider != NULL)) + { + type = thunar_vfs_mime_provider_lookup_data (provider, data, length, &prio); + if (G_LIKELY (type != NULL && prio > prio_best)) + { + prio_best = prio; + type_best = type; + } + } + } + + if (G_LIKELY (type_best != NULL)) + { + /* lookup the info for the best type (if any) */ + info = thunar_vfs_mime_database_get_info_unlocked (database, type_best); + } + else if (g_utf8_validate (data, length, NULL)) + { + /* we have valid UTF-8 text here! */ + info = thunar_vfs_mime_database_get_info_unlocked (database, "text/plain"); + } + } + + return info; +} + + + static ThunarVfsMimeInfo* thunar_vfs_mime_database_get_info_for_name_unlocked (ThunarVfsMimeDatabase *database, const gchar *name) @@ -443,46 +494,14 @@ thunar_vfs_mime_database_get_info_for_data (ThunarVfsMimeDatabase *database, gconstpointer data, gsize length) { - ThunarVfsMimeProvider *provider; - ThunarVfsMimeInfo *info = NULL; - const gchar *type_best; - const gchar *type; - GList *lp; - gint prio_best; - gint prio; + ThunarVfsMimeInfo *info; g_return_val_if_fail (THUNAR_VFS_IS_MIME_DATABASE (database), NULL); g_mutex_lock (database->lock); - if (G_LIKELY (data != NULL && length > 0)) - { - /* perform a 'best fit' lookup on all active providers */ - for (type_best = NULL, prio_best = -1, lp = database->providers; lp != NULL; lp = lp->next) - { - provider = THUNAR_VFS_MIME_PROVIDER_DATA (lp->data)->provider; - if (G_LIKELY (provider != NULL)) - { - type = thunar_vfs_mime_provider_lookup_data (provider, data, length, &prio); - if (G_LIKELY (type != NULL && prio > prio_best)) - { - prio_best = prio; - type_best = type; - } - } - } - - if (G_LIKELY (type_best != NULL)) - { - /* lookup the info for the best type (if any) */ - info = thunar_vfs_mime_database_get_info_unlocked (database, type_best); - } - else if (g_utf8_validate (data, length, NULL)) - { - /* we have valid UTF-8 text here! */ - info = thunar_vfs_mime_database_get_info_unlocked (database, "text/plain"); - } - } + /* try to determine a MIME type based on the data */ + info = thunar_vfs_mime_database_get_info_for_data_unlocked (database, data, length); /* fallback to 'application/octet-stream' if we could not determine any type */ if (G_UNLIKELY (info == NULL)) @@ -617,7 +636,21 @@ thunar_vfs_mime_database_get_info_for_file (ThunarVfsMimeDatabase *database, /* try to determine a type from the buffer contents */ if (G_LIKELY (nbytes > 0)) - info = thunar_vfs_mime_database_get_info_for_data (database, buffer, nbytes); + { + g_mutex_lock (database->lock); + + /* the regular magic check first */ + info = thunar_vfs_mime_database_get_info_for_data_unlocked (database, buffer, nbytes); + + /* then if magic doesn't tell us anything and the file is marked, + * as executable, we just guess "application/x-executable", which + * is atleast more precise than "application/octet-stream". + */ + if (G_UNLIKELY (info == NULL && (stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)) + info = thunar_vfs_mime_database_get_info_unlocked (database, "application/x-executable"); + + g_mutex_unlock (database->lock); + } /* cleanup */ g_free (buffer); diff --git a/thunar-vfs/thunar-vfs-mime-legacy.c b/thunar-vfs/thunar-vfs-mime-legacy.c index f154652eef8f31be3b50862004d67773b2858090..f49e0aeea979414a9210403b0d260529f01fa7ed 100644 --- a/thunar-vfs/thunar-vfs-mime-legacy.c +++ b/thunar-vfs/thunar-vfs-mime-legacy.c @@ -25,11 +25,32 @@ #include <config.h> #endif +#ifdef HAVE_FNMATCH_H +#include <fnmatch.h> +#endif +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + #include <thunar-vfs/thunar-vfs-mime-legacy.h> +#define THUNAR_VFS_MIME_LEGACY_GLOB(obj) ((ThunarVfsMimeLegacyGlob *) (obj)) +#define THUNAR_VFS_MIME_LEGACY_SUFFIX(obj) ((ThunarVfsMimeLegacySuffix *) (obj)) + + + +typedef struct _ThunarVfsMimeLegacyGlob ThunarVfsMimeLegacyGlob; +typedef struct _ThunarVfsMimeLegacySuffix ThunarVfsMimeLegacySuffix; + + + static void thunar_vfs_mime_legacy_class_init (ThunarVfsMimeLegacyClass *klass); +static void thunar_vfs_mime_legacy_init (ThunarVfsMimeLegacy *legacy); static void thunar_vfs_mime_legacy_finalize (ExoObject *object); static const gchar *thunar_vfs_mime_legacy_lookup_data (ThunarVfsMimeProvider *provider, gconstpointer data, @@ -44,6 +65,8 @@ static const gchar *thunar_vfs_mime_legacy_lookup_glob (ThunarVfsMime const gchar *filename); static GList *thunar_vfs_mime_legacy_get_stop_characters (ThunarVfsMimeProvider *provider); static gsize thunar_vfs_mime_legacy_get_max_buffer_extents (ThunarVfsMimeProvider *provider); +static gboolean thunar_vfs_mime_legacy_parse_globs (ThunarVfsMimeLegacy *legacy, + const gchar *directory); @@ -55,6 +78,28 @@ struct _ThunarVfsMimeLegacyClass struct _ThunarVfsMimeLegacy { ThunarVfsMimeProvider __parent__; + + GStringChunk *string_chunk; + GMemChunk *suffix_chunk; + GMemChunk *glob_chunk; + + GHashTable *literals; + ThunarVfsMimeLegacySuffix *suffixes; + GList *globs; +}; + +struct _ThunarVfsMimeLegacyGlob +{ + const gchar *pattern; + const gchar *mime_type; +}; + +struct _ThunarVfsMimeLegacySuffix +{ + ThunarVfsMimeLegacySuffix *child; + ThunarVfsMimeLegacySuffix *next; + const gchar *mime_type; + gunichar character; }; @@ -80,7 +125,7 @@ thunar_vfs_mime_legacy_get_type (void) NULL, sizeof (ThunarVfsMimeLegacy), 0, - NULL, + (GInstanceInitFunc) thunar_vfs_mime_legacy_init, NULL, }; @@ -115,9 +160,34 @@ thunar_vfs_mime_legacy_class_init (ThunarVfsMimeLegacyClass *klass) +static void +thunar_vfs_mime_legacy_init (ThunarVfsMimeLegacy *legacy) +{ + legacy->string_chunk = g_string_chunk_new (1024); + legacy->glob_chunk = g_mem_chunk_create (ThunarVfsMimeLegacyGlob, 32, G_ALLOC_ONLY); + legacy->suffix_chunk = g_mem_chunk_create (ThunarVfsMimeLegacySuffix, 128, G_ALLOC_ONLY); + + legacy->literals = g_hash_table_new (g_str_hash, g_str_equal); +} + + + static void thunar_vfs_mime_legacy_finalize (ExoObject *object) { + ThunarVfsMimeLegacy *legacy = THUNAR_VFS_MIME_LEGACY (object); + + /* free the list of globs */ + g_list_free (legacy->globs); + + /* free literals hash table */ + g_hash_table_destroy (legacy->literals); + + /* free chunks */ + g_string_chunk_free (legacy->string_chunk); + g_mem_chunk_destroy (legacy->suffix_chunk); + g_mem_chunk_destroy (legacy->glob_chunk); + (*EXO_OBJECT_CLASS (thunar_vfs_mime_legacy_parent_class)->finalize) (object); } @@ -138,6 +208,100 @@ static const gchar* thunar_vfs_mime_legacy_lookup_literal (ThunarVfsMimeProvider *provider, const gchar *filename) { + return g_hash_table_lookup (THUNAR_VFS_MIME_LEGACY (provider)->literals, filename); +} + + + +static ThunarVfsMimeLegacySuffix* +suffix_insert (ThunarVfsMimeLegacy *legacy, + ThunarVfsMimeLegacySuffix *suffix_node, + const gchar *pattern, + const gchar *mime_type) +{ + ThunarVfsMimeLegacySuffix *previous; + ThunarVfsMimeLegacySuffix *node; + gboolean found_node = FALSE; + gunichar character; + + character = g_utf8_get_char (pattern); + + if (suffix_node == NULL || character < suffix_node->character) + { + node = g_chunk_new0 (ThunarVfsMimeLegacySuffix, legacy->suffix_chunk); + node->next = suffix_node; + node->character = character; + suffix_node = node; + } + else if (character == suffix_node->character) + { + node = suffix_node; + } + else + { + for (previous = suffix_node, node = previous->next; node != NULL; previous = node, node = node->next) + { + if (character < node->character) + { + node = g_chunk_new0 (ThunarVfsMimeLegacySuffix, legacy->suffix_chunk); + node->next = previous->next; + node->character = character; + previous->next = node; + found_node = TRUE; + break; + } + else if (character == node->character) + { + found_node = TRUE; + break; + } + } + + if (!found_node) + { + node = g_chunk_new0 (ThunarVfsMimeLegacySuffix, legacy->suffix_chunk); + node->next = previous->next; + node->character = character; + previous->next = node; + } + } + + pattern = g_utf8_next_char (pattern); + if (G_UNLIKELY (*pattern == '\0')) + node->mime_type = mime_type; + else + node->child = suffix_insert (legacy, node->child, pattern, mime_type); + + return suffix_node; +} + + + +static const gchar* +suffix_lookup (ThunarVfsMimeLegacySuffix *suffix_node, + const gchar *filename, + gboolean ignore_case) +{ + ThunarVfsMimeLegacySuffix *node; + gunichar character; + + if (G_UNLIKELY (suffix_node == NULL)) + return NULL; + + character = g_utf8_get_char (filename); + if (G_UNLIKELY (ignore_case)) + character = g_unichar_tolower (character); + + for (node = suffix_node; node != NULL && character >= node->character; node = node->next) + if (character == node->character) + { + filename = g_utf8_next_char (filename); + if (*filename == '\0') + return node->mime_type; + else + return suffix_lookup (node->child, filename, ignore_case); + } + return NULL; } @@ -148,7 +312,7 @@ thunar_vfs_mime_legacy_lookup_suffix (ThunarVfsMimeProvider *provider, const gchar *suffix, gboolean ignore_case) { - return NULL; + return suffix_lookup (THUNAR_VFS_MIME_LEGACY (provider)->suffixes, suffix, ignore_case); } @@ -157,6 +321,12 @@ static const gchar* thunar_vfs_mime_legacy_lookup_glob (ThunarVfsMimeProvider *provider, const gchar *filename) { + GList *lp; + + for (lp = THUNAR_VFS_MIME_LEGACY (provider)->globs; lp != NULL; lp = lp->next) + if (fnmatch (THUNAR_VFS_MIME_LEGACY_GLOB (lp->data)->pattern, filename, 0) == 0) + return THUNAR_VFS_MIME_LEGACY_GLOB (lp->data)->mime_type; + return NULL; } @@ -165,7 +335,14 @@ thunar_vfs_mime_legacy_lookup_glob (ThunarVfsMimeProvider *provider, static GList* thunar_vfs_mime_legacy_get_stop_characters (ThunarVfsMimeProvider *provider) { - return NULL; + ThunarVfsMimeLegacySuffix *node; + GList *stopchars = NULL; + + for (node = THUNAR_VFS_MIME_LEGACY (provider)->suffixes; node != NULL; node = node->next) + if (node->character < 128u) + stopchars = g_list_prepend (stopchars, GUINT_TO_POINTER (node->character)); + + return stopchars; } @@ -178,6 +355,74 @@ thunar_vfs_mime_legacy_get_max_buffer_extents (ThunarVfsMimeProvider *provider) +static gboolean +thunar_vfs_mime_legacy_parse_globs (ThunarVfsMimeLegacy *legacy, + const gchar *directory) +{ + ThunarVfsMimeLegacyGlob *glob; + gchar line[2048]; + gchar *pattern; + gchar *path; + gchar *name; + gchar *lp; + FILE *fp; + + /* try to open the "globs" file */ + path = g_build_filename (directory, "globs", NULL); + fp = fopen (path, "r"); + g_free (path); + + /* cannot continue */ + if (G_UNLIKELY (fp == NULL)) + return FALSE; + + /* parse all globs */ + while (fgets (line, sizeof (line), fp) != NULL) + { + /* skip whitespace/comments */ + for (lp = line; g_ascii_isspace (*lp); ++lp); + if (*lp == '\0' || *lp == '#') + continue; + + /* extract the MIME-type name */ + for (name = lp; *lp != '\0' && *lp != ':'; ++lp); + if (*lp == '\0' || name == lp) + continue; + + /* extract the pattern */ + for (*lp = '\0', pattern = ++lp; *lp != '\0' && *lp != '\n' && *lp != '\r'; ++lp); + *lp = '\0'; + if (*pattern == '\0') + continue; + + /* insert the name into the string chunk */ + name = g_string_chunk_insert_const (legacy->string_chunk, name); + + /* determine the type of the pattern */ + if (strpbrk (pattern, "*?[") == NULL) + { + g_hash_table_insert (legacy->literals, g_string_chunk_insert (legacy->string_chunk, pattern), name); + } + else if (pattern[0] == '*' && pattern[1] == '.' && strpbrk (pattern + 2, "*?[") == NULL) + { + legacy->suffixes = suffix_insert (legacy, legacy->suffixes, pattern + 1, name); + } + else + { + glob = g_chunk_new (ThunarVfsMimeLegacyGlob, legacy->glob_chunk); + glob->pattern = g_string_chunk_insert (legacy->string_chunk, pattern); + glob->mime_type = name; + legacy->globs = g_list_append (legacy->globs, glob); + } + } + + fclose (fp); + + return TRUE; +} + + + /** * thunar_vfs_mime_legacy_new: * @directory : an XDG mime base directory. @@ -194,7 +439,20 @@ thunar_vfs_mime_legacy_get_max_buffer_extents (ThunarVfsMimeProvider *provider) ThunarVfsMimeProvider* thunar_vfs_mime_legacy_new (const gchar *directory) { - return NULL; + ThunarVfsMimeLegacy *legacy; + + /* allocate the new object */ + legacy = exo_object_new (THUNAR_VFS_TYPE_MIME_LEGACY); + + /* try to parse the globs file */ + if (!thunar_vfs_mime_legacy_parse_globs (legacy, directory)) + { + exo_object_unref (legacy); + return NULL; + } + + /* we got it */ + return THUNAR_VFS_MIME_PROVIDER (legacy); }