summaryrefslogtreecommitdiffstats
path: root/abs/chroot-devel/pcmanfm
diff options
context:
space:
mode:
authorJames Meyer <james.meyer@operamail.com>2008-10-20 19:43:05 (GMT)
committerJames Meyer <james.meyer@operamail.com>2008-10-20 19:43:05 (GMT)
commit917f8e0bc9dcb481b5a44c19ca8348da1e241d27 (patch)
treebc95dfebebf61ca8dd5c62467509b8a951be8aef /abs/chroot-devel/pcmanfm
parentae1c36ce00971a8ce8c634006984f2de3d4033a7 (diff)
downloadlinhes_pkgbuild-917f8e0bc9dcb481b5a44c19ca8348da1e241d27.zip
linhes_pkgbuild-917f8e0bc9dcb481b5a44c19ca8348da1e241d27.tar.gz
linhes_pkgbuild-917f8e0bc9dcb481b5a44c19ca8348da1e241d27.tar.bz2
add pcmanfm to chroot-devel
Diffstat (limited to 'abs/chroot-devel/pcmanfm')
-rw-r--r--abs/chroot-devel/pcmanfm/PKGBUILD21
-rw-r--r--abs/chroot-devel/pcmanfm/pcmanfm.install12
-rw-r--r--abs/chroot-devel/pcmanfm/vfs-file-info.c699
3 files changed, 732 insertions, 0 deletions
diff --git a/abs/chroot-devel/pcmanfm/PKGBUILD b/abs/chroot-devel/pcmanfm/PKGBUILD
new file mode 100644
index 0000000..a82890d
--- /dev/null
+++ b/abs/chroot-devel/pcmanfm/PKGBUILD
@@ -0,0 +1,21 @@
+# Maintainer: Geoffroy Carrier <geoffroy.carrier@koon.fr>
+pkgname=pcmanfm
+pkgver=0.5
+pkgrel=3
+pkgdesc="File manager of the LXDE Desktop"
+arch=('i686' 'x86_64')
+url="http://pcmanfm.sourceforge.net/"
+license=('GPL')
+groups=('lxde')
+depends=('gtk2' 'shared-mime-info' 'desktop-file-utils' 'startup-notification' 'gamin')
+makedepends=('intltool' 'pkgconfig')
+install=pcmanfm.install
+source=(http://downloads.sourceforge.net/pcmanfm/pcmanfm-$pkgver.tar.gz)
+build() {
+ cd "$srcdir/$pkgname-$pkgver"
+ cp $startdir/vfs-file-info.c src/vfs/
+
+ ./configure --sysconfdir=/etc --prefix=/usr --disable-hal || return 1
+ make || return 1
+ make DESTDIR="$pkgdir" install || return 1
+}
diff --git a/abs/chroot-devel/pcmanfm/pcmanfm.install b/abs/chroot-devel/pcmanfm/pcmanfm.install
new file mode 100644
index 0000000..1182e76
--- /dev/null
+++ b/abs/chroot-devel/pcmanfm/pcmanfm.install
@@ -0,0 +1,12 @@
+post_install() {
+ update-mime-database usr/share/mime > /dev/null
+ update-desktop-database -q
+}
+
+post_upgrade() {
+ post_install
+}
+
+post_remove() {
+ post_install
+}
diff --git a/abs/chroot-devel/pcmanfm/vfs-file-info.c b/abs/chroot-devel/pcmanfm/vfs-file-info.c
new file mode 100644
index 0000000..a397893
--- /dev/null
+++ b/abs/chroot-devel/pcmanfm/vfs-file-info.c
@@ -0,0 +1,699 @@
+/*
+* C Implementation: vfs-file-info
+*
+* Description: File information
+*
+*
+* Author: Hong Jen Yee (PCMan) <pcman.tw (AT) gmail.com>, (C) 2006
+*
+* Copyright: See COPYING file that comes with this distribution
+*
+*/
+
+#include "vfs-file-info.h"
+#include <glib.h>
+#include "glib-mem.h"
+#include <glib/gi18n.h>
+#include <grp.h> /* Query group name */
+#include <pwd.h> /* Query user name */
+#include <string.h>
+
+#include "vfs-app-desktop.h"
+#include "vfs-thumbnail-loader.h"
+#include "vfs-utils.h" /* for vfs_load_icon */
+
+static int big_thumb_size = 48, small_thumb_size = 20;
+static gboolean utf8_file_name = FALSE;
+
+void vfs_file_info_set_utf8_filename( gboolean is_utf8 )
+{
+ utf8_file_name = is_utf8;
+}
+
+VFSFileInfo* vfs_file_info_new ()
+{
+ VFSFileInfo * fi = g_slice_new0( VFSFileInfo );
+ fi->n_ref = 1;
+ return fi;
+}
+
+static void vfs_file_info_clear( VFSFileInfo* fi )
+{
+ if ( fi->disp_name && fi->disp_name != fi->name )
+ {
+ g_free( fi->disp_name );
+ fi->disp_name = NULL;
+ }
+ if ( fi->name )
+ {
+ g_free( fi->name );
+ fi->name = NULL;
+ }
+ if ( fi->disp_size )
+ {
+ g_free( fi->disp_size );
+ fi->disp_size = NULL;
+ }
+ if ( fi->disp_owner )
+ {
+ g_free( fi->disp_owner );
+ fi->disp_owner = NULL;
+ }
+ if ( fi->disp_mtime )
+ {
+ g_free( fi->disp_mtime );
+ fi->disp_mtime = NULL;
+ }
+ if ( fi->big_thumbnail )
+ {
+ gdk_pixbuf_unref( fi->big_thumbnail );
+ fi->big_thumbnail = NULL;
+ }
+ if ( fi->small_thumbnail )
+ {
+ gdk_pixbuf_unref( fi->small_thumbnail );
+ fi->small_thumbnail = NULL;
+ }
+
+ fi->disp_perm[ 0 ] = '\0';
+
+ if ( fi->mime_type )
+ {
+ vfs_mime_type_unref( fi->mime_type );
+ fi->mime_type = NULL;
+ }
+ fi->flags = VFS_FILE_INFO_NONE;
+}
+
+VFSFileInfo* vfs_file_info_ref( VFSFileInfo* fi )
+{
+ g_atomic_int_inc( &fi->n_ref );
+ return fi;
+}
+
+void vfs_file_info_unref( VFSFileInfo* fi )
+{
+ if ( g_atomic_int_dec_and_test( &fi->n_ref) )
+ {
+ vfs_file_info_clear( fi );
+ g_slice_free( VFSFileInfo, fi );
+ }
+}
+
+gboolean vfs_file_info_get( VFSFileInfo* fi,
+ const char* file_path,
+ const char* base_name )
+{
+ struct stat file_stat;
+ vfs_file_info_clear( fi );
+
+ if ( base_name )
+ fi->name = g_strdup( base_name );
+ else
+ fi->name = g_path_get_basename( file_path );
+
+ if ( lstat( file_path, &file_stat ) == 0 )
+ {
+ /* This is time-consuming but can save much memory */
+ fi->mode = file_stat.st_mode;
+ fi->dev = file_stat.st_dev;
+ fi->uid = file_stat.st_uid;
+ fi->gid = file_stat.st_gid;
+ fi->size = file_stat.st_size;
+ fi->mtime = file_stat.st_mtime;
+ fi->atime = file_stat.st_atime;
+ fi->blksize = file_stat.st_blksize;
+ fi->blocks = file_stat.st_blocks;
+
+ if ( G_LIKELY( utf8_file_name && g_utf8_validate ( fi->name, -1, NULL ) ) )
+ {
+ fi->disp_name = fi->name; /* Don't duplicate the name and save memory */
+ }
+ else
+ {
+ fi->disp_name = g_filename_display_name( fi->name );
+ }
+ fi->mime_type = vfs_mime_type_get_from_file( file_path,
+ fi->disp_name,
+ &file_stat );
+ return TRUE;
+ }
+ else
+ fi->mime_type = vfs_mime_type_get_from_type( XDG_MIME_TYPE_UNKNOWN );
+ return FALSE;
+}
+
+const char* vfs_file_info_get_name( VFSFileInfo* fi )
+{
+ return fi->name;
+}
+
+/* Get displayed name encoded in UTF-8 */
+const char* vfs_file_info_get_disp_name( VFSFileInfo* fi )
+{
+ return fi->disp_name;
+}
+
+void vfs_file_info_set_disp_name( VFSFileInfo* fi, const char* name )
+{
+ if ( fi->disp_name && fi->disp_name != fi->name )
+ g_free( fi->disp_name );
+ fi->disp_name = g_strdup( name );
+}
+
+void vfs_file_info_set_name( VFSFileInfo* fi, const char* name )
+{
+ g_free( fi->name );
+ fi->name = g_strdup( name );
+}
+
+off_t vfs_file_info_get_size( VFSFileInfo* fi )
+{
+ return fi->size;
+}
+
+const char* vfs_file_info_get_disp_size( VFSFileInfo* fi )
+{
+ if ( G_UNLIKELY( !fi->disp_size ) )
+ {
+ char buf[ 64 ];
+ vfs_file_size_to_string( buf, fi->size );
+ fi->disp_size = g_strdup( buf );
+ }
+ return fi->disp_size;
+}
+
+off_t vfs_file_info_get_blocks( VFSFileInfo* fi )
+{
+ return fi->blocks;
+}
+
+VFSMimeType* vfs_file_info_get_mime_type( VFSFileInfo* fi )
+{
+ vfs_mime_type_ref( fi->mime_type );
+ return fi->mime_type;
+}
+
+void vfs_file_info_reload_mime_type( VFSFileInfo* fi,
+ const char* full_path )
+{
+ VFSMimeType * old_mime_type;
+ struct stat file_stat;
+
+ /* convert VFSFileInfo to struct stat */
+ /* In current implementation, only st_mode is used in
+ mime-type detection, so let's save some CPU cycles
+ and don't copy unused fields.
+ */
+ file_stat.st_mode = fi->mode;
+ /*
+ file_stat.st_dev = fi->dev;
+ file_stat.st_uid = fi->uid;
+ file_stat.st_gid = fi->gid;
+ file_stat.st_size = fi->size;
+ file_stat.st_mtime = fi->mtime;
+ file_stat.st_atime = fi->atime;
+ file_stat.st_blksize = fi->blksize;
+ file_stat.st_blocks = fi->blocks;
+ */
+ old_mime_type = fi->mime_type;
+ fi->mime_type = vfs_mime_type_get_from_file( full_path,
+ fi->name, &file_stat );
+ vfs_file_info_load_special_info( fi, full_path );
+ vfs_mime_type_unref( old_mime_type ); /* FIXME: is vfs_mime_type_unref needed ?*/
+}
+
+const char* vfs_file_info_get_mime_type_desc( VFSFileInfo* fi )
+{
+ return vfs_mime_type_get_description( fi->mime_type );
+}
+
+GdkPixbuf* vfs_file_info_get_big_icon( VFSFileInfo* fi )
+{
+ /* get special icons for special files, especially for
+ some desktop icons */
+
+ if ( G_UNLIKELY( fi->flags != VFS_FILE_INFO_NONE ) )
+ {
+ int w, h;
+ int icon_size;
+ vfs_mime_type_get_icon_size( &icon_size, NULL );
+ if ( fi->big_thumbnail )
+ {
+ w = gdk_pixbuf_get_width( fi->big_thumbnail );
+ h = gdk_pixbuf_get_height( fi->big_thumbnail );
+ }
+ else
+ w = h = 0;
+
+ if ( ABS( MAX( w, h ) - icon_size ) > 2 )
+ {
+ char * icon_name = NULL;
+ if ( fi->big_thumbnail )
+ {
+ icon_name = ( char* ) g_object_steal_data(
+ G_OBJECT(fi->big_thumbnail), "name" );
+ gdk_pixbuf_unref( fi->big_thumbnail );
+ fi->big_thumbnail = NULL;
+ }
+ if ( G_LIKELY( icon_name ) )
+ {
+ if ( G_UNLIKELY( icon_name[ 0 ] == '/' ) )
+ fi->big_thumbnail = gdk_pixbuf_new_from_file( icon_name, NULL );
+ else
+ fi->big_thumbnail = vfs_load_icon(
+ gtk_icon_theme_get_default(),
+ icon_name, icon_size );
+ }
+ if ( fi->big_thumbnail )
+ g_object_set_data_full( G_OBJECT(fi->big_thumbnail), "name", icon_name, g_free );
+ else
+ g_free( icon_name );
+ }
+ return fi->big_thumbnail ? gdk_pixbuf_ref( fi->big_thumbnail ) : NULL;
+ }
+ if( G_UNLIKELY(!fi->mime_type) )
+ return NULL;
+ return vfs_mime_type_get_icon( fi->mime_type, TRUE );
+}
+
+GdkPixbuf* vfs_file_info_get_small_icon( VFSFileInfo* fi )
+{
+ return vfs_mime_type_get_icon( fi->mime_type, FALSE );
+}
+
+GdkPixbuf* vfs_file_info_get_big_thumbnail( VFSFileInfo* fi )
+{
+ return fi->big_thumbnail ? gdk_pixbuf_ref( fi->big_thumbnail ) : NULL;
+}
+
+GdkPixbuf* vfs_file_info_get_small_thumbnail( VFSFileInfo* fi )
+{
+ return fi->small_thumbnail ? gdk_pixbuf_ref( fi->small_thumbnail ) : NULL;
+}
+
+const char* vfs_file_info_get_disp_owner( VFSFileInfo* fi )
+{
+ struct passwd * puser;
+ struct group* pgroup;
+ char uid_str_buf[ 32 ];
+ char* user_name;
+ char gid_str_buf[ 32 ];
+ char* group_name;
+
+ /* FIXME: user names should be cached */
+ if ( ! fi->disp_owner )
+ {
+ puser = getpwuid( fi->uid );
+ if ( puser && puser->pw_name && *puser->pw_name )
+ user_name = puser->pw_name;
+ else
+ {
+ sprintf( uid_str_buf, "%d", fi->uid );
+ user_name = uid_str_buf;
+ }
+
+ pgroup = getgrgid( fi->gid );
+ if ( pgroup && pgroup->gr_name && *pgroup->gr_name )
+ group_name = pgroup->gr_name;
+ else
+ {
+ sprintf( gid_str_buf, "%d", fi->gid );
+ group_name = gid_str_buf;
+ }
+ fi->disp_owner = g_strdup_printf ( "%s:%s", user_name, group_name );
+ }
+ return fi->disp_owner;
+}
+
+const char* vfs_file_info_get_disp_mtime( VFSFileInfo* fi )
+{
+ if ( ! fi->disp_mtime )
+ {
+ char buf[ 64 ];
+ strftime( buf, sizeof( buf ),
+ "%Y-%m-%d %H:%M",
+ localtime( &fi->mtime ) );
+ fi->disp_mtime = g_strdup( buf );
+ }
+ return fi->disp_mtime;
+}
+
+time_t* vfs_file_info_get_mtime( VFSFileInfo* fi )
+{
+ return & fi->mtime;
+}
+
+time_t* vfs_file_info_get_atime( VFSFileInfo* fi )
+{
+ return & fi->atime;
+}
+
+static void get_file_perm_string( char* perm, mode_t mode )
+{
+ perm[ 0 ] = S_ISDIR( mode ) ? 'd' : ( S_ISLNK( mode ) ? 'l' : '-' );
+ perm[ 1 ] = ( mode & S_IRUSR ) ? 'r' : '-';
+ perm[ 2 ] = ( mode & S_IWUSR ) ? 'w' : '-';
+ perm[ 3 ] = ( mode & S_IXUSR ) ? 'x' : '-';
+ perm[ 4 ] = ( mode & S_IRGRP ) ? 'r' : '-';
+ perm[ 5 ] = ( mode & S_IWGRP ) ? 'w' : '-';
+ perm[ 6 ] = ( mode & S_IXGRP ) ? 'x' : '-';
+ perm[ 7 ] = ( mode & S_IROTH ) ? 'r' : '-';
+ perm[ 8 ] = ( mode & S_IWOTH ) ? 'w' : '-';
+ perm[ 9 ] = ( mode & S_IXOTH ) ? 'x' : '-';
+ perm[ 10 ] = '\0';
+}
+
+const char* vfs_file_info_get_disp_perm( VFSFileInfo* fi )
+{
+ if ( ! fi->disp_perm[ 0 ] )
+ get_file_perm_string( fi->disp_perm,
+ fi->mode );
+ return fi->disp_perm;
+}
+
+void vfs_file_size_to_string( char* buf, guint64 size )
+{
+ char * unit;
+ /* guint point; */
+ gfloat val;
+
+ /*
+ FIXME: Is floating point calculation slower than integer division?
+ Some profiling is needed here.
+ */
+ if ( size > ( ( guint64 ) 1 ) << 30 )
+ {
+ if ( size > ( ( guint64 ) 1 ) << 40 )
+ {
+ /*
+ size /= ( ( ( guint64 ) 1 << 40 ) / 10 );
+ point = ( guint ) ( size % 10 );
+ size /= 10;
+ */
+ val = ((gfloat)size) / ( ( guint64 ) 1 << 40 );
+ unit = "TB";
+ }
+ else
+ {
+ /*
+ size /= ( ( 1 << 30 ) / 10 );
+ point = ( guint ) ( size % 10 );
+ size /= 10;
+ */
+ val = ((gfloat)size) / ( ( guint64 ) 1 << 30 );
+ unit = "GB";
+ }
+ }
+ else if ( size > ( 1 << 20 ) )
+ {
+ /*
+ size /= ( ( 1 << 20 ) / 10 );
+ point = ( guint ) ( size % 10 );
+ size /= 10;
+ */
+ val = ((gfloat)size) / ( ( guint64 ) 1 << 20 );
+ unit = "MB";
+ }
+ else if ( size > ( 1 << 10 ) )
+ {
+ /*
+ size /= ( ( 1 << 10 ) / 10 );
+ point = size % 10;
+ size /= 10;
+ */
+ val = ((gfloat)size) / ( ( guint64 ) 1 << 10 );
+ unit = "KB";
+ }
+ else
+ {
+ unit = size > 1 ? "Bytes" : "Byte";
+ sprintf( buf, "%u %s", ( guint ) size, unit );
+ return ;
+ }
+ /* sprintf( buf, "%llu.%u %s", size, point, unit ); */
+ sprintf( buf, "%.1f %s", val, unit );
+}
+
+gboolean vfs_file_info_is_dir( VFSFileInfo* fi )
+{
+ if ( S_ISDIR( fi->mode ) )
+ return TRUE;
+ if ( S_ISLNK( fi->mode ) &&
+ 0 == strcmp( vfs_mime_type_get_type( fi->mime_type ), XDG_MIME_TYPE_DIRECTORY ) )
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean vfs_file_info_is_symlink( VFSFileInfo* fi )
+{
+ return S_ISLNK( fi->mode ) ? TRUE : FALSE;
+}
+
+gboolean vfs_file_info_is_image( VFSFileInfo* fi )
+{
+ /* FIXME: We had better use functions of xdg_mime to check this */
+ if ( ! strncmp( "image/", vfs_mime_type_get_type( fi->mime_type ), 6 ) )
+ return TRUE;
+ return FALSE;
+}
+
+gboolean vfs_file_info_is_desktop_entry( VFSFileInfo* fi )
+{
+ return 0 != (fi->flags & VFS_FILE_INFO_DESKTOP_ENTRY);
+}
+
+gboolean vfs_file_info_is_unknown_type( VFSFileInfo* fi )
+{
+ if ( ! strcmp( XDG_MIME_TYPE_UNKNOWN,
+ vfs_mime_type_get_type( fi->mime_type ) ) )
+ return TRUE;
+ return FALSE;
+}
+
+/* full path of the file is required by this function */
+gboolean vfs_file_info_is_executable( VFSFileInfo* fi, const char* file_path )
+{
+ //return mime_type_is_executable_file( file_path, fi->mime_type->type );
+ return mime_type_is_executable_file( file_path, "crfap" );
+}
+
+/* full path of the file is required by this function */
+gboolean vfs_file_info_is_text( VFSFileInfo* fi, const char* file_path )
+{
+ return mime_type_is_text_file( file_path, fi->mime_type->type );
+}
+
+/*
+* Run default action of specified file.
+* Full path of the file is required by this function.
+*/
+gboolean vfs_file_info_open_file( VFSFileInfo* fi,
+ const char* file_path,
+ GError** err )
+{
+ VFSMimeType * mime_type;
+ char* app_name;
+ VFSAppDesktop* app;
+ GList* files = NULL;
+ gboolean ret = FALSE;
+ char* argv[ 2 ];
+
+ if ( vfs_file_info_is_executable( fi, file_path ) )
+ {
+ argv[ 0 ] = (char *) file_path;
+ argv[ 1 ] = '\0';
+ ret = g_spawn_async( NULL, argv, NULL, G_SPAWN_STDOUT_TO_DEV_NULL|
+ G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, err );
+ }
+ else
+ {
+ mime_type = vfs_file_info_get_mime_type( fi );
+ app_name = vfs_mime_type_get_default_action( mime_type );
+ if ( app_name )
+ {
+ app = vfs_app_desktop_new( app_name );
+ if ( ! vfs_app_desktop_get_exec( app ) )
+ app->exec = g_strdup( app_name ); /* FIXME: app->exec */
+ files = g_list_prepend( files, (gpointer) file_path );
+ /* FIXME: working dir is needed */
+ ret = vfs_app_desktop_open_files( gdk_screen_get_default(),
+ NULL, app, files, err );
+ g_list_free( files );
+ vfs_app_desktop_unref( app );
+ g_free( app_name );
+ }
+ vfs_mime_type_unref( mime_type );
+ }
+ return ret;
+}
+
+mode_t vfs_file_info_get_mode( VFSFileInfo* fi )
+{
+ return fi->mode;
+}
+
+gboolean vfs_file_info_is_thumbnail_loaded( VFSFileInfo* fi, gboolean big )
+{
+ if ( big )
+ return ( fi->big_thumbnail != NULL );
+ return ( fi->small_thumbnail != NULL );
+}
+
+gboolean vfs_file_info_load_thumbnail( VFSFileInfo* fi,
+ const char* full_path,
+ gboolean big )
+{
+ GdkPixbuf* thumbnail;
+
+ if ( big )
+ {
+ if ( fi->big_thumbnail )
+ return TRUE;
+ }
+ else
+ {
+ if ( fi->small_thumbnail )
+ return TRUE;
+ }
+ thumbnail = vfs_thumbnail_load_for_file( full_path,
+ big ? big_thumb_size : small_thumb_size , fi->mtime );
+ if( G_LIKELY( thumbnail ) )
+ {
+ if ( big )
+ fi->big_thumbnail = thumbnail;
+ else
+ fi->small_thumbnail = thumbnail;
+ }
+ else /* fallback to mime_type icon */
+ {
+ if ( big )
+ fi->big_thumbnail = vfs_file_info_get_big_icon( fi );
+ else
+ fi->small_thumbnail = vfs_file_info_get_small_icon( fi );
+ }
+ return ( thumbnail != NULL );
+}
+
+void vfs_file_info_set_thumbnail_size( int big, int small )
+{
+ big_thumb_size = big;
+ small_thumb_size = small;
+}
+
+void vfs_file_info_load_special_info( VFSFileInfo* fi,
+ const char* file_path )
+{
+ /*if ( G_LIKELY(fi->type) && G_UNLIKELY(fi->type->name, "application/x-desktop") ) */
+ if ( G_UNLIKELY( g_str_has_suffix( fi->name, ".desktop") ) )
+ {
+ VFSAppDesktop * desktop;
+ const char* icon_name;
+
+ fi->flags |= VFS_FILE_INFO_DESKTOP_ENTRY;
+ desktop = vfs_app_desktop_new( file_path );
+ if ( vfs_app_desktop_get_disp_name( desktop ) )
+ {
+ vfs_file_info_set_disp_name(
+ fi, vfs_app_desktop_get_disp_name( desktop ) );
+ }
+
+ if ( (icon_name = vfs_app_desktop_get_icon_name( desktop )) )
+ {
+ GdkPixbuf* icon;
+ int big_size, small_size;
+ vfs_mime_type_get_icon_size( &big_size, &small_size );
+ if( ! fi->big_thumbnail )
+ {
+ icon = vfs_app_desktop_get_icon( desktop, big_size, FALSE );
+ if( G_LIKELY(icon) )
+ fi->big_thumbnail =icon;
+ }
+ if( ! fi->small_thumbnail )
+ {
+ icon = vfs_app_desktop_get_icon( desktop, small_size, FALSE );
+ if( G_LIKELY(icon) )
+ fi->small_thumbnail =icon;
+ }
+ }
+ vfs_app_desktop_unref( desktop );
+ }
+}
+
+void vfs_file_info_list_free( GList* list )
+{
+ g_list_foreach( list, (GFunc)vfs_file_info_unref, NULL );
+ g_list_free( list );
+}
+
+
+char* vfs_file_resolve_path( const char* cwd, const char* relative_path )
+{
+ GString* ret = g_string_sized_new( 4096 );
+ int len;
+ gboolean strip_tail;
+
+ g_return_val_if_fail( G_LIKELY(relative_path), NULL );
+
+ len = strlen( relative_path );
+ strip_tail = (0 == len || relative_path[len-1] != '/');
+
+ if( G_UNLIKELY(*relative_path != '/') ) /* relative path */
+ {
+ if( G_UNLIKELY(relative_path[0] == '~') ) /* home dir */
+ {
+ g_string_append( ret, g_get_home_dir());
+ ++relative_path;
+ }
+ else
+ {
+ if( ! cwd )
+ {
+ char *cwd_new;
+ cwd_new = g_get_current_dir();
+ g_string_append( ret, cwd_new );
+ g_free( cwd_new );
+ }
+ else
+ g_string_append( ret, cwd );
+ }
+ }
+
+ if( relative_path[0] != '/' && (0 == ret->len || ret->str[ ret->len - 1 ] != '/' ) )
+ g_string_append_c( ret, '/' );
+
+ while( G_LIKELY( *relative_path ) )
+ {
+ if( G_UNLIKELY(*relative_path == '.') )
+ {
+ if( relative_path[1] == '/' || relative_path[1] == '\0' ) /* current dir */
+ {
+ relative_path += relative_path[1] ? 2 : 1;
+ continue;
+ }
+ if( relative_path[1] == '.' &&
+ ( relative_path[2] == '/' || relative_path[2] == '\0') ) /* parent dir */
+ {
+ gsize len = ret->len - 2;
+ while( ret->str[ len ] != '/' )
+ --len;
+ g_string_truncate( ret, len + 1 );
+ relative_path += relative_path[2] ? 3 : 2;
+ continue;
+ }
+ }
+
+ do
+ {
+ g_string_append_c( ret, *relative_path );
+ }while( G_LIKELY( *(relative_path++) != '/' && *relative_path ) );
+ }
+
+ /* if original path contains tailing '/', preserve it; otherwise, remove it. */
+ if( strip_tail && G_LIKELY( ret->len > 1 ) && G_UNLIKELY( ret->str[ ret->len - 1 ] == '/' ) )
+ g_string_truncate( ret, ret->len - 1 );
+ return g_string_free( ret, FALSE );
+}
+