[Xfce4-commits] r26879 - in xarchiver/trunk/src: . mime-type
Jen Yee Hong
pcman at xfce.org
Mon Apr 21 21:35:06 CEST 2008
Author: pcman
Date: 2008-04-21 19:35:06 +0000 (Mon, 21 Apr 2008)
New Revision: 26879
Added:
xarchiver/trunk/src/glib-utils.c
xarchiver/trunk/src/glib-utils.h
xarchiver/trunk/src/mime-type/
xarchiver/trunk/src/mime-type/Makefile.am
xarchiver/trunk/src/mime-type/mime-action.c
xarchiver/trunk/src/mime-type/mime-action.h
xarchiver/trunk/src/mime-type/mime-cache.c
xarchiver/trunk/src/mime-type/mime-cache.h
xarchiver/trunk/src/mime-type/mime-type.c
xarchiver/trunk/src/mime-type/mime-type.h
Modified:
xarchiver/trunk/src/Makefile.am
Log:
Add missing files.
Modified: xarchiver/trunk/src/Makefile.am
===================================================================
--- xarchiver/trunk/src/Makefile.am 2008-04-21 17:19:51 UTC (rev 26878)
+++ xarchiver/trunk/src/Makefile.am 2008-04-21 19:35:06 UTC (rev 26879)
@@ -33,7 +33,9 @@
add_dialog.c add_dialog.h \
new_dialog.c new_dialog.h \
pref_dialog.c pref_dialog.h \
- glib-mem.h
+ glib-mem.h \
+ glib-utils.h \
+ glib-utils.c
xarchiver_CFLAGS = \
@GTK_CFLAGS@ \
Added: xarchiver/trunk/src/glib-utils.c
===================================================================
--- xarchiver/trunk/src/glib-utils.c (rev 0)
+++ xarchiver/trunk/src/glib-utils.c 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,65 @@
+/*
+* C++ Interface: glib-mem
+*
+* Description: Compatibility macros for older versions of glib
+*
+*
+* Author: Hong Jen Yee (PCMan) <pcman.tw (AT) gmail.com>, (C) 2006
+*
+* Copyright: See COPYING file that comes with this distribution
+*
+*/
+
+#include "glib-utils.h"
+
+/* older versions of glib don't provde these API */
+#if ! GLIB_CHECK_VERSION(2, 8, 0)
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+int g_mkdir_with_parents(const gchar *pathname, int mode)
+{
+ struct stat statbuf;
+ char *dir, *sep;
+ dir = g_strdup( pathname );
+ sep = dir[0] == '/' ? dir + 1 : dir;
+ do {
+ sep = strchr( sep, '/' );
+ if( G_LIKELY( sep ) )
+ *sep = '\0';
+
+ if( stat( dir, &statbuf) == 0 )
+ {
+ if( ! S_ISDIR(statbuf.st_mode) ) /* parent not dir */
+ goto err;
+ }
+ else /* stat failed */
+ {
+ if( errno == ENOENT ) /* not exists */
+ {
+ if( mkdir( dir, mode ) == -1 )
+ goto err;
+ }
+ else
+ goto err; /* unknown error */
+ }
+
+ if( G_LIKELY( sep ) )
+ {
+ *sep = '/';
+ ++sep;
+ }
+ else
+ break;
+ }while( sep );
+ g_free( dir );
+ return 0;
+err:
+ g_free( dir );
+ return -1;
+}
+#endif
Added: xarchiver/trunk/src/glib-utils.h
===================================================================
--- xarchiver/trunk/src/glib-utils.h (rev 0)
+++ xarchiver/trunk/src/glib-utils.h 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,24 @@
+/*
+* C++ Interface: glib-mem
+*
+* Description: Compatibility macros for older versions of glib
+*
+*
+* Author: Hong Jen Yee (PCMan) <pcman.tw (AT) gmail.com>, (C) 2006
+*
+* Copyright: See COPYING file that comes with this distribution
+*
+*/
+
+#ifndef _GLIB_UTILS_H_
+#define _GLIB_UTILS_H_
+
+#include <glib.h>
+
+#if ! GLIB_CHECK_VERSION(2, 8, 0)
+/* older versions of glib don't provde these API */
+int g_mkdir_with_parents(const gchar *pathname, int mode);
+#endif
+
+#endif
+
Added: xarchiver/trunk/src/mime-type/Makefile.am
===================================================================
--- xarchiver/trunk/src/mime-type/Makefile.am (rev 0)
+++ xarchiver/trunk/src/mime-type/Makefile.am 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,18 @@
+noinst_LTLIBRARIES = libmimetype.la
+
+libmimetype_la_SOURCES = \
+ mime-action.c \
+ mime-action.h \
+ mime-cache.c \
+ mime-cache.h \
+ mime-type.c \
+ mime-type.h
+
+INCLUDES = \
+ -I${top_srcdir} -I. -I..
+
+libmimetype_la_CFLAGS = \
+ @GLIB_CFLAGS@
+
+libmimetype_la_LIBADD = \
+ @GLIB_LIBS@
Added: xarchiver/trunk/src/mime-type/mime-action.c
===================================================================
--- xarchiver/trunk/src/mime-type/mime-action.c (rev 0)
+++ xarchiver/trunk/src/mime-type/mime-action.c 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,497 @@
+/*
+ * mime-action.c
+ *
+ * Copyright 2007 PCMan <pcman.tw at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mime-action.h"
+#include "glib-utils.h"
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+gboolean save_to_file( const char* path, const char* data, gssize len )
+{
+ int fd = creat( path, 0644 );
+ if( fd == -1 )
+ return FALSE;
+
+ if( write( fd, data, len ) == -1 )
+ {
+ close( fd );
+ return FALSE;
+ }
+ close( fd );
+ return TRUE;
+}
+
+const char group_desktop[] = "Desktop Entry";
+const char key_mime_type[] = "MimeType";
+
+typedef char* (*DataDirFunc) ( const char* dir, const char* mime_type, gpointer user_data );
+
+static char* data_dir_foreach( DataDirFunc func, const char* mime_type, gpointer user_data )
+{
+ char* ret = NULL;
+ const gchar* const * dirs;
+ const char* dir = g_get_user_data_dir();
+
+ if( (ret = func( dir, mime_type, user_data )) )
+ return ret;
+
+ dirs = g_get_system_data_dirs();
+ for( ; *dirs; ++dirs )
+ {
+ if( (ret = func( *dirs, mime_type, user_data )) )
+ return ret;
+ }
+ return NULL;
+}
+
+static void update_desktop_database()
+{
+ char* argv[3];
+ argv[0] = g_find_program_in_path( "update-desktop-database" );
+ if( G_UNLIKELY( ! argv[0] ) )
+ return;
+ argv[1] = g_build_filename( g_get_user_data_dir(), "applications", NULL );
+ argv[2] = NULL;
+ g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+ g_free( argv[0] );
+ g_free( argv[1] );
+}
+
+static int strv_index( char** strv, const char* str )
+{
+ char**p;
+ if( G_LIKELY( strv && str ) )
+ {
+ for( p = strv; *p; ++p )
+ {
+ if( 0 == strcmp( *p, str ) )
+ return (p - strv);
+ }
+ }
+ return -1;
+}
+
+static char* get_actions( const char* dir, const char* type, GArray* actions )
+{
+ GKeyFile* file;
+ gboolean opened;
+ char** apps = NULL;
+ char* path = g_build_filename( dir, "applications/mimeinfo.cache", NULL );
+ file = g_key_file_new();
+ opened = g_key_file_load_from_file( file, path, 0, NULL );
+ g_free( path );
+ if( G_LIKELY( opened ) )
+ {
+ gsize n_apps = 0, i;
+ apps = g_key_file_get_string_list( file, "MIME Cache", type, &n_apps, NULL );
+ for( i = 0; i < n_apps; ++i )
+ {
+ if( -1 == strv_index( (char**)actions->data, apps[i] ) )
+ {
+ /* check for existence */
+ path = mime_type_locate_desktop_file( dir, apps[i] );
+ if( G_LIKELY(path) )
+ {
+ g_array_append_val( actions, apps[i] );
+ g_free( path );
+ }
+ else
+ g_free( apps[i] );
+ apps[i] = NULL; /* steal the string */
+ }
+ else
+ {
+ g_free( apps[i] );
+ apps[i] = NULL;
+ }
+ }
+ g_free( apps ); /* don't call g_strfreev since all strings in the array was stolen. */
+ }
+ g_key_file_free( file );
+ return NULL; /* return NULL so the for_each operation doesn't stop. */
+}
+
+/*
+ * Get a list of applications supporting this mime-type
+ * The returned string array was newly allocated, and should be
+ * freed with g_strfreev() when no longer used.
+ */
+char** mime_type_get_actions( const char* type )
+{
+ GArray* actions = g_array_sized_new( TRUE, FALSE, sizeof(char*), 10 );
+ char* default_app = NULL;
+
+ /* FIXME: actions of parent types should be added, too. */
+
+ /* get all actions for this file type */
+ data_dir_foreach( (DataDirFunc)get_actions, type, actions );
+
+ /* ensure default app is in the list */
+ if( G_LIKELY( ( default_app = mime_type_get_default_action( type ) ) ) )
+ {
+ int i = strv_index( (char**)actions->data, default_app );
+ if( i == -1 ) /* default app is not in the list, add it! */
+ {
+ g_array_prepend_val( actions, default_app );
+ }
+ else /* default app is in the list, move it to the first. */
+ {
+ if( i != 0 )
+ {
+ char** pdata = (char**)actions->data;
+ char* tmp = pdata[i];
+ g_array_remove_index( actions, i );
+ g_array_prepend_val( actions, tmp );
+ }
+ g_free( default_app );
+ }
+ }
+ return (char**)g_array_free( actions, actions->len == 0 );
+}
+
+/*
+ * NOTE:
+ * This API is very time consuming, but unfortunately, due to the damn poor design of
+ * Freedesktop.org spec, all the insane checks here are necessary. Sigh... :-(
+ */
+gboolean mime_type_has_action( const char* type, const char* desktop_id )
+{
+ char** actions, **action;
+ char *cmd = NULL, *name = NULL;
+ gboolean found = FALSE;
+ gboolean is_desktop = g_str_has_suffix( desktop_id, ".desktop" );
+
+ if( is_desktop )
+ {
+ char** types;
+ GKeyFile* kf = g_key_file_new();
+ char* filename = mime_type_locate_desktop_file( NULL, desktop_id );
+ if( filename && g_key_file_load_from_file( kf, filename, 0, NULL ) )
+ {
+ types = g_key_file_get_string_list( kf, group_desktop, key_mime_type, NULL, NULL );
+ if( -1 != strv_index( types, type ) )
+ {
+ /* our mime-type is already found in the desktop file. no further check is needed */
+ found = TRUE;
+ }
+ g_strfreev( types );
+
+ if( ! found ) /* get the content of desktop file for comparison */
+ {
+ cmd = g_key_file_get_string( kf, group_desktop, "Exec", NULL );
+ name = g_key_file_get_string( kf, group_desktop, "Name", NULL );
+ }
+ }
+ g_free( filename );
+ g_key_file_free( kf );
+ }
+ else
+ {
+ cmd = (char*)desktop_id;
+ }
+
+ actions = mime_type_get_actions( type );
+ if( actions )
+ {
+ for( action = actions; ! found && *action; ++action )
+ {
+ /* Try to match directly by desktop_id first */
+ if( is_desktop && 0 == strcmp( *action, desktop_id ) )
+ {
+ found = TRUE;
+ }
+ else /* Then, try to match by "Exec" and "Name" keys */
+ {
+ char *name2 = NULL, *cmd2 = NULL, *filename = NULL;
+ GKeyFile* kf = g_key_file_new();
+ filename = mime_type_locate_desktop_file( NULL, *action );
+ if( filename && g_key_file_load_from_file( kf, filename, 0, NULL ) )
+ {
+ cmd2 = g_key_file_get_string( kf, group_desktop, "Exec", NULL );
+ if( cmd && cmd2 && 0 == strcmp( cmd, cmd2 ) ) /* 2 desktop files have same "Exec" */
+ {
+ if( is_desktop )
+ {
+ name2 = g_key_file_get_string( kf, group_desktop, "Name", NULL );
+ /* Then, check if the "Name" keys of 2 desktop files are the same. */
+ if( name && name2 && 0 == strcmp( name, name2 ) )
+ {
+ /* Both "Exec" and "Name" keys of the 2 desktop files are
+ * totally the same. So, despite having different desktop id
+ * They actually refer to the same application. */
+ found = TRUE;
+ }
+ g_free( name2 );
+ }
+ else
+ found = TRUE;
+ }
+ }
+ g_free( filename );
+ g_free( cmd2 );
+ g_key_file_free( kf );
+ }
+ }
+ g_strfreev( actions );
+ }
+ if( is_desktop )
+ {
+ g_free( cmd );
+ g_free( name );
+ }
+ return found;
+}
+
+#if 0
+static gboolean is_custom_desktop_file( const char* desktop_id )
+{
+ char* path = g_build_filename( g_get_user_data_dir(), "applications", desktop_id, NULL );
+ gboolean ret = g_file_test( path, G_FILE_TEST_EXISTS );
+ g_free( path );
+ return ret;
+}
+#endif
+
+static char* make_custom_desktop_file( const char* desktop_id, const char* mime_type )
+{
+ char *name = NULL, *cust_template = NULL, *cust = NULL, *path, *dir;
+ char* file_content = NULL;
+ gsize len = 0;
+ guint i;
+
+ if( G_LIKELY( g_str_has_suffix(desktop_id, ".desktop") ) )
+ {
+ GKeyFile *kf = g_key_file_new();
+ char* name = mime_type_locate_desktop_file( NULL, desktop_id );
+ if( G_UNLIKELY( ! name || ! g_key_file_load_from_file( kf, name,
+ G_KEY_FILE_KEEP_TRANSLATIONS, NULL ) ) )
+ {
+ g_free( name );
+ return NULL; /* not a valid desktop file */
+ }
+ g_free( name );
+/*
+ FIXME: If the source desktop_id refers to a custom desktop file, and
+ value of the MimeType key equals to our mime-type, there is no
+ need to generate a new desktop file.
+ if( G_UNLIKELY( is_custom_desktop_file( desktop_id ) ) )
+ {
+ }
+*/
+ /* set our mime-type */
+ g_key_file_set_string_list( kf, group_desktop, key_mime_type, &mime_type, 1 );
+ /* store id of original desktop file, for future use. */
+ g_key_file_set_string( kf, group_desktop, "X-MimeType-Derived", desktop_id );
+ g_key_file_set_string( kf, group_desktop, "NoDisplay", "true" );
+
+ name = g_strndup( desktop_id, strlen(desktop_id) - 8 );
+ cust_template = g_strdup_printf( "%s-usercustom-%%d.desktop", name );
+ g_free( name );
+
+ file_content = g_key_file_to_data( kf, &len, NULL );
+ g_key_file_free( kf );
+ }
+ else /* it's not a desktop_id, but a command */
+ {
+ char* p;
+ const char file_templ[] =
+ "[Desktop Entry]\n"
+ "Encoding=UTF-8\n"
+ "Name=%s\n"
+ "Exec=%s\n"
+ "MimeType=%s\n"
+ "Icon=exec\n"
+ "NoDisplay=true\n"; /* FIXME: Terminal? */
+ /* Make a user-created desktop file for the command */
+ name = g_path_get_basename( desktop_id );
+ if( (p = strchr(name, ' ')) ) /* FIXME: skip command line arguments. is this safe? */
+ *p = '\0';
+ file_content = g_strdup_printf( file_templ, name, desktop_id, mime_type );
+ len = strlen( file_content );
+ cust_template = g_strdup_printf( "%s-usercreated-%%d.desktop", name );
+ g_free( name );
+ }
+
+ /* generate unique file name */
+ dir = g_build_filename( g_get_user_data_dir(), "applications", NULL );
+ g_mkdir_with_parents( dir, 0700 );
+ for( i = 0; ; ++i )
+ {
+ /* generate the basename */
+ cust = g_strdup_printf( cust_template, i );
+ path = g_build_filename( dir, cust, NULL ); /* test if the filename already exists */
+ if( g_file_test( path, G_FILE_TEST_EXISTS ) )
+ {
+ g_free( cust );
+ g_free( path );
+ }
+ else /* this generated filename can be used */
+ break;
+ }
+ g_free( dir );
+ if( G_LIKELY( path ) )
+ {
+ save_to_file( path, file_content, len );
+ g_free( path );
+
+ /* execute update-desktop-database" to update mimeinfo.cache */
+ update_desktop_database();
+ }
+ return cust;
+}
+
+/*
+ * Add an applications used to open this mime-type
+ * desktop_id is the name of *.desktop file.
+ *
+ * custom_desktop: used to store name of the newly created user-custom desktop file, can be NULL.
+ */
+void mime_type_add_action( const char* type, const char* desktop_id, char** custom_desktop )
+{
+ char* cust;
+ if( mime_type_has_action( type, desktop_id ) )
+ {
+ if( custom_desktop )
+ *custom_desktop = g_strdup( desktop_id );
+ return;
+ }
+
+ cust = make_custom_desktop_file( desktop_id, type );
+ if( custom_desktop )
+ *custom_desktop = cust;
+ else
+ g_free( cust );
+}
+
+static char* _locate_desktop_file( const char* dir, const char* unused, const gpointer desktop_id )
+{
+ char *path, *sep = NULL;
+ gboolean found = FALSE;
+
+ path = g_build_filename( dir, "applications", (const char*)desktop_id, NULL );
+ sep = strchr( (const char*)desktop_id, '-' );
+ if( sep )
+ sep = strrchr( path, '-' );
+
+ do{
+ if( g_file_test( path, G_FILE_TEST_IS_REGULAR ) )
+ {
+ found = TRUE;
+ break;
+ }
+ if( sep )
+ {
+ *sep = '/';
+ sep = strchr( sep + 1, '-' );
+ }
+ else
+ break;
+ }while( ! found );
+
+ if( ! found )
+ {
+ g_free( path );
+ return NULL;
+ }
+ return path;
+}
+
+char* mime_type_locate_desktop_file( const char* dir, const char* desktop_id )
+{
+ if( dir )
+ return _locate_desktop_file( dir, NULL, (gpointer) desktop_id );
+ return data_dir_foreach( _locate_desktop_file, NULL, (gpointer) desktop_id );
+}
+
+static char* get_default_action( const char* dir, const char* type, gpointer user_data )
+{
+ GKeyFile* file;
+ gboolean opened;
+ char* action = NULL;
+ char* path = g_build_filename( dir, "applications/defaults.list", NULL );
+ file = g_key_file_new();
+ opened = g_key_file_load_from_file( file, path, 0, NULL );
+ g_free( path );
+ if( G_LIKELY( opened ) )
+ action = g_key_file_get_string( file, "Default Applications", type, NULL );
+ g_key_file_free( file );
+
+ /* check for existence */
+ if( action )
+ {
+ path = mime_type_locate_desktop_file( NULL, action );
+ if( G_LIKELY( path ) )
+ g_free( path );
+ else
+ {
+ g_free( action );
+ action = NULL;
+ }
+ }
+ return action;
+}
+
+/*
+ * Get default applications used to open this mime-type
+ * The returned string was newly allocated, and should be
+ * freed when no longer used.
+ * If NULL is returned, that means default app is not set for this mime-type.
+ */
+char* mime_type_get_default_action( const char* type )
+{
+ /* FIXME: need to check parent types if default action of current type is not set. */
+ return data_dir_foreach( (DataDirFunc)get_default_action, type, NULL );
+}
+
+/*
+ * Set default applications used to open this mime-type
+ * desktop_id is the name of *.desktop file.
+ */
+void mime_type_set_default_action( const char* type, const char* desktop_id )
+{
+ GKeyFile* file;
+ gsize len = 0;
+ char* data = NULL;
+ char* dir = g_build_filename( g_get_user_data_dir(), "applications", NULL );
+ char* path = g_build_filename( dir, "defaults.list", NULL );
+
+ g_mkdir_with_parents( dir, 0700 );
+ g_free( dir );
+
+ file = g_key_file_new();
+ /* Load old file content, if available */
+ g_key_file_load_from_file( file, path, 0, NULL );
+ g_key_file_set_string( file, "Default Applications", type, desktop_id );
+ data = g_key_file_to_data( file, &len, NULL );
+ g_key_file_free( file );
+
+ save_to_file( path, data, len );
+
+ g_free( path );
+ g_free( data );
+}
Added: xarchiver/trunk/src/mime-type/mime-action.h
===================================================================
--- xarchiver/trunk/src/mime-type/mime-action.h (rev 0)
+++ xarchiver/trunk/src/mime-type/mime-action.h 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,69 @@
+/*
+ * mime-action.h
+ *
+ * Copyright 2007 PCMan <pcman.tw at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifndef _MIME_ACTION_H_INCLUDED_
+#define _MIME_ACTION_H_INCLUDED_
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Get a list of applications supporting this mime-type
+ * The returned string array was newly allocated, and should be
+ * freed with g_strfreev() when no longer used.
+ */
+char** mime_type_get_actions( const char* type );
+
+/*
+ * Add an applications used to open this mime-type
+ * desktop_id is the name of *.desktop file.
+ *
+ * custom_desktop: used to store name of the newly created user-custom desktop file, can be NULL.
+ */
+void mime_type_add_action( const char* type, const char* desktop_id, char** custom_desktop );
+
+/*
+ * Check if an applications currently set to open this mime-type
+ * desktop_id is the name of *.desktop file.
+ */
+gboolean mime_type_has_action( const char* type, const char* desktop_id );
+
+/*
+ * Get default applications used to open this mime-type
+ * The returned string was newly allocated, and should be
+ * freed when no longer used.
+ * If NULL is returned, that means default app is not set for this mime-type.
+ */
+char* mime_type_get_default_action( const char* type );
+
+/*
+ * Set default applications used to open this mime-type
+ * desktop_id is the name of *.desktop file.
+ */
+void mime_type_set_default_action( const char* type, const char* desktop_id );
+
+/* Locate the file path of desktop file by desktop_id */
+char* mime_type_locate_desktop_file( const char* dir, const char* desktop_id );
+
+G_END_DECLS
+
+#endif
Added: xarchiver/trunk/src/mime-type/mime-cache.c
===================================================================
--- xarchiver/trunk/src/mime-type/mime-cache.c (rev 0)
+++ xarchiver/trunk/src/mime-type/mime-cache.c 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,425 @@
+/*
+ * mime-cache.c
+ *
+ * Copyright 2007 PCMan <pcman.tw at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mime-cache.h"
+
+#include <glib.h>
+#include "glib-mem.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <fnmatch.h>
+
+#define LIB_MAJOR_VERSION 1
+#define LIB_MINOR_VERSION 0
+
+/* handle byte order here */
+#define VAL16(buffrer, idx) GUINT16_FROM_BE(*(guint16*)(buffer + idx))
+#define VAL32(buffer, idx) GUINT32_FROM_BE(*(guint32*)(buffer + idx))
+
+/* cache header */
+#define MAJOR_VERSION 0
+#define MINOR_VERSION 2
+#define ALIAS_LIST 4
+#define PARENT_LIST 8
+#define LITERAL_LIST 12
+#define SUFFIX_TREE 16
+#define GLOB_LIST 20
+#define MAGIC_LIST 24
+#define NAMESPACE_LIST 28
+#define HEADER_SIZE 32
+
+MimeCache* mime_cache_new( const char* file_path )
+{
+ MimeCache* cache = NULL;
+ cache = g_slice_new0( MimeCache );
+ if( G_LIKELY( file_path ) )
+ mime_cache_load( cache, file_path );
+ return cache;
+}
+
+static void mime_cache_unload( MimeCache* cache, gboolean clear )
+{
+ if( G_LIKELY(cache->buffer) )
+ {
+#ifdef HAVE_MMAP
+ munmap( (char*)cache->buffer, cache->size );
+#else
+ g_free( cache->buffer );
+#endif
+ }
+ g_free( cache->file_path );
+ if( clear )
+ memset( cache, 0, sizeof(MimeCache) );
+}
+
+void mime_cache_free( MimeCache* cache )
+{
+ mime_cache_unload( cache, FALSE );
+ g_slice_free( MimeCache, cache );
+}
+
+gboolean mime_cache_load( MimeCache* cache, const char* file_path )
+{
+ int fd = -1;
+ struct stat statbuf;
+ char* buffer = NULL;
+ guint32 offset;
+
+ /* Unload old cache first if needed */
+ if( file_path == cache->file_path )
+ cache->file_path = NULL; /* steal the string to prevent it from being freed during unload */
+ mime_cache_unload( cache, TRUE );
+
+ /* Store the file path */
+ cache->file_path = g_strdup( file_path );
+
+ /* Open the file and map it into memory */
+ fd = open ( file_path, O_RDONLY, 0 );
+
+ if ( fd < 0 )
+ return FALSE;
+
+ if( fstat ( fd, &statbuf ) < 0 || statbuf.st_size < HEADER_SIZE )
+ {
+ close( fd );
+ return FALSE;
+ }
+
+#ifdef HAVE_MMAP
+ buffer = mmap( NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0 );
+#else
+ buffer = g_malloc( statbuf.st_size );
+ if( buffer )
+ read( fd, buffer, statbuf.st_size );
+ else
+ buffer = (void*)-1;
+#endif
+ close( fd );
+
+ if ( buffer == (void*)-1 )
+ return FALSE;
+
+ /* Check version */
+ if ( VAL16( buffer, MAJOR_VERSION ) != LIB_MAJOR_VERSION ||
+ VAL16( buffer, MINOR_VERSION) != LIB_MINOR_VERSION )
+ {
+#ifdef HAVE_MMAP
+ munmap ( buffer, statbuf.st_size );
+#else
+ g_free( buffer );
+#endif
+ return FALSE;
+ }
+ cache->buffer = buffer;
+ cache->size = statbuf.st_size;
+
+ offset = VAL32(buffer, ALIAS_LIST);
+ cache->alias = buffer + offset + 4;
+ cache->n_alias = VAL32( buffer, offset );
+
+ offset = VAL32(buffer, PARENT_LIST);
+ cache->parents = buffer + offset + 4;
+ cache->n_parents = VAL32( buffer, offset );
+
+ offset = VAL32(buffer, LITERAL_LIST);
+ cache->literals = buffer + offset + 4;
+ cache->n_literals = VAL32( buffer, offset );
+
+ offset = VAL32(buffer, GLOB_LIST);
+ cache->globs = buffer + offset + 4;
+ cache->n_globs = VAL32( buffer, offset );
+
+ offset = VAL32(buffer, SUFFIX_TREE);
+ cache->suffix_roots = buffer + VAL32( buffer + offset, 4 );
+ cache->n_suffix_roots = VAL32( buffer, offset );
+
+ offset = VAL32(buffer, MAGIC_LIST);
+ cache->n_magics = VAL32( buffer, offset );
+ cache->magic_max_extent = VAL32( buffer + offset, 4 );
+ cache->magics = buffer + VAL32( buffer + offset, 8 );
+
+ return TRUE;
+}
+
+static gboolean magic_rule_match( const char* buf, const char* rule, const char* data, int len )
+{
+ gboolean match = FALSE;
+ guint32 offset = VAL32( rule, 0 );
+ guint32 range = VAL32( rule, 4 );
+
+ guint32 max_offset = offset + range;
+ guint32 val_len = VAL32( rule, 12 );
+
+ for( ; offset < max_offset && (offset + val_len) <= len ; ++offset )
+ {
+ guint32 val_off = VAL32( rule, 16 );
+ guint32 mask_off = VAL32( rule, 20 );
+ const char* value = buf + val_off;
+ /* FIXME: word_size and byte order are not supported! */
+
+ if( G_UNLIKELY( mask_off > 0 ) ) /* compare with mask applied */
+ {
+ int i = 0;
+ const char* mask = buf + mask_off;
+
+ for( ; i < val_len; ++i )
+ {
+ if( (data[offset + i] & mask[i]) != value[i] )
+ break;
+ }
+ if( i >= val_len )
+ match = TRUE;
+ }
+ else /* direct comparison */
+ {
+ if( 0 == memcmp( value, data + offset, val_len ) )
+ match = TRUE;
+ }
+
+ if( match )
+ {
+ guint32 n_children = VAL32( rule, 24 );
+ if( n_children > 0 )
+ {
+ guint32 first_child_off = VAL32( rule, 28 );
+ guint i;
+ rule = buf + first_child_off;
+ for( i = 0; i < n_children; ++i, rule += 32 )
+ {
+ if( magic_rule_match( buf, rule, data, len ) )
+ return TRUE;
+ }
+ }
+ else
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean magic_match( const char* buf, const char* magic, const char* data, int len )
+{
+ guint32 n_rules = VAL32( magic, 8 );
+ guint32 rules_off = VAL32( magic, 12 );
+ const char* rule = buf + rules_off;
+ int i;
+
+ for( i = 0; i < n_rules; ++i, rule += 32 )
+ if( magic_rule_match( buf, rule, data, len ) )
+ return TRUE;
+ return FALSE;
+}
+
+const char* mime_cache_lookup_magic( MimeCache* cache, const char* data, int len )
+{
+ const char* magic = cache->magics;
+ int i;
+
+ if( G_UNLIKELY( ! data || (0 == len) || ! magic ) )
+ return NULL;
+
+ for( i = 0; i < cache->n_magics; ++i, magic += 16 )
+ {
+ if( magic_match( cache->buffer, magic, data, len ) )
+ {
+ return cache->buffer + VAL32( magic, 4 );
+ }
+ }
+ return NULL;
+}
+
+static const char* lookup_suffix_nodes( const char* buf, const char* nodes, guint32 n, const char* name )
+{
+ gunichar uchar;
+
+ uchar = g_unichar_tolower( g_utf8_get_char( name ) );
+
+ /* binary search */
+ int upper = n, lower = 0;
+ int middle = n/2;
+
+ while( upper >= lower )
+ {
+ const char* node =nodes + middle * 16;
+ guint32 ch = VAL32(node, 0);
+
+ if( uchar < ch )
+ upper = middle - 1;
+ else if( uchar > ch )
+ lower = middle + 1;
+ else /* uchar == ch */
+ {
+ guint32 n_children = VAL32(node, 8);
+ name =g_utf8_next_char(name);
+
+ if( n_children > 0 )
+ {
+ guint32 first_child_off;
+ if( uchar == 0 )
+ return NULL;
+
+ if( ! name || 0 == name[0] )
+ {
+ guint32 offset = VAL32(node, 4);
+ return offset ? buf + offset : NULL;
+ }
+ first_child_off = VAL32(node, 12);
+ return lookup_suffix_nodes( buf, (buf + first_child_off), n_children, name );
+ }
+ else
+ {
+ if( ! name || 0 == name[0] )
+ {
+ guint32 offset = VAL32(node, 4);
+ return offset ? buf + offset : NULL;
+ }
+ return NULL;
+ }
+ }
+ middle = (upper + lower) / 2;
+ }
+ return NULL;
+}
+
+const char* mime_cache_lookup_suffix( MimeCache* cache, const char* filename, const char** suffix_pos )
+{
+ const char* root = cache->suffix_roots;
+ int i, n = cache->n_suffix_roots;
+ const char* mime_type = NULL, *ret = NULL, *prev_suffix_pos = (const char*)-1;
+
+ if( G_UNLIKELY( ! filename || ! *filename || 0 == n ) )
+ return NULL;
+
+ for( i = 0; i <n; ++i, root += 16 )
+ {
+ guint32 first_child_off;
+ guint32 ch = VAL32( root, 0 );
+ const char* suffix;
+
+ suffix = strchr( filename, ch );
+ if( ! suffix )
+ continue;
+
+ first_child_off = VAL32( root, 12 );
+ n = VAL32( root, 8 );
+ do{
+ mime_type = lookup_suffix_nodes( cache->buffer, cache->buffer + first_child_off, n, g_utf8_next_char(suffix) );
+ if( mime_type && suffix < prev_suffix_pos ) /* we want the longest suffix matched. */
+ {
+ ret = mime_type;
+ prev_suffix_pos = suffix;
+ }
+ }while( (suffix = strchr( suffix + 1, ch )) );
+ }
+ *suffix_pos = ret ? prev_suffix_pos : (const char*)-1;
+ return ret;
+}
+
+static const char* lookup_str_in_entries( MimeCache* cache, const char* entries, int n, const char* str )
+{
+ int upper = n, lower = 0;
+ int middle = upper/2;
+
+ if( G_LIKELY( entries && str && *str ) )
+ {
+ /* binary search */
+ while( upper >= lower )
+ {
+ const char* entry = entries + middle * 8;
+ const char* str2 = cache->buffer + VAL32(entry, 0);
+ int comp = strcmp( str, str2 );
+ if( comp < 0 )
+ upper = middle - 1;
+ else if( comp > 0 )
+ lower = middle + 1;
+ else /* comp == 0 */
+ return ( cache->buffer+ VAL32(entry, 4) );
+ middle = (upper + lower) / 2;
+ }
+ }
+ return NULL;
+}
+
+const char* mime_cache_lookup_alias( MimeCache* cache, const char* mime_type )
+{
+ return lookup_str_in_entries( cache, cache->alias, cache->n_alias, mime_type );
+}
+
+const char* mime_cache_lookup_literal( MimeCache* cache, const char* filename )
+{
+ return lookup_str_in_entries( cache, cache->literals, cache->n_literals, filename );
+}
+
+const char* mime_cache_lookup_glob( MimeCache* cache, const char* filename, int *glob_len )
+{
+ const char* entry = cache->globs, *type = NULL;
+ int i;
+ int max_glob_len = 0;
+
+ for( i = 0; i < cache->n_globs; ++i )
+ {
+ const char* glob = cache->buffer + VAL32( entry, 0 );
+ int glob_len;
+ if( 0 == fnmatch( glob, filename, 0 ) && (glob_len = strlen(glob)) > max_glob_len )
+ {
+ max_glob_len = glob_len;
+ type = (cache->buffer + VAL32( entry, 4 ));
+ }
+ entry += 8;
+ }
+ return type;
+}
+
+const char** mime_cache_lookup_parents( MimeCache* cache, const char* mime_type )
+{
+ guint32 n, i;
+ const char** result;
+ const char* parents;
+
+ parents = lookup_str_in_entries( cache, cache->parents, cache->n_parents, mime_type );
+ if( ! parents )
+ return NULL;
+ n = VAL32(parents, 0);
+ parents += 4;
+
+ result = (const char**)g_new( char*, n + 1 );
+
+ for( i = 0; i < n; ++i )
+ {
+ guint32 parent_off = VAL32( parents, i * 4 );
+ const char* parent = cache->buffer + parent_off;
+ result[i] = parent;
+ }
+ result[ n ] = NULL;
+ return result;
+}
Added: xarchiver/trunk/src/mime-type/mime-cache.h
===================================================================
--- xarchiver/trunk/src/mime-type/mime-cache.h (rev 0)
+++ xarchiver/trunk/src/mime-type/mime-cache.h 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,71 @@
+/*
+ * mime-cache.h
+ *
+ * Copyright 2007 PCMan <pcman.tw at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifndef _MIME_CACHE_H_INCLUDED_
+#define _MIME_CACHE_H_INCLUDED_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+struct _MimeCache
+{
+ char* file_path;
+ const char* buffer;
+ guint size;
+
+ guint32 n_alias;
+ const char* alias;
+
+ guint32 n_parents;
+ const char* parents;
+
+ guint32 n_literals;
+ const char* literals;
+
+ guint32 n_globs;
+ const char* globs;
+
+ guint32 n_suffix_roots;
+ const char* suffix_roots;
+
+ guint32 n_magics;
+ guint32 magic_max_extent;
+ const char* magics;
+};
+typedef struct _MimeCache MimeCache;
+
+MimeCache* mime_cache_new( const char* file_path );
+gboolean mime_cache_load( MimeCache* cache, const char* file_path );
+gboolean mime_cache_reload( MimeCache* cache );
+void mime_cache_free( MimeCache* cache );
+
+const char* mime_cache_lookup_literal( MimeCache* cache, const char* filename );
+const char* mime_cache_lookup_glob( MimeCache* cache, const char* filename, int *glob_len );
+const char* mime_cache_lookup_suffix( MimeCache* cache, const char* filename, const char** suffix_pos );
+const char* mime_cache_lookup_magic( MimeCache* cache, const char* data, int len );
+const char** mime_cache_lookup_parents( MimeCache* cache, const char* mime_type );
+const char* mime_cache_lookup_alias( MimeCache* cache, const char* mime_type );
+
+G_END_DECLS
+#endif
Added: xarchiver/trunk/src/mime-type/mime-type.c
===================================================================
--- xarchiver/trunk/src/mime-type/mime-type.c (rev 0)
+++ xarchiver/trunk/src/mime-type/mime-type.c 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,676 @@
+/*
+ * mime-type.c
+ *
+ * Copyright 2007 Houng Jen Yee (PCMan) <pcman.tw at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+/* Currently this library is NOT MT-safe */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mime-type.h"
+#include "mime-cache.h"
+
+#include <string.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "glib-mem.h"
+
+/*
+ * FIXME:
+ * Currently, mmap cannot be used because of the limitation of mmap.
+ * When a file is mapped for mime-type sniffing (checking file magic),
+ * they could be deleted during the check and hence result in Bus error.
+ * (Refer to the man page of mmap for detail)
+ * So here I undef HAVE_MMAP to disable the implementation using mmap.
+ */
+#undef HAVE_MMAP
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+/* max extent used to checking text files */
+#define TEXT_MAX_EXTENT 512
+
+const char xdg_mime_type_unknown[] = "application/octet-stream";
+const char xdg_mime_type_directory[] = "inode/directory";
+const char xdg_mime_type_executable[] = "application/x-executable";
+const char xdg_mime_type_plain_text[] = "text/plain";
+
+static MimeCache** caches = NULL;
+static guint n_caches = 0;
+guint32 mime_cache_max_extent = 0;
+
+/* allocated buffer used for mime magic checking to
+ prevent frequent memory allocation */
+static char* mime_magic_buf = NULL;
+/* for MT safety, the buffer should be locked */
+G_LOCK_DEFINE_STATIC(mime_magic_buf);
+
+/* load all mime.cache files on the system,
+ * including /usr/share/mime/mime.cache,
+ * /usr/local/share/mime/mime.cache,
+ * and $HOME/.local/share/mime/mime.cache. */
+static void mime_cache_load_all();
+
+/* free all mime.cache files on the system */
+static void mime_cache_free_all();
+
+static gboolean mime_type_is_data_plain_text( const char* data, int len );
+
+/*
+ * Get mime-type of the specified file (quick, but less accurate):
+ * Mime-type of the file is determined by cheking the filename only.
+ * If statbuf != NULL, it will be used to determine if the file is a directory.
+*/
+const char* mime_type_get_by_filename( const char* filename, struct stat* statbuf )
+{
+ const char* type = NULL, *suffix_pos = NULL, *prev_suffix_pos = (const char*)-1;
+ int i;
+ MimeCache* cache;
+
+ if( G_UNLIKELY( statbuf && S_ISDIR( statbuf->st_mode ) ) )
+ return XDG_MIME_TYPE_DIRECTORY;
+
+ for( i = 0; ! type && i < n_caches; ++i )
+ {
+ cache = caches[i];
+ type = mime_cache_lookup_literal( cache, filename );
+ if( G_LIKELY( ! type ) )
+ {
+ const char* _type = mime_cache_lookup_suffix( cache, filename, &suffix_pos );
+ if( _type &&suffix_pos < prev_suffix_pos )
+ {
+ type = _type;
+ prev_suffix_pos = suffix_pos;
+ }
+ }
+ }
+
+ if( G_UNLIKELY( ! type ) ) /* glob matching */
+ {
+ int max_glob_len = 0, glob_len = 0;
+ for( i = 0; ! type && i < n_caches; ++i )
+ {
+ cache = caches[i];
+ const char* matched_type;
+ matched_type = mime_cache_lookup_glob( cache, filename, &glob_len );
+ /* according to the spec, we should use the longest glob matched. */
+ if( matched_type && glob_len > max_glob_len )
+ {
+ type = matched_type;
+ max_glob_len = glob_len;
+ }
+ }
+ }
+
+ return type && *type ? type : XDG_MIME_TYPE_UNKNOWN;
+}
+
+/*
+ * Get mime-type info of the specified file (slow, but more accurate):
+ * To determine the mime-type of the file, mime_type_get_by_filename() is
+ * tried first. If the mime-type couldn't be determined, the content of
+ * the file will be checked, which is much more time-consuming.
+ * If statbuf is not NULL, it will be used to determine if the file is a directory,
+ * or if the file is an executable file; otherwise, the function will call stat()
+ * to gather this info itself. So if you already have stat info of the file,
+ * pass it to the function to prevent checking the file stat again.
+ * If you have basename of the file, pass it to the function can improve the
+ * efifciency, too. Otherwise, the function will try to get the basename of
+ * the specified file again.
+*/
+const char* mime_type_get_by_file( const char* filepath, struct stat* statbuf, const char* basename )
+{
+ const char* type;
+ struct stat _statbuf;
+
+ if( statbuf == NULL || G_UNLIKELY( S_ISLNK(statbuf->st_mode) ) )
+ {
+ statbuf = &_statbuf;
+ if( stat ( filepath, statbuf ) == -1 )
+ return XDG_MIME_TYPE_UNKNOWN;
+ }
+
+ if( S_ISDIR( statbuf->st_mode ) )
+ return XDG_MIME_TYPE_DIRECTORY;
+
+ if( basename == NULL )
+ {
+ basename = g_utf8_strrchr( filepath, -1, '/' );
+ if( G_LIKELY( basename ) )
+ ++basename;
+ else
+ basename = filepath;
+ }
+
+ if( G_LIKELY(basename) )
+ {
+ type = mime_type_get_by_filename( basename, statbuf );
+ if( G_LIKELY( strcmp( type, XDG_MIME_TYPE_UNKNOWN ) ) )
+ return type;
+ type = NULL;
+ }
+
+ if( G_LIKELY(statbuf->st_size > 0) )
+ {
+ int fd = -1;
+ char* data;
+
+ /* Open the file and map it into memory */
+ fd = open ( filepath, O_RDONLY, 0 );
+ if ( fd != -1 )
+ {
+ int len = mime_cache_max_extent < statbuf->st_size ? mime_cache_max_extent : statbuf->st_size;
+#ifdef HAVE_MMAP
+ data = (char*) mmap( NULL, len, PROT_READ, MAP_SHARED, fd, 0 );
+#else
+ /*
+ * FIXME: Can g_alloca() be used here? It's very fast, but is it safe?
+ * Actually, we can allocate a block of memory with the size of mime_cache_max_extent,
+ * then we don't need to do dynamic allocation/free every time, but multi-threading
+ * will be a nightmare, so...
+ */
+ /* try to lock the common buffer */
+ if( G_LIKELY( G_TRYLOCK( mime_magic_buf ) ) )
+ data = mime_magic_buf;
+ else /* the buffer is in use, allocate new one */
+ data = g_malloc( len );
+
+ len = read( fd, data, len );
+
+ if( G_UNLIKELY( len == -1 ) )
+ {
+ if( G_LIKELY( data == mime_magic_buf ) )
+ G_UNLOCK( mime_magic_buf );
+ else
+ g_free( data );
+ data = (void*)-1;
+ }
+#endif
+ if( data != (void*)-1 )
+ {
+ int i;
+ for( i = 0; ! type && i < n_caches; ++i )
+ type = mime_cache_lookup_magic( caches[i], data, len );
+
+ /* Check for executable file */
+ if( ! type && g_file_test( filepath, G_FILE_TEST_IS_EXECUTABLE ) )
+ type = XDG_MIME_TYPE_EXECUTABLE;
+
+ /* fallback: check for plain text */
+ if( ! type )
+ {
+ if( mime_type_is_data_plain_text( data, len > TEXT_MAX_EXTENT ? TEXT_MAX_EXTENT : len ) )
+ type = XDG_MIME_TYPE_PLAIN_TEXT;
+ }
+
+#ifdef HAVE_MMAP
+ munmap ( (char*)data, len );
+#else
+ if( G_LIKELY( data == mime_magic_buf ) )
+ G_UNLOCK( mime_magic_buf ); /* unlock the common buffer */
+ else /* we use our own buffer */
+ g_free( data );
+#endif
+ }
+ close( fd );
+ }
+ }
+ else
+ {
+ /* empty file can be viewed as text file */
+ type = XDG_MIME_TYPE_PLAIN_TEXT;
+ }
+ return type && *type ? type : XDG_MIME_TYPE_UNKNOWN;
+}
+
+/* Get the name of mime-type */
+/*const char* mime_type_get_type( MimeInfo* info )
+{
+ return info->type_name;
+}
+*/
+
+static char* parse_xml_desc( const char* buf, size_t len, const char* locale )
+{
+ const char *buf_end = buf + len;
+ const char *comment = NULL, *comment_end, *eng_comment;
+ size_t eng_comment_len = 0, comment_len = 0;
+ char target[64];
+ static const char end_comment_tag[]="</comment>";
+
+ eng_comment = g_strstr_len( buf, len, "<comment>" ); /* default English comment */
+ if( G_UNLIKELY( ! eng_comment ) ) /* This xml file is invalid */
+ return NULL;
+ len -= 9;
+ eng_comment += 9;
+ comment_end = g_strstr_len( eng_comment, len, end_comment_tag ); /* find </comment> */
+ if( G_UNLIKELY( ! comment_end ) )
+ return NULL;
+ eng_comment_len = comment_end - eng_comment;
+
+ if( G_LIKELY( locale ) )
+ {
+ int target_len = g_snprintf( target, 64, "<comment xml:lang=\"%s\">", locale);
+ buf = comment_end + 10;
+ len = (buf_end - buf);
+ if( G_LIKELY( ( comment = g_strstr_len( buf, len, target ) ) ) )
+ {
+ len -= target_len;
+ comment += target_len;
+ comment_end = g_strstr_len( comment, len, end_comment_tag ); /* find </comment> */
+ if( G_LIKELY( comment_end ) )
+ comment_len = (comment_end - comment);
+ else
+ comment = NULL;
+ }
+ }
+ if( G_LIKELY( comment ) )
+ return g_strndup( comment, comment_len );
+ return g_strndup( eng_comment, eng_comment_len );
+}
+
+static char* _mime_type_get_desc( const char* type, const char* data_dir, const char* locale )
+{
+ int fd;
+ struct stat statbuf;
+ char *buffer, *_locale, *desc;
+ char file_path[ 256 ];
+
+ /* FIXME: This path shouldn't be hard-coded. */
+ g_snprintf( file_path, 256, "%s/mime/%s.xml", data_dir, type );
+
+ fd = open ( file_path, O_RDONLY, 0 );
+ if ( G_UNLIKELY( fd == -1 ) )
+ return NULL;
+ if( G_UNLIKELY( fstat ( fd, &statbuf ) == -1 ) )
+ {
+ close( fd );
+ return NULL;
+ }
+#ifdef HAVE_MMAP
+ buffer = (char*)mmap( NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0 );
+#else
+ buffer = (char*)g_malloc( statbuf.st_size );
+ if( read( fd, buffer, statbuf.st_size ) == -1 )
+ {
+ g_free( buffer );
+ buffer = (char*)-1;
+ }
+#endif
+ close( fd );
+ if ( G_UNLIKELY( buffer == (void*)-1 ) )
+ return NULL;
+
+ _locale = NULL;
+ if( ! locale )
+ {
+ const char* const * langs = g_get_language_names();
+ char* dot = strchr( langs[0], '.' );
+ if( dot )
+ locale = _locale = g_strndup( langs[0], (size_t)(dot - langs[0]) );
+ else
+ locale = langs[0];
+ }
+ desc = parse_xml_desc( buffer, statbuf.st_size, locale );
+ g_free( _locale );
+
+#ifdef HAVE_MMAP
+ munmap ( buffer, statbuf.st_size );
+#else
+ g_free( buffer );
+#endif
+ return desc;
+}
+
+/*
+ * Get human-readable description of the mime-type
+ * If locale is NULL, current locale will be used.
+ * The returned string should be freed when no longer used.
+*/
+char* mime_type_get_desc( const char* type, const char* locale )
+{
+ char* desc;
+ const gchar* const * dir;
+
+ dir = g_get_system_data_dirs();
+ for( ; *dir; ++dir )
+ {
+ desc = _mime_type_get_desc( type, *dir, locale );
+ if( G_LIKELY(desc) )
+ return desc;
+ }
+
+ /*
+ * FIXME: According to specs on freedesktop.org, user_data_dir has
+ * higher priority than system_data_dirs, but in most cases, there was
+ * no file, or very few files in user_data_dir, so checking it first will
+ * result in many unnecessary open() system calls, yealding bad performance.
+ * Since the spec really sucks, we don't follow it here.
+ */
+ desc = _mime_type_get_desc( type, g_get_user_data_dir(), locale );
+ return desc;
+}
+
+void mime_type_finalize()
+{
+/*
+ if( G_LIKELY( table ) )
+ {
+ g_hash_table_destroy( table );
+ table = NULL;
+ }
+*/
+ mime_cache_free_all();
+}
+
+#if 0
+void test_parents(const char* type)
+{
+ int i;
+ const char** parents = NULL;
+ const char** p;
+
+ for( i = 0; i < n_caches; ++i )
+ {
+ parents = mime_cache_lookup_parents( caches[i], type );
+ if( parents )
+ break;
+ }
+ if( parents )
+ for( p = parents; *p; ++p )
+ {
+ g_debug( "%s is parent of %s", *p, type );
+ }
+ else
+ g_debug( "no parent found" );
+}
+
+void test_alias( const char* type )
+{
+ int i;
+ const char* alias = NULL;
+ for( i = 0; i < n_caches; ++i )
+ {
+ alias = mime_cache_lookup_alias( caches[i], type );
+ if( alias )
+ break;
+ }
+ g_debug("test:\nalias of %s is %s", type, alias );
+
+}
+#endif
+
+void mime_type_init()
+{
+ mime_cache_load_all();
+// table = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, (GDestroyNotify)mime_type_unref );
+}
+
+/* load all mime.cache files on the system,
+ * including /usr/share/mime/mime.cache,
+ * /usr/local/share/mime/mime.cache,
+ * and $HOME/.local/share/mime/mime.cache. */
+void mime_cache_load_all()
+{
+ const char* const * dirs;
+ int i;
+ const char filename[] = "/mime/mime.cache";
+ char* path;
+
+ dirs = g_get_system_data_dirs();
+ n_caches = g_strv_length( (char**)dirs ) + 1;
+ caches = (MimeCache**)g_slice_alloc( n_caches * sizeof(MimeCache*) );
+
+ path = g_build_filename( g_get_user_data_dir(), filename, NULL );
+ caches[0] = mime_cache_new( path );
+ g_free( path );
+ if( caches[0]->magic_max_extent > mime_cache_max_extent )
+ mime_cache_max_extent = caches[0]->magic_max_extent;
+
+ for( i = 1; i < n_caches; ++i )
+ {
+ path = g_build_filename( dirs[i - 1], filename, NULL );
+ caches[ i ] = mime_cache_new( path );
+ g_free( path );
+ if( caches[i]->magic_max_extent > mime_cache_max_extent )
+ mime_cache_max_extent = caches[i]->magic_max_extent;
+ }
+ mime_magic_buf = g_malloc( mime_cache_max_extent );
+ return ;
+}
+
+/* free all mime.cache files on the system */
+void mime_cache_free_all()
+{
+ mime_cache_foreach( (GFunc)mime_cache_free, NULL );
+ g_slice_free1( n_caches * sizeof(MimeCache*), caches );
+ n_caches = 0;
+ caches = NULL;
+ mime_cache_max_extent = 0;
+
+ g_free( mime_magic_buf );
+ mime_magic_buf = NULL;
+}
+
+/* Iterate through all mime caches */
+void mime_cache_foreach( GFunc func, gpointer user_data )
+{
+ int i;
+ for( i = 0; i < n_caches; ++i )
+ func( caches[i], user_data );
+}
+
+gboolean mime_cache_reload( MimeCache* cache )
+{
+ int i;
+ gboolean ret = mime_cache_load( cache, cache->file_path );
+ /* recalculate max magic extent */
+ for( i = 0; i < n_caches; ++i )
+ {
+ if( caches[i]->magic_max_extent > mime_cache_max_extent )
+ mime_cache_max_extent = caches[i]->magic_max_extent;
+ }
+
+ G_LOCK( mime_magic_buf );
+
+ mime_magic_buf = g_realloc( mime_magic_buf, mime_cache_max_extent );
+
+ G_UNLOCK( mime_magic_buf );
+
+ return ret;
+}
+
+gboolean mime_type_is_data_plain_text( const char* data, int len )
+{
+ int i;
+ if ( G_LIKELY( len >= 0 && data ) )
+ {
+ for ( i = 0; i < len; ++i )
+ {
+ if ( data[ i ] == '\0' )
+ return FALSE;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean mime_type_is_text_file( const char *file_path, const char* mime_type )
+{
+ int file;
+ int rlen;
+ gboolean ret = FALSE;
+
+ if( mime_type )
+ {
+ if( mime_type_is_subclass( mime_type, XDG_MIME_TYPE_PLAIN_TEXT ) )
+ return TRUE;
+ if( ! g_str_has_prefix( mime_type, "text/" ) && ! g_str_has_prefix( mime_type, "application/" ) )
+ return FALSE;
+ }
+
+ if( !file_path )
+ return FALSE;
+
+ file = open ( file_path, O_RDONLY );
+ if ( file != -1 )
+ {
+ struct stat statbuf;
+ if( fstat( file, &statbuf ) != -1 )
+ {
+ if( S_ISREG( statbuf.st_mode ) )
+ {
+#ifdef HAVE_MMAP
+ char* data;
+ rlen = statbuf.st_size < TEXT_MAX_EXTENT ? statbuf.st_size : TEXT_MAX_EXTENT;
+ data = (char*) mmap( NULL, rlen, PROT_READ, MAP_SHARED, file, 0 );
+ ret = mime_type_is_data_plain_text( data, rlen );
+ munmap ( (char*)data, rlen );
+#else
+ unsigned char data[ TEXT_MAX_EXTENT ];
+ rlen = read ( file, data, sizeof( data ) );
+ ret = mime_type_is_data_plain_text( (char*) data, rlen );
+#endif
+ }
+ }
+ close ( file );
+ }
+ return ret;
+}
+
+gboolean mime_type_is_executable_file( const char *file_path, const char* mime_type )
+{
+ if ( !mime_type )
+ {
+ mime_type = mime_type_get_by_file( file_path, NULL, NULL );
+ }
+
+ /*
+ * Only executable types can be executale.
+ * Since some common types, such as application/x-shellscript,
+ * are not in mime database, we have to add them ourselves.
+ */
+ if ( mime_type != XDG_MIME_TYPE_UNKNOWN &&
+ (mime_type_is_subclass( mime_type, XDG_MIME_TYPE_EXECUTABLE ) ||
+ mime_type_is_subclass( mime_type, "application/x-shellscript" ) ) )
+ {
+ if ( file_path )
+ {
+ if ( ! g_file_test( file_path, G_FILE_TEST_IS_EXECUTABLE ) )
+ return FALSE;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Check if the specified mime_type is the subclass of the specified parent type */
+gboolean mime_type_is_subclass( const char* type, const char* parent )
+{
+ int i;
+ const char** parents = NULL;
+ const char** p;
+
+ /* special case, the type specified is identical to the parent type. */
+ if( G_UNLIKELY( 0 == strcmp(type, parent) ) )
+ return TRUE;
+
+ for( i = 0; i < n_caches; ++i )
+ {
+ parents = mime_cache_lookup_parents( caches[i], type );
+ if( parents )
+ {
+ for( p = parents; *p; ++p )
+ {
+ if( 0 == strcmp( parent, *p ) )
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * Get all parent type of this mime_type
+ * The returned string array should be freed with g_strfreev().
+ */
+char** mime_type_get_parents( const char* type )
+{
+ int i;
+ const char** parents = NULL;
+ const char** p;
+ GArray* ret = g_array_sized_new( TRUE, TRUE, sizeof(char*), 5 );
+
+ for( i = 0; i < n_caches; ++i )
+ {
+ parents = mime_cache_lookup_parents( caches[i], type );
+ if( parents )
+ {
+ for( p = parents; *p; ++p )
+ {
+ char* parent = g_strdup( *p );
+ g_array_append_val( ret, parent );
+ }
+ }
+ }
+ return (char**)g_array_free( ret, (0 == ret->len) );
+}
+
+/*
+ * Get all alias types of this mime_type
+ * The returned string array should be freed with g_strfreev().
+ */
+char** mime_type_get_alias( const char* type )
+{
+ int i;
+ const char** alias = NULL;
+ const char** p;
+ GArray* ret = g_array_sized_new( TRUE, TRUE, sizeof(char*), 5 );
+
+ for( i = 0; i < n_caches; ++i )
+ {
+ alias = (const char **) mime_cache_lookup_alias( caches[i], type );
+ if( alias )
+ {
+ for( p = alias; *p; ++p )
+ {
+ char* type = g_strdup( *p );
+ g_array_append_val( ret, type );
+ }
+ }
+ }
+ return (char**)g_array_free( ret, (0 == ret->len) );
+}
+
+/*
+ * Get mime caches
+ */
+MimeCache** mime_type_get_caches( int* n )
+{
+ *n = n_caches;
+ return caches;
+}
Added: xarchiver/trunk/src/mime-type/mime-type.h
===================================================================
--- xarchiver/trunk/src/mime-type/mime-type.h (rev 0)
+++ xarchiver/trunk/src/mime-type/mime-type.h 2008-04-21 19:35:06 UTC (rev 26879)
@@ -0,0 +1,135 @@
+/*
+ * mime-type.h
+ *
+ * Copyright 2007 Houng Jen Yee (PCMan) <pcman.tw at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#ifndef _MIME_TYPE_H_INCLUDED_
+#define _MIME_TYPE_H_INCLUDED_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glib.h>
+
+#include "mime-action.h"
+#include "mime-cache.h"
+
+G_BEGIN_DECLS
+
+extern const char xdg_mime_type_unknown[];
+#define XDG_MIME_TYPE_UNKNOWN xdg_mime_type_unknown
+extern const char xdg_mime_type_directory[];
+#define XDG_MIME_TYPE_DIRECTORY xdg_mime_type_directory
+extern const char xdg_mime_type_executable[];
+#define XDG_MIME_TYPE_EXECUTABLE xdg_mime_type_executable
+extern const char xdg_mime_type_plain_text[];
+#define XDG_MIME_TYPE_PLAIN_TEXT xdg_mime_type_plain_text
+
+/* Opaque data structure storing information of recognized mime-types */
+//typedef struct MimeInfo MimeInfo;
+
+/* Initialize the library */
+void mime_type_init();
+
+/* Reload the shared-mime-database */
+gboolean mime_type_reload();
+
+/* Finalize the library and free related resources */
+void mime_type_finalize();
+
+/* Get additional info of the specified mime-type */
+//MimeInfo* mime_type_get_by_type( const char* type_name );
+
+/*
+ * Get mime-type info of the specified file (quick, but less accurate):
+ * Mime-type of the file is determined by cheking the filename only.
+ * If statbuf != NULL, it will be used to determine if the file is a directory,
+ * or if the file is an executable file.
+*/
+const char* mime_type_get_by_filename( const char* filename, struct stat* statbuf );
+
+/*
+ * Get mime-type info of the specified file (slow, but more accurate):
+ * To determine the mime-type of the file, mime_type_get_by_filename() is
+ * tried first. If the mime-type couldn't be determined, the content of
+ * the file will be checked, which is much more time-consuming.
+ * If statbuf is not NULL, it will be used to determine if the file is a directory,
+ * or if the file is an executable file; otherwise, the function will call stat()
+ * to gather this info itself. So if you already have stat info of the file,
+ * pass it to the function to prevent checking the file stat again.
+ * If you have basename of the file, pass it to the function can improve the
+ * efifciency, too. Otherwise, the function will try to get the basename of
+ * the specified file again.
+*/
+const char* mime_type_get_by_file( const char* filepath, struct stat* statbuf, const char* basename );
+
+gboolean mime_type_is_text_file( const char * file_path, const char * mime_type );
+
+gboolean mime_type_is_executable_file( const char * file_path, const char * mime_type );
+
+/* Check if the specified mime_type is the subclass of the specified parent type */
+gboolean mime_type_is_subclass( const char* type, const char* parent );
+
+/*
+ * Get all parent type of this mime_type
+ * The returned string array should be freed with g_strfreev().
+ */
+char** mime_type_get_parents( const char* type );
+
+/*
+ * Get all alias types of this mime_type
+ * The returned string array should be freed with g_strfreev().
+ */
+char** mime_type_get_alias( const char* type );
+
+/* Add reference to the MimeInfo */
+//MimeInfo* mime_type_ref( MimeInfo* info );
+
+/*
+ * Decrease reference count of the MimeInfo:
+ * When reference count of the MimeInfo struct is decreased to
+ * zero, the data structure will be freed automatically.
+*/
+//void mime_type_unref( MimeInfo* info );
+
+/* Get the name of mime-type */
+//const char* mime_type_get_type( MimeInfo* info );
+
+/*
+ * Get human-readable description of the mime-type
+ * If locale is NULL, current locale will be used.
+ * The returned string should be freed when no longer used.
+*/
+char* mime_type_get_desc( const char* type, const char* locale );
+
+/*
+ * Iterate through all mime caches
+ * Can be used to hook file alteration monitor for the cache files to handle reloading.
+ */
+void mime_cache_foreach( GFunc func, gpointer user_data );
+
+/*
+ * Get mime caches
+ */
+MimeCache** mime_type_get_caches( int* n );
+
+/* max magic extent of all caches */
+extern guint32 mime_cache_max_extent;
+
+G_END_DECLS
+#endif
More information about the Xfce4-commits
mailing list