[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