2
* C Implementation: vfs-file-info
4
* Description: File information
7
* Author: Hong Jen Yee (PCMan) <pcman.tw (AT) gmail.com>, (C) 2006
9
* Copyright: See COPYING file that comes with this distribution
13
#include "vfs-file-info.h"
17
#include <glib/gi18n.h>
18
#include <grp.h> /* Query group name */
19
#include <pwd.h> /* Query user name */
22
#include "vfs-app-desktop.h"
23
#include "md5.h" /* for thumbnails */
25
#define _MAX( x, y ) (x > y ? x : y)
27
static int big_thumb_size = 48, small_thumb_size = 20;
28
static gboolean utf8_file_name = FALSE;
30
void vfs_file_info_set_utf8_filename( gboolean is_utf8 )
32
utf8_file_name = is_utf8;
35
VFSFileInfo* vfs_file_info_new ()
37
VFSFileInfo * fi = g_slice_new0( VFSFileInfo );
42
static void vfs_file_info_clear( VFSFileInfo* fi )
44
if ( fi->disp_name && fi->disp_name != fi->name )
46
g_free( fi->disp_name );
56
g_free( fi->disp_size );
61
g_free( fi->disp_owner );
62
fi->disp_owner = NULL;
66
g_free( fi->disp_mtime );
67
fi->disp_mtime = NULL;
69
if ( fi->big_thumbnail )
71
gdk_pixbuf_unref( fi->big_thumbnail );
72
fi->big_thumbnail = NULL;
74
if ( fi->small_thumbnail )
76
gdk_pixbuf_unref( fi->small_thumbnail );
77
fi->small_thumbnail = NULL;
80
fi->disp_perm[ 0 ] = '\0';
84
vfs_mime_type_unref( fi->mime_type );
87
fi->flags = VFS_FILE_INFO_NONE;
90
void vfs_file_info_ref( VFSFileInfo* fi )
95
void vfs_file_info_unref( VFSFileInfo* fi )
100
vfs_file_info_clear( fi );
101
g_slice_free( VFSFileInfo, fi );
105
gboolean vfs_file_info_get( VFSFileInfo* fi,
106
const char* file_path,
107
const char* base_name )
109
struct stat file_stat;
110
vfs_file_info_clear( fi );
113
fi->name = g_strdup( base_name );
115
fi->name = g_path_get_basename( file_path );
117
if ( lstat( file_path, &file_stat ) == 0 )
119
/* This is time-consuming but can save much memory */
120
fi->mode = file_stat.st_mode;
121
fi->dev = file_stat.st_dev;
122
fi->uid = file_stat.st_uid;
123
fi->gid = file_stat.st_gid;
124
fi->size = file_stat.st_size;
125
fi->mtime = file_stat.st_mtime;
126
fi->atime = file_stat.st_atime;
127
fi->blksize = file_stat.st_blksize;
128
fi->blocks = file_stat.st_blocks;
130
if ( G_LIKELY( utf8_file_name && g_utf8_validate ( fi->name, -1, NULL ) ) )
132
fi->disp_name = fi->name; /* Don't duplicate the name and save memory */
136
fi->disp_name = g_filename_display_name( fi->name );
138
fi->mime_type = vfs_mime_type_get_from_file( file_path,
144
fi->mime_type = vfs_mime_type_get_from_type( XDG_MIME_TYPE_UNKNOWN );
148
const char* vfs_file_info_get_name( VFSFileInfo* fi )
153
/* Get displayed name encoded in UTF-8 */
154
const char* vfs_file_info_get_disp_name( VFSFileInfo* fi )
156
return fi->disp_name;
159
void vfs_file_info_set_disp_name( VFSFileInfo* fi, const char* name )
161
if ( fi->disp_name && fi->disp_name != fi->name )
162
g_free( fi->disp_name );
163
fi->disp_name = g_strdup( name );
166
void vfs_file_info_set_name( VFSFileInfo* fi, const char* name )
169
fi->name = g_strdup( name );
172
off_t vfs_file_info_get_size( VFSFileInfo* fi )
177
const char* vfs_file_info_get_disp_size( VFSFileInfo* fi )
179
if ( G_UNLIKELY( !fi->disp_size ) )
182
file_size_to_string( buf, fi->size );
183
fi->disp_size = g_strdup( buf );
185
return fi->disp_size;
188
off_t vfs_file_info_get_blocks( VFSFileInfo* fi )
193
VFSMimeType* vfs_file_info_get_mime_type( VFSFileInfo* fi )
195
vfs_mime_type_ref( fi->mime_type );
196
return fi->mime_type;
199
void vfs_file_info_reload_mime_type( VFSFileInfo* fi,
200
const char* full_path )
202
VFSMimeType * old_mime_type;
203
struct stat file_stat;
205
/* convert VFSFileInfo to struct stat */
206
/* In current implementation, only st_mode is used in
207
mime-type detection, so let's save some CPU cycles
208
and don't copy unused fields.
210
file_stat.st_mode = fi->mode;
212
file_stat.st_dev = fi->dev;
213
file_stat.st_uid = fi->uid;
214
file_stat.st_gid = fi->gid;
215
file_stat.st_size = fi->size;
216
file_stat.st_mtime = fi->mtime;
217
file_stat.st_atime = fi->atime;
218
file_stat.st_blksize = fi->blksize;
219
file_stat.st_blocks = fi->blocks;
221
old_mime_type = fi->mime_type;
222
fi->mime_type = vfs_mime_type_get_from_file( full_path,
223
fi->name, &file_stat );
224
vfs_mime_type_unref( old_mime_type ); /* FIXME: is vfs_mime_type_unref needed ?*/
227
const char* vfs_file_info_get_mime_type_desc( VFSFileInfo* fi )
229
return vfs_mime_type_get_description( fi->mime_type );
232
GdkPixbuf* vfs_file_info_get_big_icon( VFSFileInfo* fi )
234
/* get special icons for special files, especially for
235
some desktop icons */
237
if ( G_UNLIKELY( fi->flags != VFS_FILE_INFO_NONE ) )
241
vfs_mime_type_get_icon_size( &icon_size, NULL );
242
if ( fi->big_thumbnail )
244
w = gdk_pixbuf_get_width( fi->big_thumbnail );
245
h = gdk_pixbuf_get_height( fi->big_thumbnail );
250
if ( _MAX( w, h ) != icon_size )
252
char * icon_name = NULL;
253
if ( fi->big_thumbnail )
255
icon_name = ( char* ) g_object_steal_data(
256
G_OBJECT(fi->big_thumbnail), "name" );
257
gdk_pixbuf_unref( fi->big_thumbnail );
258
fi->big_thumbnail = NULL;
260
if ( G_LIKELY( icon_name ) )
262
if ( G_UNLIKELY( icon_name[ 0 ] == '/' ) )
263
fi->big_thumbnail = gdk_pixbuf_new_from_file( icon_name, NULL );
265
fi->big_thumbnail = gtk_icon_theme_load_icon(
266
gtk_icon_theme_get_default(),
268
icon_size, 0, NULL );
270
if ( fi->big_thumbnail )
271
g_object_set_data_full( G_OBJECT(fi->big_thumbnail), "name", icon_name, g_free );
275
return fi->big_thumbnail ? gdk_pixbuf_ref( fi->big_thumbnail ) : NULL;
277
if( G_UNLIKELY(!fi->mime_type) )
279
return vfs_mime_type_get_icon( fi->mime_type, TRUE );
282
GdkPixbuf* vfs_file_info_get_small_icon( VFSFileInfo* fi )
284
return vfs_mime_type_get_icon( fi->mime_type, FALSE );
287
GdkPixbuf* vfs_file_info_get_big_thumbnail( VFSFileInfo* fi )
289
return fi->big_thumbnail ? gdk_pixbuf_ref( fi->big_thumbnail ) : NULL;
292
GdkPixbuf* vfs_file_info_get_small_thumbnail( VFSFileInfo* fi )
294
return fi->small_thumbnail ? gdk_pixbuf_ref( fi->small_thumbnail ) : NULL;
297
const char* vfs_file_info_get_disp_owner( VFSFileInfo* fi )
299
struct passwd * puser;
300
struct group* pgroup;
301
char uid_str_buf[ 32 ];
303
char gid_str_buf[ 32 ];
306
/* FIXME: user names should be cached */
307
if ( ! fi->disp_owner )
309
puser = getpwuid( fi->uid );
310
if ( puser && puser->pw_name && *puser->pw_name )
311
user_name = puser->pw_name;
314
sprintf( uid_str_buf, "%d", fi->uid );
315
user_name = uid_str_buf;
318
pgroup = getgrgid( fi->gid );
319
if ( pgroup && pgroup->gr_name && *pgroup->gr_name )
320
group_name = pgroup->gr_name;
323
sprintf( gid_str_buf, "%d", fi->gid );
324
group_name = gid_str_buf;
326
fi->disp_owner = g_strdup_printf ( "%s:%s", user_name, group_name );
328
return fi->disp_owner;
331
const char* vfs_file_info_get_disp_mtime( VFSFileInfo* fi )
333
if ( ! fi->disp_mtime )
336
strftime( buf, sizeof( buf ),
338
localtime( &fi->mtime ) );
339
fi->disp_mtime = g_strdup( buf );
341
return fi->disp_mtime;
344
time_t* vfs_file_info_get_mtime( VFSFileInfo* fi )
349
time_t* vfs_file_info_get_atime( VFSFileInfo* fi )
354
static void get_file_perm_string( char* perm, mode_t mode )
356
perm[ 0 ] = S_ISDIR( mode ) ? 'd' : ( S_ISLNK( mode ) ? 'l' : '-' );
357
perm[ 1 ] = ( mode & S_IRUSR ) ? 'r' : '-';
358
perm[ 2 ] = ( mode & S_IWUSR ) ? 'w' : '-';
359
perm[ 3 ] = ( mode & S_IXUSR ) ? 'x' : '-';
360
perm[ 4 ] = ( mode & S_IRGRP ) ? 'r' : '-';
361
perm[ 5 ] = ( mode & S_IWGRP ) ? 'w' : '-';
362
perm[ 6 ] = ( mode & S_IXGRP ) ? 'x' : '-';
363
perm[ 7 ] = ( mode & S_IROTH ) ? 'r' : '-';
364
perm[ 8 ] = ( mode & S_IWOTH ) ? 'w' : '-';
365
perm[ 9 ] = ( mode & S_IXOTH ) ? 'x' : '-';
369
const char* vfs_file_info_get_disp_perm( VFSFileInfo* fi )
371
if ( ! fi->disp_perm[ 0 ] )
372
get_file_perm_string( fi->disp_perm,
374
return fi->disp_perm;
377
void file_size_to_string( char* buf, guint64 size )
384
FIXME: Is floating point calculation slower than integer division?
385
Some profiling is needed here.
387
if ( size > ( ( guint64 ) 1 ) << 30 )
389
if ( size > ( ( guint64 ) 1 ) << 40 )
392
size /= ( ( ( guint64 ) 1 << 40 ) / 10 );
393
point = ( guint ) ( size % 10 );
396
val = ((gfloat)size) / ( ( guint64 ) 1 << 40 );
402
size /= ( ( 1 << 30 ) / 10 );
403
point = ( guint ) ( size % 10 );
406
val = ((gfloat)size) / ( ( guint64 ) 1 << 30 );
410
else if ( size > ( 1 << 20 ) )
413
size /= ( ( 1 << 20 ) / 10 );
414
point = ( guint ) ( size % 10 );
417
val = ((gfloat)size) / ( ( guint64 ) 1 << 20 );
420
else if ( size > ( 1 << 10 ) )
423
size /= ( ( 1 << 10 ) / 10 );
427
val = ((gfloat)size) / ( ( guint64 ) 1 << 10 );
432
unit = size > 1 ? "Bytes" : "Byte";
433
sprintf( buf, "%u %s", ( guint ) size, unit );
436
/* sprintf( buf, "%llu.%u %s", size, point, unit ); */
437
sprintf( buf, "%.1f %s", val, unit );
440
gboolean vfs_file_info_is_dir( VFSFileInfo* fi )
442
if ( S_ISDIR( fi->mode ) )
444
if ( S_ISLNK( fi->mode ) &&
445
0 == strcmp( vfs_mime_type_get_type( fi->mime_type ), XDG_MIME_TYPE_DIRECTORY ) )
452
gboolean vfs_file_info_is_symlink( VFSFileInfo* fi )
454
return S_ISLNK( fi->mode ) ? TRUE : FALSE;
457
gboolean vfs_file_info_is_image( VFSFileInfo* fi )
459
/* FIXME: We had better use functions of xdg_mime to check this */
460
if ( ! strncmp( "image/", vfs_mime_type_get_type( fi->mime_type ), 6 ) )
465
gboolean vfs_file_info_is_unknown_type( VFSFileInfo* fi )
467
if ( ! strcmp( XDG_MIME_TYPE_UNKNOWN,
468
vfs_mime_type_get_type( fi->mime_type ) ) )
473
/* full path of the file is required by this function */
474
gboolean vfs_file_info_is_executable( VFSFileInfo* fi, const char* file_path )
476
return xdg_mime_is_executable_file( file_path, fi->mime_type->type );
479
/* full path of the file is required by this function */
480
gboolean vfs_file_info_is_text( VFSFileInfo* fi, const char* file_path )
482
return xdg_mime_is_text_file( file_path, fi->mime_type->type );
486
* Run default action of specified file.
487
* Full path of the file is required by this function.
489
gboolean vfs_file_info_open_file( VFSFileInfo* fi,
490
const char* file_path,
493
VFSMimeType * mime_type;
497
gboolean ret = FALSE;
500
if ( vfs_file_info_is_executable( fi, file_path ) )
502
argv[ 0 ] = file_path;
504
ret = g_spawn_async( NULL, argv, NULL, G_SPAWN_STDOUT_TO_DEV_NULL|
505
G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, err );
509
mime_type = vfs_file_info_get_mime_type( fi );
510
app_name = vfs_mime_type_get_default_action( mime_type );
513
app = vfs_app_desktop_new( app_name );
514
if ( ! vfs_app_desktop_get_exec( app ) )
515
app->exec = g_strdup( app_name ); /* FIXME: app->exec */
516
files = g_list_prepend( files, file_path );
517
/* FIXME: working dir is needed */
518
ret = vfs_app_desktop_open_files( gdk_screen_get_default(),
519
NULL, app, files, err );
520
g_list_free( files );
521
vfs_app_desktop_unref( app );
524
vfs_mime_type_unref( mime_type );
529
mode_t vfs_file_info_get_mode( VFSFileInfo* fi )
534
static void unload_thumnails_if_needed( VFSFileInfo* fi )
537
if ( fi->big_thumbnail && fi->flags == VFS_FILE_INFO_NONE )
539
w = gdk_pixbuf_get_width( fi->big_thumbnail );
540
h = gdk_pixbuf_get_height( fi->big_thumbnail );
541
if ( _MAX( w, h ) != big_thumb_size )
543
gdk_pixbuf_unref( fi->big_thumbnail );
544
fi->big_thumbnail = NULL;
547
if ( fi->small_thumbnail && fi->flags == VFS_FILE_INFO_NONE )
549
w = gdk_pixbuf_get_width( fi->small_thumbnail );
550
h = gdk_pixbuf_get_height( fi->small_thumbnail );
551
if ( MAX( w, h ) != small_thumb_size )
553
gdk_pixbuf_unref( fi->small_thumbnail );
554
fi->small_thumbnail = NULL;
559
gboolean vfs_file_info_is_thumbnail_loaded( VFSFileInfo* fi, gboolean big )
561
/* FIXME: I cannot find a better place to unload thumbnails */
562
unload_thumnails_if_needed( fi );
564
return ( fi->big_thumbnail != NULL );
565
return ( fi->small_thumbnail != NULL );
568
gboolean vfs_file_info_load_thumbnail( VFSFileInfo* fi,
569
const char* full_path,
573
md5_byte_t md5[ 16 ];
574
char* thumbnail_file;
575
char file_name[ 36 ];
576
char mtime_str[ 32 ];
577
const char* thumb_mtime;
580
GdkPixbuf* thumbnail, *result = NULL;
582
/* FIXME: I cannot find a better place to unload thumbnails */
583
unload_thumnails_if_needed( fi );
587
if ( fi->big_thumbnail && big )
592
if ( fi->small_thumbnail )
596
if ( !gdk_pixbuf_get_file_info( full_path, &w, &h ) )
597
return FALSE; /* image format cannot be recognized */
599
/* If the image itself is very small, we should load it directly */
600
if ( w <= 128 && h <= 128 )
602
size = big ? big_thumb_size : small_thumb_size;
603
thumbnail = gdk_pixbuf_new_from_file_at_scale( full_path, size, size,
606
fi->big_thumbnail = thumbnail;
608
fi->small_thumbnail = thumbnail;
609
return ( thumbnail != NULL );
612
uri = g_filename_to_uri( full_path, NULL, NULL );
614
md5_state_t md5_state;
615
md5_init( &md5_state );
616
md5_append( &md5_state, ( md5_byte_t * ) uri, strlen( uri ) );
617
md5_finish( &md5_state, md5 );
619
for ( i = 0; i < 16; ++i )
621
sprintf( ( file_name + i * 2 ), "%02x", md5[ i ] );
623
strcpy( ( file_name + i * 2 ), ".png" );
625
thumbnail_file = g_build_filename( g_get_home_dir(),
626
".thumbnails/normal",
629
/* load existing thumbnail */
630
thumbnail = gdk_pixbuf_new_from_file( thumbnail_file, NULL );
632
!( thumb_mtime = gdk_pixbuf_get_option( thumbnail, "tEXt::Thumb::MTime" ) ) ||
633
atol( thumb_mtime ) != fi->mtime )
635
/* create new thumbnail */
636
thumbnail = gdk_pixbuf_new_from_file_at_size( full_path, 128, 128, NULL );
639
sprintf( mtime_str, "%lu", fi->mtime );
640
gdk_pixbuf_save( thumbnail, thumbnail_file, "png", NULL,
641
"tEXt::Thumb::URI", uri, "tEXt::Thumb::MTime", mtime_str, NULL );
647
w = gdk_pixbuf_get_width( thumbnail );
648
h = gdk_pixbuf_get_height( thumbnail );
649
size = big ? big_thumb_size : small_thumb_size;
664
result = gdk_pixbuf_scale_simple(
666
w, h, GDK_INTERP_BILINEAR );
667
gdk_pixbuf_unref( thumbnail );
669
fi->big_thumbnail = result;
671
fi->small_thumbnail = result;
674
g_free( thumbnail_file );
675
return ( result != NULL );
678
void vfs_file_info_set_thumbnail_size( int big, int small )
680
big_thumb_size = big;
681
small_thumb_size = small;
684
void vfs_file_info_load_special_info( VFSFileInfo* fi,
685
const char* file_path )
687
if ( g_str_has_suffix( fi->name, ".desktop" ) )
689
VFSAppDesktop * desktop;
690
const char* icon_name;
691
fi->flags |= VFS_FILE_INFO_DESKTOP_ENTRY;
692
desktop = vfs_app_desktop_new( file_path );
693
if ( vfs_app_desktop_get_disp_name( desktop ) )
695
vfs_file_info_set_disp_name(
696
fi, vfs_app_desktop_get_disp_name( desktop ) );
698
if( fi->big_thumbnail )
700
gdk_pixbuf_unref( fi->big_thumbnail );
701
fi->big_thumbnail = NULL;
703
if ( (icon_name = vfs_app_desktop_get_icon_name( desktop )) )
705
char * _icon_name = strdup( icon_name );
708
vfs_mime_type_get_icon_size( &size, NULL );
709
/* icon name is a full path */
710
if ( icon_name[ 0 ] == '/' )
712
icon = gdk_pixbuf_new_from_file_at_size(
713
icon_name, size, size, NULL );
717
char* dot = strchr( _icon_name, '.' );
720
icon = gtk_icon_theme_load_icon(
721
gtk_icon_theme_get_default(),
722
_icon_name, size, 0, NULL );
724
/* save app icon in thumbnail */
725
fi->big_thumbnail = icon;
726
if ( G_LIKELY( icon ) )
727
g_object_set_data_full( G_OBJECT(icon), "name", _icon_name, g_free );
729
g_free( _icon_name );
731
vfs_app_desktop_unref( desktop );
735
void vfs_file_info_list_free( GList* list )
737
g_list_foreach( list, (GFunc)vfs_file_info_unref, NULL );
742
char* vfs_file_resolve_path( const char* cwd, const char* relative_path )
744
GString* ret = g_string_sized_new( 4096 );
746
if( G_UNLIKELY(*relative_path != '/') ) /* relative path */
748
if( G_UNLIKELY(relative_path[0] == '~') ) /* home dir */
750
g_string_append( ret, g_get_home_dir());
757
cwd = g_get_current_dir();
758
g_string_append( ret, cwd );
762
g_string_append( ret, cwd );
766
if( relative_path[0] != '/' )
767
g_string_append_c( ret, '/' );
769
while( G_LIKELY( *relative_path ) )
771
if( G_UNLIKELY(*relative_path == '.') )
773
if( relative_path[1] == '/' || relative_path[1] == '\0' ) /* current dir */
775
relative_path += relative_path[1] ? 2 : 1;
778
if( relative_path[1] == '.' &&
779
( relative_path[2] == '/' || relative_path[2] == '\0') ) /* parent dir */
781
gsize len = ret->len - 2;
782
while( ret->str[ len ] != '/' )
784
g_string_truncate( ret, len + 1 );
785
relative_path += relative_path[2] ? 3 : 2;
792
g_string_append_c( ret, *relative_path );
793
}while( G_LIKELY( *(relative_path++) != '/' && *relative_path ) );
796
/* remove tailing '/' */
797
if( G_LIKELY( ret->len > 1 ) && G_UNLIKELY( ret->str[ ret->len - 1 ] == '/' ) )
798
g_string_truncate( ret, ret->len - 1 );
799
return g_string_free( ret, FALSE );