mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-14 21:36:13 +01:00
b6f7f37427
In order to make xdgmime properly relocatable so that unit tests can use it without it reading and modifying the user’s actual xdgmime files, and without the need to call setenv() (and get tied up with thread safety problems), add a xdg_mime_set_dirs() method to allow the dirs to be overridden. They will still default to the values of $XDG_DATA_HOME and $XDG_DATA_DIRS. Signed-off-by: Philip Withnall <withnall@endlessm.com>
1025 lines
23 KiB
C
1025 lines
23 KiB
C
/* -*- mode: C; c-file-style: "gnu" -*- */
|
||
/* xdgmime.c: XDG Mime Spec mime resolver. Based on version 0.11 of the spec.
|
||
*
|
||
* More info can be found at http://www.freedesktop.org/standards/
|
||
*
|
||
* 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:
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Lesser General Public
|
||
* License as published by the Free Software Foundation; either
|
||
* version 2.1 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Lesser General Public
|
||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include "xdgmime.h"
|
||
#include "xdgmimeint.h"
|
||
#include "xdgmimeglob.h"
|
||
#include "xdgmimemagic.h"
|
||
#include "xdgmimealias.h"
|
||
#include "xdgmimeicon.h"
|
||
#include "xdgmimeparent.h"
|
||
#include "xdgmimecache.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 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;
|
||
static XdgIconList *icon_list = NULL;
|
||
static XdgIconList *generic_icon_list = NULL;
|
||
|
||
static char **xdg_dirs = NULL; /* NULL terminated */
|
||
|
||
XdgMimeCache **_caches = NULL;
|
||
static int n_caches = 0;
|
||
|
||
const char xdg_mime_type_unknown[] = "application/octet-stream";
|
||
const char xdg_mime_type_empty[] = "application/x-zerosize";
|
||
const char xdg_mime_type_textplain[] = "text/plain";
|
||
|
||
|
||
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 void
|
||
xdg_dir_time_list_add (char *file_name,
|
||
time_t mtime)
|
||
{
|
||
XdgDirTimeList *list;
|
||
|
||
for (list = dir_time_list; list; list = list->next)
|
||
{
|
||
if (strcmp (list->directory_name, file_name) == 0)
|
||
{
|
||
free (file_name);
|
||
return;
|
||
}
|
||
}
|
||
|
||
list = calloc (1, sizeof (XdgDirTimeList));
|
||
list->checked = XDG_CHECKED_UNCHECKED;
|
||
list->directory_name = file_name;
|
||
list->mtime = mtime;
|
||
list->next = dir_time_list;
|
||
dir_time_list = list;
|
||
}
|
||
|
||
static void
|
||
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;
|
||
|
||
assert (directory != NULL);
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/mime.cache") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/mime.cache");
|
||
if (stat (file_name, &st) == 0)
|
||
{
|
||
XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name);
|
||
|
||
if (cache != NULL)
|
||
{
|
||
xdg_dir_time_list_add (file_name, st.st_mtime);
|
||
|
||
_caches = realloc (_caches, sizeof (XdgMimeCache *) * (n_caches + 2));
|
||
_caches[n_caches] = cache;
|
||
_caches[n_caches + 1] = NULL;
|
||
n_caches++;
|
||
|
||
return FALSE;
|
||
}
|
||
}
|
||
free (file_name);
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/globs2") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/globs2");
|
||
if (stat (file_name, &st) == 0)
|
||
{
|
||
_xdg_mime_glob_read_from_file (global_hash, file_name, TRUE);
|
||
xdg_dir_time_list_add (file_name, st.st_mtime);
|
||
}
|
||
else
|
||
{
|
||
free (file_name);
|
||
file_name = malloc (strlen (directory) + strlen ("/globs") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/globs");
|
||
if (stat (file_name, &st) == 0)
|
||
{
|
||
_xdg_mime_glob_read_from_file (global_hash, file_name, FALSE);
|
||
xdg_dir_time_list_add (file_name, st.st_mtime);
|
||
}
|
||
else
|
||
{
|
||
free (file_name);
|
||
}
|
||
}
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/magic") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/magic");
|
||
if (stat (file_name, &st) == 0)
|
||
{
|
||
_xdg_mime_magic_read_from_file (global_magic, file_name);
|
||
xdg_dir_time_list_add (file_name, st.st_mtime);
|
||
}
|
||
else
|
||
{
|
||
free (file_name);
|
||
}
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/aliases") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/aliases");
|
||
_xdg_mime_alias_read_from_file (alias_list, file_name);
|
||
free (file_name);
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/subclasses") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/subclasses");
|
||
_xdg_mime_parent_read_from_file (parent_list, file_name);
|
||
free (file_name);
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/icons") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/icons");
|
||
_xdg_mime_icon_read_from_file (icon_list, file_name);
|
||
free (file_name);
|
||
|
||
file_name = malloc (strlen (directory) + strlen ("/generic-icons") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/generic-icons");
|
||
_xdg_mime_icon_read_from_file (generic_icon_list, file_name);
|
||
free (file_name);
|
||
|
||
return FALSE; /* Keep processing */
|
||
}
|
||
|
||
/* Set @xdg_dirs from the environment. It must not have been set already. */
|
||
static void
|
||
xdg_init_dirs (void)
|
||
{
|
||
const char *xdg_data_home, *home, *xdg_data_dirs;
|
||
const char *ptr;
|
||
size_t n_dirs = 0;
|
||
size_t i, current_dir;
|
||
|
||
assert (xdg_dirs == NULL);
|
||
|
||
xdg_data_home = getenv ("XDG_DATA_HOME");
|
||
home = getenv ("HOME");
|
||
xdg_data_dirs = getenv ("XDG_DATA_DIRS");
|
||
|
||
if (xdg_data_dirs == NULL)
|
||
xdg_data_dirs = "/usr/local/share/:/usr/share/";
|
||
|
||
/* Work out how many dirs we’re dealing with. */
|
||
if (xdg_data_home != NULL || home != NULL)
|
||
n_dirs++;
|
||
n_dirs++; /* initial entry in @xdg_data_dirs */
|
||
for (i = 0; xdg_data_dirs[i] != '\0'; i++)
|
||
if (xdg_data_dirs[i] == ':')
|
||
n_dirs++;
|
||
|
||
xdg_dirs = calloc (n_dirs + 1 /* NULL terminator */, sizeof (char *));
|
||
current_dir = 0;
|
||
|
||
/* $XDG_DATA_HOME */
|
||
if (xdg_data_home != NULL)
|
||
{
|
||
char *mime_subdir;
|
||
|
||
mime_subdir = malloc (strlen (xdg_data_home) + strlen ("/mime/") + 1);
|
||
strcpy (mime_subdir, xdg_data_home);
|
||
strcat (mime_subdir, "/mime/");
|
||
|
||
xdg_dirs[current_dir++] = mime_subdir;
|
||
}
|
||
else if (home != NULL)
|
||
{
|
||
char *guessed_xdg_home;
|
||
|
||
guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/mime/") + 1);
|
||
strcpy (guessed_xdg_home, home);
|
||
strcat (guessed_xdg_home, "/.local/share/mime/");
|
||
|
||
xdg_dirs[current_dir++] = guessed_xdg_home;
|
||
}
|
||
|
||
/* $XDG_DATA_DIRS */
|
||
ptr = xdg_data_dirs;
|
||
|
||
while (*ptr != '\000')
|
||
{
|
||
const char *end_ptr;
|
||
char *dir;
|
||
int len;
|
||
|
||
end_ptr = ptr;
|
||
while (*end_ptr != ':' && *end_ptr != '\000')
|
||
end_ptr ++;
|
||
|
||
if (end_ptr == ptr)
|
||
{
|
||
ptr++;
|
||
continue;
|
||
}
|
||
|
||
if (*end_ptr == ':')
|
||
len = end_ptr - ptr;
|
||
else
|
||
len = end_ptr - ptr + 1;
|
||
dir = malloc (len + strlen ("/mime/") + 1);
|
||
strncpy (dir, ptr, len);
|
||
dir[len] = '\0';
|
||
strcat (dir, "/mime/");
|
||
|
||
xdg_dirs[current_dir++] = dir;
|
||
|
||
ptr = end_ptr;
|
||
}
|
||
|
||
/* NULL terminator */
|
||
xdg_dirs[current_dir] = NULL;
|
||
|
||
need_reread = TRUE;
|
||
}
|
||
|
||
/* Runs a command on all the directories in the search path (@xdg_dirs). */
|
||
static void
|
||
xdg_run_command_on_dirs (XdgDirectoryFunc func,
|
||
void *user_data)
|
||
{
|
||
size_t i;
|
||
|
||
if (xdg_dirs == NULL)
|
||
xdg_init_dirs ();
|
||
|
||
for (i = 0; xdg_dirs[i] != NULL; i++)
|
||
{
|
||
if ((func) (xdg_dirs[i], user_data))
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Allows the calling code to override the directories used by xdgmime, without
|
||
* having to change environment variables in a running process (which is not
|
||
* thread safe). This is intended to be used by tests. The changes will be
|
||
* picked up by xdg_mime_init() next time public API is called.
|
||
*
|
||
* This will set @xdg_dirs. Directories in @dirs must be complete, including
|
||
* the conventional `/mime` subdirectory. This is to allow tests to override
|
||
* them without the need to create a subdirectory. */
|
||
void
|
||
xdg_mime_set_dirs (const char * const *dirs)
|
||
{
|
||
size_t i;
|
||
|
||
for (i = 0; xdg_dirs != NULL && xdg_dirs[i] != NULL; i++)
|
||
free (xdg_dirs[i]);
|
||
if (xdg_dirs != NULL)
|
||
free (xdg_dirs[i]);
|
||
xdg_dirs = NULL;
|
||
|
||
if (dirs != NULL)
|
||
{
|
||
for (i = 0; dirs[i] != NULL; i++);
|
||
xdg_dirs = calloc (i + 1 /* NULL terminator */, sizeof (char*));
|
||
for (i = 0; dirs[i] != NULL; i++)
|
||
xdg_dirs[i] = strdup (dirs[i]);
|
||
xdg_dirs[i] = NULL;
|
||
}
|
||
|
||
need_reread = TRUE;
|
||
}
|
||
|
||
/* 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,
|
||
int *exists)
|
||
{
|
||
struct stat st;
|
||
|
||
/* If the file exists */
|
||
if (stat (file_path, &st) == 0)
|
||
{
|
||
XdgDirTimeList *list;
|
||
|
||
if (exists)
|
||
*exists = TRUE;
|
||
|
||
for (list = dir_time_list; list; list = list->next)
|
||
{
|
||
if (! strcmp (list->directory_name, file_path))
|
||
{
|
||
if (st.st_mtime == list->mtime)
|
||
list->checked = XDG_CHECKED_VALID;
|
||
else
|
||
list->checked = XDG_CHECKED_INVALID;
|
||
|
||
return (list->checked != XDG_CHECKED_VALID);
|
||
}
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
if (exists)
|
||
*exists = FALSE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static int
|
||
xdg_check_dir (const char *directory,
|
||
int *invalid_dir_list)
|
||
{
|
||
int invalid, exists;
|
||
char *file_name;
|
||
|
||
assert (directory != NULL);
|
||
|
||
/* Check the mime.cache file */
|
||
file_name = malloc (strlen (directory) + strlen ("/mime.cache") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/mime.cache");
|
||
invalid = xdg_check_file (file_name, &exists);
|
||
free (file_name);
|
||
if (invalid)
|
||
{
|
||
*invalid_dir_list = TRUE;
|
||
return TRUE;
|
||
}
|
||
else if (exists)
|
||
{
|
||
return FALSE;
|
||
}
|
||
|
||
/* Check the globs file */
|
||
file_name = malloc (strlen (directory) + strlen ("/globs") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/globs");
|
||
invalid = xdg_check_file (file_name, NULL);
|
||
free (file_name);
|
||
if (invalid)
|
||
{
|
||
*invalid_dir_list = TRUE;
|
||
return TRUE;
|
||
}
|
||
|
||
/* Check the magic file */
|
||
file_name = malloc (strlen (directory) + strlen ("/magic") + 1);
|
||
strcpy (file_name, directory); strcat (file_name, "/magic");
|
||
invalid = xdg_check_file (file_name, NULL);
|
||
free (file_name);
|
||
if (invalid)
|
||
{
|
||
*invalid_dir_list = TRUE;
|
||
return TRUE;
|
||
}
|
||
|
||
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 ();
|
||
icon_list = _xdg_mime_icon_list_new ();
|
||
generic_icon_list = _xdg_mime_icon_list_new ();
|
||
|
||
xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory,
|
||
NULL);
|
||
|
||
need_reread = FALSE;
|
||
}
|
||
}
|
||
|
||
const char *
|
||
xdg_mime_get_mime_type_for_data (const void *data,
|
||
size_t len,
|
||
int *result_prio)
|
||
{
|
||
const char *mime_type;
|
||
|
||
if (len == 0)
|
||
{
|
||
*result_prio = 100;
|
||
return XDG_MIME_TYPE_EMPTY;
|
||
}
|
||
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
mime_type = _xdg_mime_cache_get_mime_type_for_data (data, len, result_prio);
|
||
else
|
||
mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, result_prio, NULL, 0);
|
||
|
||
if (mime_type)
|
||
return mime_type;
|
||
|
||
return _xdg_binary_or_text_fallback(data, len);
|
||
}
|
||
|
||
#ifdef NOT_USED_IN_GIO
|
||
|
||
const char *
|
||
xdg_mime_get_mime_type_for_file (const char *file_name,
|
||
struct stat *statbuf)
|
||
{
|
||
const char *mime_type;
|
||
/* currently, only a few globs occur twice, and none
|
||
* more often, so 5 seems plenty.
|
||
*/
|
||
const char *mime_types[5];
|
||
FILE *file;
|
||
unsigned char *data;
|
||
int max_extent;
|
||
int bytes_read;
|
||
struct stat buf;
|
||
const char *base_name;
|
||
int n;
|
||
|
||
if (file_name == NULL)
|
||
return NULL;
|
||
if (! _xdg_utf8_validate (file_name))
|
||
return NULL;
|
||
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf);
|
||
|
||
base_name = _xdg_get_base_name (file_name);
|
||
n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 5);
|
||
|
||
if (n == 1)
|
||
return mime_types[0];
|
||
|
||
if (!statbuf)
|
||
{
|
||
if (stat (file_name, &buf) != 0)
|
||
return XDG_MIME_TYPE_UNKNOWN;
|
||
|
||
statbuf = &buf;
|
||
}
|
||
|
||
if (!S_ISREG (statbuf->st_mode))
|
||
return XDG_MIME_TYPE_UNKNOWN;
|
||
|
||
/* FIXME: Need to make sure that max_extent isn't totally broken. This could
|
||
* be large and need getting from a stream instead of just reading it all
|
||
* in. */
|
||
max_extent = _xdg_mime_magic_get_buffer_extents (global_magic);
|
||
data = malloc (max_extent);
|
||
if (data == NULL)
|
||
return XDG_MIME_TYPE_UNKNOWN;
|
||
|
||
file = fopen (file_name, "r");
|
||
if (file == NULL)
|
||
{
|
||
free (data);
|
||
return XDG_MIME_TYPE_UNKNOWN;
|
||
}
|
||
|
||
bytes_read = fread (data, 1, max_extent, file);
|
||
if (ferror (file))
|
||
{
|
||
free (data);
|
||
fclose (file);
|
||
return XDG_MIME_TYPE_UNKNOWN;
|
||
}
|
||
|
||
mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL,
|
||
mime_types, n);
|
||
|
||
free (data);
|
||
fclose (file);
|
||
|
||
if (mime_type)
|
||
return mime_type;
|
||
|
||
return _xdg_binary_or_text_fallback(data, bytes_read);
|
||
}
|
||
|
||
const char *
|
||
xdg_mime_get_mime_type_from_file_name (const char *file_name)
|
||
{
|
||
const char *mime_type;
|
||
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_get_mime_type_from_file_name (file_name);
|
||
|
||
if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, &mime_type, 1))
|
||
return mime_type;
|
||
else
|
||
return XDG_MIME_TYPE_UNKNOWN;
|
||
}
|
||
|
||
#endif
|
||
|
||
int
|
||
xdg_mime_get_mime_types_from_file_name (const char *file_name,
|
||
const char *mime_types[],
|
||
int n_mime_types)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_get_mime_types_from_file_name (file_name, mime_types, n_mime_types);
|
||
|
||
return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types);
|
||
}
|
||
|
||
#ifdef NOT_USED_IN_GIO
|
||
|
||
int
|
||
xdg_mime_is_valid_mime_type (const char *mime_type)
|
||
{
|
||
/* FIXME: We should make this a better test
|
||
*/
|
||
return _xdg_utf8_validate (mime_type);
|
||
}
|
||
|
||
#endif
|
||
|
||
void
|
||
xdg_mime_shutdown (void)
|
||
{
|
||
XdgCallbackList *list;
|
||
|
||
/* FIXME: Need to make this (and the whole library) thread safe */
|
||
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;
|
||
}
|
||
|
||
if (alias_list)
|
||
{
|
||
_xdg_mime_alias_list_free (alias_list);
|
||
alias_list = NULL;
|
||
}
|
||
|
||
if (parent_list)
|
||
{
|
||
_xdg_mime_parent_list_free (parent_list);
|
||
parent_list = NULL;
|
||
}
|
||
|
||
if (icon_list)
|
||
{
|
||
_xdg_mime_icon_list_free (icon_list);
|
||
icon_list = NULL;
|
||
}
|
||
|
||
if (generic_icon_list)
|
||
{
|
||
_xdg_mime_icon_list_free (generic_icon_list);
|
||
generic_icon_list = NULL;
|
||
}
|
||
|
||
if (_caches)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < n_caches; i++)
|
||
_xdg_mime_cache_unref (_caches[i]);
|
||
free (_caches);
|
||
_caches = NULL;
|
||
n_caches = 0;
|
||
}
|
||
|
||
for (list = callback_list; list; list = list->next)
|
||
(list->callback) (list->data);
|
||
|
||
need_reread = TRUE;
|
||
}
|
||
|
||
int
|
||
xdg_mime_get_max_buffer_extents (void)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_get_max_buffer_extents ();
|
||
|
||
return _xdg_mime_magic_get_buffer_extents (global_magic);
|
||
}
|
||
|
||
const char *
|
||
_xdg_mime_unalias_mime_type (const char *mime_type)
|
||
{
|
||
const char *lookup;
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_unalias_mime_type (mime_type);
|
||
|
||
if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL)
|
||
return lookup;
|
||
|
||
return mime_type;
|
||
}
|
||
|
||
const char *
|
||
xdg_mime_unalias_mime_type (const char *mime_type)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
return _xdg_mime_unalias_mime_type (mime_type);
|
||
}
|
||
|
||
int
|
||
_xdg_mime_mime_type_equal (const char *mime_a,
|
||
const char *mime_b)
|
||
{
|
||
const char *unalias_a, *unalias_b;
|
||
|
||
unalias_a = _xdg_mime_unalias_mime_type (mime_a);
|
||
unalias_b = _xdg_mime_unalias_mime_type (mime_b);
|
||
|
||
if (strcmp (unalias_a, unalias_b) == 0)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
int
|
||
xdg_mime_mime_type_equal (const char *mime_a,
|
||
const char *mime_b)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
return _xdg_mime_mime_type_equal (mime_a, mime_b);
|
||
}
|
||
|
||
int
|
||
xdg_mime_media_type_equal (const char *mime_a,
|
||
const char *mime_b)
|
||
{
|
||
char *sep;
|
||
|
||
sep = strchr (mime_a, '/');
|
||
|
||
if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if 1
|
||
static int
|
||
ends_with (const char *str,
|
||
const char *suffix)
|
||
{
|
||
int length;
|
||
int suffix_length;
|
||
|
||
length = strlen (str);
|
||
suffix_length = strlen (suffix);
|
||
if (length < suffix_length)
|
||
return 0;
|
||
|
||
if (strcmp (str + length - suffix_length, suffix) == 0)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
xdg_mime_is_super_type (const char *mime)
|
||
{
|
||
return ends_with (mime, "/*");
|
||
}
|
||
#endif
|
||
|
||
int
|
||
_xdg_mime_mime_type_subclass (const char *mime,
|
||
const char *base)
|
||
{
|
||
const char *umime, *ubase;
|
||
const char **parents;
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_mime_type_subclass (mime, base);
|
||
|
||
umime = _xdg_mime_unalias_mime_type (mime);
|
||
ubase = _xdg_mime_unalias_mime_type (base);
|
||
|
||
if (strcmp (umime, ubase) == 0)
|
||
return 1;
|
||
|
||
#if 1
|
||
/* 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 &&
|
||
strncmp (umime, "inode/", 6) != 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;
|
||
}
|
||
|
||
int
|
||
xdg_mime_mime_type_subclass (const char *mime,
|
||
const char *base)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
return _xdg_mime_mime_type_subclass (mime, base);
|
||
}
|
||
|
||
char **
|
||
xdg_mime_list_mime_parents (const char *mime)
|
||
{
|
||
const char *umime;
|
||
const char **parents;
|
||
char **result;
|
||
int i, n;
|
||
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_list_mime_parents (mime);
|
||
|
||
umime = _xdg_mime_unalias_mime_type (mime);
|
||
|
||
parents = _xdg_mime_parent_list_lookup (parent_list, umime);
|
||
|
||
if (!parents)
|
||
return NULL;
|
||
|
||
for (i = 0; parents[i]; i++) ;
|
||
|
||
n = (i + 1) * sizeof (char *);
|
||
result = (char **) malloc (n);
|
||
memcpy (result, parents, n);
|
||
|
||
return result;
|
||
}
|
||
|
||
#ifdef NOT_USED_IN_GIO
|
||
|
||
const char **
|
||
xdg_mime_get_mime_parents (const char *mime)
|
||
{
|
||
const char *umime;
|
||
|
||
xdg_mime_init ();
|
||
|
||
umime = _xdg_mime_unalias_mime_type (mime);
|
||
|
||
return _xdg_mime_parent_list_lookup (parent_list, umime);
|
||
}
|
||
|
||
void
|
||
xdg_mime_dump (void)
|
||
{
|
||
xdg_mime_init();
|
||
|
||
printf ("*** ALIASES ***\n\n");
|
||
_xdg_mime_alias_list_dump (alias_list);
|
||
printf ("\n*** PARENTS ***\n\n");
|
||
_xdg_mime_parent_list_dump (parent_list);
|
||
printf ("\n*** CACHE ***\n\n");
|
||
_xdg_glob_hash_dump (global_hash);
|
||
printf ("\n*** GLOBS ***\n\n");
|
||
_xdg_glob_hash_dump (global_hash);
|
||
printf ("\n*** GLOBS REVERSE TREE ***\n\n");
|
||
_xdg_mime_cache_glob_dump ();
|
||
}
|
||
|
||
#endif
|
||
|
||
/* 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;
|
||
}
|
||
|
||
#ifdef NOT_USED_IN_GIO
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
const char *
|
||
xdg_mime_get_icon (const char *mime)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_get_icon (mime);
|
||
|
||
return _xdg_mime_icon_list_lookup (icon_list, mime);
|
||
}
|
||
|
||
const char *
|
||
xdg_mime_get_generic_icon (const char *mime)
|
||
{
|
||
xdg_mime_init ();
|
||
|
||
if (_caches)
|
||
return _xdg_mime_cache_get_generic_icon (mime);
|
||
|
||
return _xdg_mime_icon_list_lookup (generic_icon_list, mime);
|
||
}
|