diff options
Diffstat (limited to 'gtk/xdgmime/xdgmime.c')
-rw-r--r-- | gtk/xdgmime/xdgmime.c | 565 |
1 files changed, 498 insertions, 67 deletions
diff --git a/gtk/xdgmime/xdgmime.c b/gtk/xdgmime/xdgmime.c index 2101288277..df618b2798 100644 --- a/gtk/xdgmime/xdgmime.c +++ b/gtk/xdgmime/xdgmime.c @@ -3,8 +3,8 @@ * * More info can be found at http://www.freedesktop.org/standards/ * - * Copyright (C) 2003 Red Hat, Inc. - * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> + * Copyright (C) 2003,2004 Red Hat, Inc. + * Copyright (C) 2003,2004 Jonathan Blandford <jrb@alum.mit.edu> * * Licensed under the Academic Free License version 2.0 * Or under the following terms: @@ -30,107 +30,357 @@ #include "xdgmimeint.h" #include "xdgmimeglob.h" #include "xdgmimemagic.h" +#include "xdgmimealias.h" +#include "xdgmimeparent.h" #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> +#include <sys/time.h> #include <unistd.h> +#include <assert.h> + +typedef struct XdgDirTimeList XdgDirTimeList; +typedef struct XdgCallbackList XdgCallbackList; + +static int need_reread = TRUE; +static time_t last_stat_time = 0; -static int initted = 0; static XdgGlobHash *global_hash = NULL; static XdgMimeMagic *global_magic = NULL; +static XdgAliasList *alias_list = NULL; +static XdgParentList *parent_list = NULL; +static XdgDirTimeList *dir_time_list = NULL; +static XdgCallbackList *callback_list = NULL; const char *xdg_mime_type_unknown = "application/octet-stream"; + +enum +{ + XDG_CHECKED_UNCHECKED, + XDG_CHECKED_VALID, + XDG_CHECKED_INVALID +}; + +struct XdgDirTimeList +{ + time_t mtime; + char *directory_name; + int checked; + XdgDirTimeList *next; +}; + +struct XdgCallbackList +{ + XdgCallbackList *next; + XdgCallbackList *prev; + int callback_id; + XdgMimeCallback callback; + void *data; + XdgMimeDestroy destroy; +}; + +/* Function called by xdg_run_command_on_dirs. If it returns TRUE, further + * directories aren't looked at */ +typedef int (*XdgDirectoryFunc) (const char *directory, + void *user_data); + +static XdgDirTimeList * +xdg_dir_time_list_new (void) +{ + XdgDirTimeList *retval; + + retval = calloc (1, sizeof (XdgDirTimeList)); + retval->checked = XDG_CHECKED_UNCHECKED; + + return retval; +} + static void -_xdg_mime_init_from_directory (const char *directory) +xdg_dir_time_list_free (XdgDirTimeList *list) +{ + XdgDirTimeList *next; + + while (list) + { + next = list->next; + free (list->directory_name); + free (list); + list = next; + } +} + +static int +xdg_mime_init_from_directory (const char *directory) { char *file_name; + struct stat st; + XdgDirTimeList *list; + + assert (directory != NULL); file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1); - strcpy (file_name, directory); - strcat (file_name, "/mime/globs"); - _xdg_mime_glob_read_from_file (global_hash, file_name); - free (file_name); + strcpy (file_name, directory); strcat (file_name, "/mime/globs"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_glob_read_from_file (global_hash, file_name); + + list = xdg_dir_time_list_new (); + list->directory_name = file_name; + list->mtime = st.st_mtime; + list->next = dir_time_list; + dir_time_list = list; + } + else + { + free (file_name); + } file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1); - strcpy (file_name, directory); - strcat (file_name, "/mime/magic"); - _xdg_mime_magic_read_from_file (global_magic, file_name); + strcpy (file_name, directory); strcat (file_name, "/mime/magic"); + if (stat (file_name, &st) == 0) + { + _xdg_mime_magic_read_from_file (global_magic, file_name); + + list = xdg_dir_time_list_new (); + list->directory_name = file_name; + list->mtime = st.st_mtime; + list->next = dir_time_list; + dir_time_list = list; + } + else + { + free (file_name); + } + + file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/aliases"); + _xdg_mime_alias_read_from_file (alias_list, file_name); + free (file_name); + + file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/subclasses"); + _xdg_mime_parent_read_from_file (parent_list, file_name); free (file_name); + + return FALSE; /* Keep processing */ } +/* Runs a command on all the directories in the search path */ static void -xdg_mime_init (void) +xdg_run_command_on_dirs (XdgDirectoryFunc func, + void *user_data) { - if (initted == 0) + const char *xdg_data_home; + const char *xdg_data_dirs; + const char *ptr; + + xdg_data_home = getenv ("XDG_DATA_HOME"); + if (xdg_data_home) + { + if ((func) (xdg_data_home, user_data)) + return; + } + else { - const char *xdg_data_home; - const char *xdg_data_dirs; - const char *ptr; + const char *home; - global_hash = _xdg_glob_hash_new (); - global_magic = _xdg_mime_magic_new (); + home = getenv ("HOME"); + if (home != NULL) + { + char *guessed_xdg_home; + int stop_processing; + + guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1); + strcpy (guessed_xdg_home, home); + strcat (guessed_xdg_home, "/.local/share/"); + stop_processing = (func) (guessed_xdg_home, user_data); + free (guessed_xdg_home); + + if (stop_processing) + return; + } + } + + xdg_data_dirs = getenv ("XDG_DATA_DIRS"); + if (xdg_data_dirs == NULL) + xdg_data_dirs = "/usr/local/share/:/usr/share/"; + + ptr = xdg_data_dirs; + + while (*ptr != '\000') + { + const char *end_ptr; + char *dir; + int len; + int stop_processing; + + end_ptr = ptr; + while (*end_ptr != ':' && *end_ptr != '\000') + end_ptr ++; - /* We look for globs and magic files based upon the XDG Base Directory - * Specification - */ - xdg_data_home = getenv ("XDG_DATA_HOME"); - if (xdg_data_home) + if (end_ptr == ptr) { - _xdg_mime_init_from_directory (xdg_data_home); + ptr++; + continue; } + + if (*end_ptr == ':') + len = end_ptr - ptr; else - { - const char *home; + len = end_ptr - ptr + 1; + dir = malloc (len + 1); + strncpy (dir, ptr, len); + dir[len] = '\0'; + stop_processing = (func) (dir, user_data); + free (dir); + + if (stop_processing) + return; + + ptr = end_ptr; + } +} - home = getenv ("HOME"); - if (home != NULL) +/* Checks file_path to make sure it has the same mtime as last time it was + * checked. If it has a different mtime, or if the file doesn't exist, it + * returns FALSE. + * + * FIXME: This doesn't protect against permission changes. + */ +static int +xdg_check_file (const char *file_path) +{ + struct stat st; + + /* If the file exists */ + if (stat (file_path, &st) == 0) + { + XdgDirTimeList *list; + + for (list = dir_time_list; list; list = list->next) + { + if (! strcmp (list->directory_name, file_path) && + st.st_mtime == list->mtime) { - char *guessed_xdg_home; + if (list->checked == XDG_CHECKED_UNCHECKED) + list->checked = XDG_CHECKED_VALID; + else if (list->checked == XDG_CHECKED_VALID) + list->checked = XDG_CHECKED_INVALID; - guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1); - strcpy (guessed_xdg_home, home); - strcat (guessed_xdg_home, "/.local/share/"); - _xdg_mime_init_from_directory (guessed_xdg_home); - free (guessed_xdg_home); + return (list->checked != XDG_CHECKED_VALID); } } + return TRUE; + } - xdg_data_dirs = getenv ("XDG_DATA_DIRS"); - if (xdg_data_dirs == NULL) - xdg_data_dirs = "/usr/local/share/:/usr/share/"; + return FALSE; +} - ptr = xdg_data_dirs; +static int +xdg_check_dir (const char *directory, + int *invalid_dir_list) +{ + int invalid; + char *file_name; - while (*ptr != '\000') - { - const char *end_ptr; - char *dir; - int len; + assert (directory != NULL); - end_ptr = ptr; - while (*end_ptr != ':' && *end_ptr != '\000') - end_ptr ++; + /* Check the globs file */ + file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/globs"); + invalid = xdg_check_file (file_name); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } - if (end_ptr == ptr) - { - ptr++; - continue; - } + /* Check the magic file */ + file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1); + strcpy (file_name, directory); strcat (file_name, "/mime/magic"); + invalid = xdg_check_file (file_name); + free (file_name); + if (invalid) + { + *invalid_dir_list = TRUE; + return TRUE; + } - if (*end_ptr == ':') - len = end_ptr - ptr; - else - len = end_ptr - ptr + 1; - dir = malloc (len + 1); - strncpy (dir, ptr, len); - dir[len] = '\0'; - _xdg_mime_init_from_directory (dir); - free (dir); - - ptr = end_ptr; - } - initted = 1; + return FALSE; /* Keep processing */ +} + +/* Walks through all the mime files stat()ing them to see if they've changed. + * Returns TRUE if they have. */ +static int +xdg_check_dirs (void) +{ + XdgDirTimeList *list; + int invalid_dir_list = FALSE; + + for (list = dir_time_list; list; list = list->next) + list->checked = XDG_CHECKED_UNCHECKED; + + xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir, + &invalid_dir_list); + + if (invalid_dir_list) + return TRUE; + + for (list = dir_time_list; list; list = list->next) + { + if (list->checked != XDG_CHECKED_VALID) + return TRUE; + } + + return FALSE; +} + +/* We want to avoid stat()ing on every single mime call, so we only look for + * newer files every 5 seconds. This will return TRUE if we need to reread the + * mime data from disk. + */ +static int +xdg_check_time_and_dirs (void) +{ + struct timeval tv; + time_t current_time; + int retval = FALSE; + + gettimeofday (&tv, NULL); + current_time = tv.tv_sec; + + if (current_time >= last_stat_time + 5) + { + retval = xdg_check_dirs (); + last_stat_time = current_time; + } + + return retval; +} + +/* Called in every public function. It reloads the hash function if need be. + */ +static void +xdg_mime_init (void) +{ + if (xdg_check_time_and_dirs ()) + { + xdg_mime_shutdown (); + } + + if (need_reread) + { + global_hash = _xdg_glob_hash_new (); + global_magic = _xdg_mime_magic_new (); + alias_list = _xdg_mime_alias_list_new (); + parent_list = _xdg_mime_parent_list_new (); + + xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory, + NULL); + + need_reread = FALSE; } } @@ -239,17 +489,36 @@ xdg_mime_is_valid_mime_type (const char *mime_type) void xdg_mime_shutdown (void) { + XdgCallbackList *list; + /* FIXME: Need to make this (and the whole library) thread safe */ - if (initted) + if (dir_time_list) + { + xdg_dir_time_list_free (dir_time_list); + dir_time_list = NULL; + } + + if (global_hash) { _xdg_glob_hash_free (global_hash); global_hash = NULL; - + } + if (global_magic) + { _xdg_mime_magic_free (global_magic); global_magic = NULL; + } - initted = 0; + if (alias_list) + { + _xdg_mime_alias_list_free (alias_list); + alias_list = NULL; } + + for (list = callback_list; list; list = list->next) + (list->callback) (list->data); + + need_reread = TRUE; } int @@ -259,3 +528,165 @@ xdg_mime_get_max_buffer_extents (void) return _xdg_mime_magic_get_buffer_extents (global_magic); } + +static const char * +unalias_mime_type (const char *mime_type) +{ + const char *lookup; + + if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL) + return lookup; + + return mime_type; +} + +int +xdg_mime_mime_type_equal (const char *mime_a, + const char *mime_b) +{ + const char *unalias_a, *unalias_b; + + xdg_mime_init (); + + unalias_a = unalias_mime_type (mime_a); + unalias_b = unalias_mime_type (mime_b); + + if (strcmp (unalias_a, unalias_b) == 0) + return 1; + + return 0; +} + +int +xdg_mime_media_type_equal (const char *mime_a, + const char *mime_b) +{ + char *sep; + + xdg_mime_init (); + + sep = strchr (mime_a, '/'); + + if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0) + return 1; + + return 0; +} + +#if 0 +static int +xdg_mime_is_super_type (const char *mime) +{ + int length; + const char *type; + + length = strlen (mime); + type = &(mime[length - 2]); + + if (strcmp (type, "/*") == 0) + return 1; + + return 0; +} +#endif + +int +xdg_mime_mime_type_subclass (const char *mime, + const char *base) +{ + const char *umime, *ubase; + const char **parents; + + xdg_mime_init (); + + umime = unalias_mime_type (mime); + ubase = unalias_mime_type (base); + + if (strcmp (umime, ubase) == 0) + return 1; + +#if 0 + /* Handle supertypes */ + if (xdg_mime_is_super_type (ubase) && + xdg_mime_media_type_equal (umime, ubase)) + return 1; +#endif + + /* Handle special cases text/plain and application/octet-stream */ + if (strcmp (ubase, "text/plain") == 0 && + strncmp (umime, "text/", 5) == 0) + return 1; + + if (strcmp (ubase, "application/octet-stream") == 0) + return 1; + + parents = _xdg_mime_parent_list_lookup (parent_list, umime); + for (; parents && *parents; parents++) + { + if (xdg_mime_mime_type_subclass (*parents, ubase)) + return 1; + } + + return 0; +} + +void +xdg_mime_dump (void) +{ + printf ("*** ALIASES ***\n\n"); + _xdg_mime_alias_list_dump (alias_list); + printf ("\n*** PARENTS ***\n\n"); + _xdg_mime_parent_list_dump (parent_list); +} + + +/* Registers a function to be called every time the mime database reloads its files + */ +int +xdg_mime_register_reload_callback (XdgMimeCallback callback, + void *data, + XdgMimeDestroy destroy) +{ + XdgCallbackList *list_el; + static int callback_id = 1; + + /* Make a new list element */ + list_el = calloc (1, sizeof (XdgCallbackList)); + list_el->callback_id = callback_id; + list_el->callback = callback; + list_el->data = data; + list_el->destroy = destroy; + list_el->next = callback_list; + if (list_el->next) + list_el->next->prev = list_el; + + callback_list = list_el; + callback_id ++; + + return callback_id - 1; +} + +void +xdg_mime_remove_callback (int callback_id) +{ + XdgCallbackList *list; + + for (list = callback_list; list; list = list->next) + { + if (list->callback_id == callback_id) + { + if (list->next) + list->next = list->prev; + + if (list->prev) + list->prev->next = list->next; + else + callback_list = list->next; + + /* invoke the destroy handler */ + (list->destroy) (list->data); + free (list); + return; + } + } +} |