/* * fm-file-info.c * * Copyright 2009 - 2010 Hong Jen Yee (PCMan) * * 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 #endif #include "fm-file-info.h" #include #include #include /* Query group name */ #include /* Query user name */ #include #include #include #include #include "fm-utils.h" #include static gboolean use_si_prefix = TRUE; static FmMimeType* desktop_entry_type = NULL; static FmMimeType* shortcut_type = NULL; static FmMimeType* mountable_type = NULL; /* intialize the file info system */ void _fm_file_info_init() { fm_mime_type_init(); desktop_entry_type = fm_mime_type_get_for_type("application/x-desktop"); /* fake mime-types for mountable and shortcuts */ shortcut_type = fm_mime_type_get_for_type("inode/x-shortcut"); shortcut_type->description = g_strdup(_("Shortcuts")); mountable_type = fm_mime_type_get_for_type("inode/x-mountable"); mountable_type->description = g_strdup(_("Mount Point")); } void _fm_file_info_finalize() { } FmFileInfo* fm_file_info_new () { FmFileInfo * fi = g_slice_new0( FmFileInfo ); fi->n_ref = 1; return fi; } void fm_file_info_set_from_gfileinfo(FmFileInfo* fi, GFileInfo* inf) { const char* tmp; GIcon* gicon; GFileType type; g_return_if_fail(fi->path); /* if display name is the same as its name, just use it. */ tmp = g_file_info_get_display_name(inf); if( strcmp(tmp, fi->path->name) == 0 ) fi->disp_name = fi->path->name; else fi->disp_name = g_strdup(tmp); fi->size = g_file_info_get_size(inf); tmp = g_file_info_get_content_type(inf); if( tmp ) fi->type = fm_mime_type_get_for_type( tmp ); fi->mode = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_MODE); fi->uid = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_UID); fi->gid = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_GID); type = g_file_info_get_file_type(inf); if( 0 == fi->mode ) /* if UNIX file mode is not available, compose a fake one. */ { switch(type) { case G_FILE_TYPE_REGULAR: fi->mode |= S_IFREG; break; case G_FILE_TYPE_DIRECTORY: fi->mode |= S_IFDIR; break; case G_FILE_TYPE_SYMBOLIC_LINK: fi->mode |= S_IFLNK; break; case G_FILE_TYPE_SHORTCUT: break; case G_FILE_TYPE_MOUNTABLE: break; } /* if it's a special file but it doesn't have UNIX mode, compose a fake one. */ if(type == G_FILE_TYPE_SPECIAL && 0 == fi->mode) { if(strcmp(tmp, "inode/chardevice")==0) fi->mode |= S_IFCHR; else if(strcmp(tmp, "inode/blockdevice")==0) fi->mode |= S_IFBLK; else if(strcmp(tmp, "inode/fifo")==0) fi->mode |= S_IFIFO; #ifdef S_IFSOCK else if(strcmp(tmp, "inode/socket")==0) fi->mode |= S_IFSOCK; #endif } } /* set file icon according to mime-type */ if( !fi->type || !fi->type->icon ) { gicon = g_file_info_get_icon(inf); fi->icon = fm_icon_from_gicon(gicon); /* g_object_unref(gicon); this is not needed since * g_file_info_get_icon didn't increase ref_count. * the object returned by g_file_info_get_icon is * owned by GFileInfo. */ } else fi->icon = fm_icon_ref(fi->type->icon); if( type == G_FILE_TYPE_MOUNTABLE || G_FILE_TYPE_SHORTCUT ) { const char* uri = g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); if( uri ) { if(g_str_has_prefix(uri, "file:/")) fi->target = g_filename_from_uri(uri, NULL, NULL); else fi->target = g_strdup(uri); } if( !fi->type ) { /* FIXME: is this appropriate? */ if( type == G_FILE_TYPE_SHORTCUT ) fi->type = fm_mime_type_ref(shortcut_type); else fi->type = fm_mime_type_ref(mountable_type); } /* FIXME: how about target of symlinks? */ } if(fm_path_is_native(fi->path)) { fi->dev = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_UNIX_DEVICE); } else { tmp = g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_ID_FILESYSTEM); fi->fs_id = g_intern_string(tmp); } fi->mtime = g_file_info_get_attribute_uint64(inf, G_FILE_ATTRIBUTE_TIME_MODIFIED); fi->atime = g_file_info_get_attribute_uint64(inf, G_FILE_ATTRIBUTE_TIME_ACCESS); } FmFileInfo* fm_file_info_new_from_gfileinfo(FmPath* path, GFileInfo* inf) { FmFileInfo* fi = fm_file_info_new(); fi->path = fm_path_ref(path); fm_file_info_set_from_gfileinfo(fi, inf); return fi; } void _fm_file_info_set_from_menu_cache_item(FmFileInfo* fi, MenuCacheItem* item) { const char* icon_name = menu_cache_item_get_icon(item); fi->disp_name = g_strdup(menu_cache_item_get_name(item)); if(icon_name) { char* tmp_name = NULL; if(icon_name[0] != '/') /* this is a icon name, not a full path to icon file. */ { char* dot = strrchr(icon_name, '.'); /* remove file extension, this is a hack to fix non-standard desktop entry files */ if(G_UNLIKELY(dot)) { ++dot; if(strcmp(dot, "png") == 0 || strcmp(dot, "svg") == 0 || strcmp(dot, "xpm") == 0) { tmp_name = g_strndup(icon_name, dot - icon_name - 1); icon_name = tmp_name; } } } fi->icon = fm_icon_from_name(icon_name); if(G_UNLIKELY(tmp_name)) g_free(tmp_name); } if(menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR) { fi->mode |= S_IFDIR; } else if(menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP) { fi->mode |= S_IFREG; fi->target = menu_cache_item_get_file_path(item); } fi->type = fm_mime_type_ref(shortcut_type); } FmFileInfo* _fm_file_info_new_from_menu_cache_item(FmPath* path, MenuCacheItem* item) { FmFileInfo* fi = fm_file_info_new(); fi->path = fm_path_ref(path); _fm_file_info_set_from_menu_cache_item(fi, item); return fi; } static void fm_file_info_clear( FmFileInfo* fi ) { if( fi->collate_key ) { if( fi->collate_key != fi->disp_name ) g_free(fi->collate_key); fi->collate_key = NULL; } if( fi->disp_name && fi->disp_name != fi->path->name ) { g_free( fi->disp_name ); fi->disp_name = NULL; } if( fi->path ) { fm_path_unref(fi->path); fi->path = NULL; } if( fi->disp_size ) { g_free( fi->disp_size ); fi->disp_size = NULL; } g_free(fi->target); if( fi->type ) { fm_mime_type_unref(fi->type); fi->type = NULL; } if( fi->icon ) { fm_icon_unref(fi->icon); fi->icon = NULL; } } FmFileInfo* fm_file_info_ref( FmFileInfo* fi ) { g_atomic_int_inc( &fi->n_ref ); return fi; } void fm_file_info_unref( FmFileInfo* fi ) { /* g_debug("unref file info: %d", fi->n_ref); */ if ( g_atomic_int_dec_and_test( &fi->n_ref) ) { fm_file_info_clear( fi ); g_slice_free( FmFileInfo, fi ); } } void fm_file_info_copy(FmFileInfo* fi, FmFileInfo* src) { FmPath* tmp_path = fm_path_ref(src->path); FmMimeType* tmp_type = fm_mime_type_ref(src->type); FmIcon* tmp_icon = fm_icon_ref(src->icon); /* NOTE: we need to ref source first. Otherwise, * if path, mime_type, and icon are identical in src * and fi, calling fm_file_info_clear() first on fi * might unref that. */ fm_file_info_clear(fi); fi->path = tmp_path; fi->type = tmp_type; fi->icon = tmp_icon; fi->mode = src->mode; if(fm_path_is_native(fi->path)) fi->dev = src->dev; else fi->fs_id = src->fs_id; fi->uid = src->uid; fi->gid = src->gid; fi->size = src->size; fi->mtime = src->mtime; fi->atime = src->atime; fi->blksize = src->blksize; fi->blocks = src->blocks; if(src->disp_name == src->path->name) fi->disp_name = src->disp_name; else fi->disp_name = g_strdup(src->disp_name); fi->collate_key = g_strdup(src->collate_key); fi->disp_size = g_strdup(src->disp_size); fi->disp_mtime = g_strdup(src->disp_mtime); fi->type = fm_mime_type_ref(src->type); fi->icon = fm_icon_ref(src->icon); } FmPath* fm_file_info_get_path( FmFileInfo* fi ) { return fi->path; } const char* fm_file_info_get_name( FmFileInfo* fi ) { return fi->path->name; } /* Get displayed name encoded in UTF-8 */ const char* fm_file_info_get_disp_name( FmFileInfo* fi ) { if(G_UNLIKELY(!fi->disp_name)) { /* FIXME: this is not guaranteed to be UTF-8. * Encoding conversion is needed here. */ return fi->path->name; } return fi->disp_name; } void fm_file_info_set_disp_name( FmFileInfo* fi, const char* name ) { if ( fi->disp_name && fi->disp_name != fi->path->name ) g_free( fi->disp_name ); fi->disp_name = g_strdup( name ); } goffset fm_file_info_get_size( FmFileInfo* fi ) { return fi->size; } const char* fm_file_info_get_disp_size( FmFileInfo* fi ) { if ( G_UNLIKELY( !fi->disp_size ) ) { if( S_ISREG(fi->mode) ) { char buf[ 64 ]; fm_file_size_to_str( buf, fi->size, use_si_prefix ); fi->disp_size = g_strdup( buf ); } } return fi->disp_size; } goffset fm_file_info_get_blocks( FmFileInfo* fi ) { return fi->blocks; } FmMimeType* fm_file_info_get_mime_type( FmFileInfo* fi ) { return fi->type ? fm_mime_type_ref(fi->type) : NULL; } mode_t fm_file_info_get_mode( FmFileInfo* fi ) { return fi->mode; } gboolean fm_file_info_is_dir( FmFileInfo* fi ) { return (S_ISDIR( fi->mode ) || (S_ISLNK( fi->mode ) && (0 == strcmp( fi->type->type, "inode/directory" )))); } gboolean fm_file_info_is_symlink( FmFileInfo* fi ) { return S_ISLNK( fi->mode ) ? TRUE : FALSE; } gboolean fm_file_info_is_shortcut(FmFileInfo* fi) { return fi->type == shortcut_type; } gboolean fm_file_info_is_mountable(FmFileInfo* fi) { return fi->type == mountable_type; } gboolean fm_file_info_is_image( FmFileInfo* fi ) { /* FIXME: We had better use functions of xdg_mime to check this */ if ( ! strncmp( "image/", fi->type->type, 6 ) ) return TRUE; return FALSE; } gboolean fm_file_info_is_desktop_entry( FmFileInfo* fi ) { return fi->type == desktop_entry_type; /* return g_strcmp0(fi->type->type, "application/x-desktop") == 0; */ } gboolean fm_file_info_is_unknown_type( FmFileInfo* fi ) { return g_content_type_is_unknown(fi->type->type); } /* full path of the file is required by this function */ gboolean fm_file_info_is_executable_type( FmFileInfo* fi ) { // FIXME: didn't check access rights. // return mime_type_is_executable_file( file_path, fi->type->type ); return g_content_type_can_be_executable(fi->type->type); } gboolean fm_file_info_can_thumbnail(FmFileInfo* fi) { /* We cannot use S_ISREG here as this exclude all symlinks */ if( !(fi->mode & S_IFREG) || fm_file_info_is_desktop_entry(fi) || fm_file_info_is_unknown_type(fi)) return FALSE; return TRUE; } const char* fm_file_info_get_collate_key( FmFileInfo* fi ) { if( G_UNLIKELY(!fi->collate_key) ) { char* casefold = g_utf8_casefold(fi->disp_name, -1); char* collate = g_utf8_collate_key_for_filename(casefold, -1); g_free(casefold); if( strcmp(collate, fi->disp_name) ) fi->collate_key = collate; else { fi->collate_key = fi->disp_name; g_free(collate); } } return fi->collate_key; } const char* fm_file_info_get_target( FmFileInfo* fi ) { return fi->target; } const char* fm_file_info_get_desc( FmFileInfo* fi ) { /* FIXME: how to handle descriptions for virtual files without mime-tyoes? */ return fi->type ? fm_mime_type_get_desc( fi->type ) : NULL; } const char* fm_file_info_get_disp_mtime( FmFileInfo* fi ) { /* FIXME: This can cause problems if the file really has mtime=0. */ /* We'd better hide mtime for virtual files only. */ if(fi->mtime > 0) { if ( ! fi->disp_mtime ) { char buf[ 128 ]; strftime( buf, sizeof( buf ), "%x %R", localtime( &fi->mtime ) ); fi->disp_mtime = g_strdup( buf ); } } return fi->disp_mtime; } time_t* fm_file_info_get_mtime( FmFileInfo* fi ) { return &fi->mtime; } time_t* fm_file_info_get_atime( FmFileInfo* fi ) { return &fi->atime; } static FmListFuncs fm_list_funcs = { fm_file_info_ref, fm_file_info_unref }; FmFileInfoList* fm_file_info_list_new() { return fm_list_new(&fm_list_funcs); } gboolean fm_list_is_file_info_list(FmList* list) { return list->funcs == &fm_list_funcs; } /* return TRUE if all files in the list are of the same type */ gboolean fm_file_info_list_is_same_type(FmFileInfoList* list) { /* FIXME: handle virtual files without mime-types */ if( ! fm_list_is_empty(list) ) { GList* l = fm_list_peek_head_link(list); FmFileInfo* fi = (FmFileInfo*)l->data; l = l->next; for(;l;l=l->next) { FmFileInfo* fi2 = (FmFileInfo*)l->data; if(fi->type != fi2->type) return FALSE; } } return TRUE; } /* return TRUE if all files in the list are on the same fs */ gboolean fm_file_info_list_is_same_fs(FmFileInfoList* list) { if( ! fm_list_is_empty(list) ) { GList* l = fm_list_peek_head_link(list); FmFileInfo* fi = (FmFileInfo*)l->data; l = l->next; for(;l;l=l->next) { FmFileInfo* fi2 = (FmFileInfo*)l->data; gboolean is_native = fm_path_is_native(fi->path); if( is_native != fm_path_is_native(fi2->path) ) return FALSE; if( is_native ) { if( fi->dev != fi2->dev ) return FALSE; } else { if( fi->fs_id != fi2->fs_id ) return FALSE; } } } return TRUE; }