Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* vi:set et ai sw=2 sts=2 ts=2: */ | |
| /*- | |
| * Copyright (c) 2005-2007 Benedikt Meurer <benny@xfce.org> | |
| * Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org> | |
| * | |
| * 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 | |
| #ifdef HAVE_SYS_TYPES_H | |
| #include <sys/types.h> | |
| #endif | |
| #ifdef HAVE_SYS_STAT_H | |
| #include <sys/stat.h> | |
| #endif | |
| #ifdef HAVE_ERRNO_H | |
| #include <errno.h> | |
| #endif | |
| #ifdef HAVE_MEMORY_H | |
| #include <memory.h> | |
| #endif | |
| #ifdef HAVE_STDLIB_H | |
| #include <stdlib.h> | |
| #endif | |
| #ifdef HAVE_STRING_H | |
| #include <string.h> | |
| #endif | |
| #ifdef HAVE_TIME_H | |
| #include <time.h> | |
| #endif | |
| #ifdef HAVE_UNISTD_H | |
| #include <unistd.h> | |
| #endif | |
| #include <gio/gio.h> | |
| #include <libxfce4ui/libxfce4ui.h> | |
| #include <thunarx/thunarx.h> | |
| #include <thunar/thunar-application.h> | |
| #include <thunar/thunar-chooser-dialog.h> | |
| #include <thunar/thunar-exec.h> | |
| #include <thunar/thunar-file.h> | |
| #include <thunar/thunar-file-monitor.h> | |
| #include <thunar/thunar-gio-extensions.h> | |
| #include <thunar/thunar-gobject-extensions.h> | |
| #include <thunar/thunar-private.h> | |
| #include <thunar/thunar-preferences.h> | |
| #include <thunar/thunar-user.h> | |
| #include <thunar/thunar-util.h> | |
| #include <thunar/thunar-dialogs.h> | |
| #include <thunar/thunar-icon-factory.h> | |
| /* Dump the file cache every X second, set to 0 to disable */ | |
| #define DUMP_FILE_CACHE 0 | |
| /* Signal identifiers */ | |
| enum | |
| { | |
| DESTROY, | |
| LAST_SIGNAL, | |
| }; | |
| static void thunar_file_info_init (ThunarxFileInfoIface *iface); | |
| static void thunar_file_dispose (GObject *object); | |
| static void thunar_file_finalize (GObject *object); | |
| static gchar *thunar_file_info_get_name (ThunarxFileInfo *file_info); | |
| static gchar *thunar_file_info_get_uri (ThunarxFileInfo *file_info); | |
| static gchar *thunar_file_info_get_parent_uri (ThunarxFileInfo *file_info); | |
| static gchar *thunar_file_info_get_uri_scheme (ThunarxFileInfo *file_info); | |
| static gchar *thunar_file_info_get_mime_type (ThunarxFileInfo *file_info); | |
| static gboolean thunar_file_info_has_mime_type (ThunarxFileInfo *file_info, | |
| const gchar *mime_type); | |
| static gboolean thunar_file_info_is_directory (ThunarxFileInfo *file_info); | |
| static GFileInfo *thunar_file_info_get_file_info (ThunarxFileInfo *file_info); | |
| static GFileInfo *thunar_file_info_get_filesystem_info (ThunarxFileInfo *file_info); | |
| static GFile *thunar_file_info_get_location (ThunarxFileInfo *file_info); | |
| static void thunar_file_info_changed (ThunarxFileInfo *file_info); | |
| static gboolean thunar_file_denies_access_permission (const ThunarFile *file, | |
| ThunarFileMode usr_permissions, | |
| ThunarFileMode grp_permissions, | |
| ThunarFileMode oth_permissions); | |
| static void thunar_file_monitor (GFileMonitor *monitor, | |
| GFile *path, | |
| GFile *other_path, | |
| GFileMonitorEvent event_type, | |
| gpointer user_data); | |
| static void thunar_file_watch_reconnect (ThunarFile *file); | |
| static gboolean thunar_file_load (ThunarFile *file, | |
| GCancellable *cancellable, | |
| GError **error); | |
| static gboolean thunar_file_is_readable (const ThunarFile *file); | |
| static gboolean thunar_file_same_filesystem (const ThunarFile *file_a, | |
| const ThunarFile *file_b); | |
| G_LOCK_DEFINE_STATIC (file_cache_mutex); | |
| G_LOCK_DEFINE_STATIC (file_content_type_mutex); | |
| G_LOCK_DEFINE_STATIC (file_rename_mutex); | |
| static ThunarUserManager *user_manager; | |
| static GHashTable *file_cache; | |
| static guint32 effective_user_id; | |
| static GQuark thunar_file_watch_quark; | |
| static guint file_signals[LAST_SIGNAL]; | |
| #define FLAG_SET_THUMB_STATE(file,new_state) G_STMT_START{ (file)->flags = ((file)->flags & ~THUNAR_FILE_FLAG_THUMB_MASK) | (new_state); }G_STMT_END | |
| #define FLAG_GET_THUMB_STATE(file) ((file)->flags & THUNAR_FILE_FLAG_THUMB_MASK) | |
| #define FLAG_SET(file,flag) G_STMT_START{ ((file)->flags |= (flag)); }G_STMT_END | |
| #define FLAG_UNSET(file,flag) G_STMT_START{ ((file)->flags &= ~(flag)); }G_STMT_END | |
| #define FLAG_IS_SET(file,flag) (((file)->flags & (flag)) != 0) | |
| #define DEFAULT_CONTENT_TYPE "application/octet-stream" | |
| typedef enum | |
| { | |
| THUNAR_FILE_FLAG_THUMB_MASK = 0x03, /* storage for ThunarFileThumbState */ | |
| THUNAR_FILE_FLAG_IN_DESTRUCTION = 1 << 2, /* for avoiding recursion during destroy */ | |
| THUNAR_FILE_FLAG_IS_MOUNTED = 1 << 3, /* whether this file is mounted */ | |
| } | |
| ThunarFileFlags; | |
| struct _ThunarFileClass | |
| { | |
| GObjectClass __parent__; | |
| /* signals */ | |
| void (*destroy) (ThunarFile *file); | |
| }; | |
| struct _ThunarFile | |
| { | |
| GObject __parent__; | |
| /* storage for the file information */ | |
| GFileInfo *info; | |
| GFileType kind; | |
| GFile *gfile; | |
| gchar *content_type; | |
| gchar *icon_name; | |
| gchar *custom_icon_name; | |
| gchar *display_name; | |
| gchar *basename; | |
| gchar *thumbnail_path; | |
| /* sorting */ | |
| gchar *collate_key; | |
| gchar *collate_key_nocase; | |
| /* flags for thumbnail state etc */ | |
| ThunarFileFlags flags; | |
| }; | |
| typedef struct | |
| { | |
| GFileMonitor *monitor; | |
| guint watch_count; | |
| } | |
| ThunarFileWatch; | |
| typedef struct | |
| { | |
| ThunarFileGetFunc func; | |
| gpointer user_data; | |
| GCancellable *cancellable; | |
| } | |
| ThunarFileGetData; | |
| static struct | |
| { | |
| GUserDirectory type; | |
| const gchar *icon_name; | |
| } | |
| thunar_file_dirs[] = | |
| { | |
| { G_USER_DIRECTORY_DESKTOP, "user-desktop" }, | |
| { G_USER_DIRECTORY_DOCUMENTS, "folder-documents" }, | |
| { G_USER_DIRECTORY_DOWNLOAD, "folder-download" }, | |
| { G_USER_DIRECTORY_MUSIC, "folder-music" }, | |
| { G_USER_DIRECTORY_PICTURES, "folder-pictures" }, | |
| { G_USER_DIRECTORY_PUBLIC_SHARE, "folder-publicshare" }, | |
| { G_USER_DIRECTORY_TEMPLATES, "folder-templates" }, | |
| { G_USER_DIRECTORY_VIDEOS, "folder-videos" } | |
| }; | |
| G_DEFINE_TYPE_WITH_CODE (ThunarFile, thunar_file, G_TYPE_OBJECT, | |
| G_IMPLEMENT_INTERFACE (THUNARX_TYPE_FILE_INFO, thunar_file_info_init)) | |
| #ifdef G_ENABLE_DEBUG | |
| #ifdef HAVE_ATEXIT | |
| static gboolean thunar_file_atexit_registered = FALSE; | |
| static void | |
| thunar_file_atexit_foreach (gpointer key, | |
| gpointer value, | |
| gpointer user_data) | |
| { | |
| gchar *uri; | |
| uri = g_file_get_uri (key); | |
| g_print ("--> %s (%u)\n", uri, G_OBJECT (value)->ref_count); | |
| if (G_OBJECT (key)->ref_count > 2) | |
| g_print (" GFile (%u)\n", G_OBJECT (key)->ref_count - 2); | |
| g_free (uri); | |
| } | |
| static void | |
| thunar_file_atexit (void) | |
| { | |
| G_LOCK (file_cache_mutex); | |
| if (file_cache == NULL || g_hash_table_size (file_cache) == 0) | |
| { | |
| G_UNLOCK (file_cache_mutex); | |
| return; | |
| } | |
| g_print ("--- Leaked a total of %u ThunarFile objects:\n", | |
| g_hash_table_size (file_cache)); | |
| g_hash_table_foreach (file_cache, thunar_file_atexit_foreach, NULL); | |
| g_print ("\n"); | |
| G_UNLOCK (file_cache_mutex); | |
| } | |
| #endif | |
| #endif | |
| #if DUMP_FILE_CACHE | |
| static void | |
| thunar_file_cache_dump_foreach (gpointer gfile, | |
| gpointer value, | |
| gpointer user_data) | |
| { | |
| gchar *name; | |
| name = g_file_get_parse_name (G_FILE (gfile)); | |
| g_print (" %s (%u)\n", name, G_OBJECT (value)->ref_count); | |
| g_free (name); | |
| } | |
| static gboolean | |
| thunar_file_cache_dump (gpointer user_data) | |
| { | |
| G_LOCK (file_cache_mutex); | |
| if (file_cache != NULL) | |
| { | |
| g_print ("--- %d ThunarFile objects in cache:\n", | |
| g_hash_table_size (file_cache)); | |
| g_hash_table_foreach (file_cache, thunar_file_cache_dump_foreach, NULL); | |
| g_print ("\n"); | |
| } | |
| G_UNLOCK (file_cache_mutex); | |
| return TRUE; | |
| } | |
| #endif | |
| static void | |
| thunar_file_class_init (ThunarFileClass *klass) | |
| { | |
| GObjectClass *gobject_class; | |
| #ifdef G_ENABLE_DEBUG | |
| #ifdef HAVE_ATEXIT | |
| if (G_UNLIKELY (!thunar_file_atexit_registered)) | |
| { | |
| atexit ((void (*)(void)) thunar_file_atexit); | |
| thunar_file_atexit_registered = TRUE; | |
| } | |
| #endif | |
| #endif | |
| #if DUMP_FILE_CACHE | |
| g_timeout_add_seconds (DUMP_FILE_CACHE, thunar_file_cache_dump, NULL); | |
| #endif | |
| /* pre-allocate the required quarks */ | |
| thunar_file_watch_quark = g_quark_from_static_string ("thunar-file-watch"); | |
| /* grab a reference on the user manager */ | |
| user_manager = thunar_user_manager_get_default (); | |
| /* determine the effective user id of the process */ | |
| effective_user_id = geteuid (); | |
| gobject_class = G_OBJECT_CLASS (klass); | |
| gobject_class->dispose = thunar_file_dispose; | |
| gobject_class->finalize = thunar_file_finalize; | |
| /** | |
| * ThunarFile::destroy: | |
| * @file : the #ThunarFile instance. | |
| * | |
| * Emitted when the system notices that the @file | |
| * was destroyed. | |
| **/ | |
| file_signals[DESTROY] = | |
| g_signal_new (I_("destroy"), | |
| G_TYPE_FROM_CLASS (klass), | |
| G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, | |
| G_STRUCT_OFFSET (ThunarFileClass, destroy), | |
| NULL, NULL, | |
| g_cclosure_marshal_VOID__VOID, | |
| G_TYPE_NONE, 0); | |
| } | |
| static void | |
| thunar_file_init (ThunarFile *file) | |
| { | |
| } | |
| static void | |
| thunar_file_info_init (ThunarxFileInfoIface *iface) | |
| { | |
| iface->get_name = thunar_file_info_get_name; | |
| iface->get_uri = thunar_file_info_get_uri; | |
| iface->get_parent_uri = thunar_file_info_get_parent_uri; | |
| iface->get_uri_scheme = thunar_file_info_get_uri_scheme; | |
| iface->get_mime_type = thunar_file_info_get_mime_type; | |
| iface->has_mime_type = thunar_file_info_has_mime_type; | |
| iface->is_directory = thunar_file_info_is_directory; | |
| iface->get_file_info = thunar_file_info_get_file_info; | |
| iface->get_filesystem_info = thunar_file_info_get_filesystem_info; | |
| iface->get_location = thunar_file_info_get_location; | |
| iface->changed = thunar_file_info_changed; | |
| } | |
| static void | |
| thunar_file_dispose (GObject *object) | |
| { | |
| ThunarFile *file = THUNAR_FILE (object); | |
| /* check that we don't recurse here */ | |
| if (!FLAG_IS_SET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION)) | |
| { | |
| /* emit the "destroy" signal */ | |
| FLAG_SET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION); | |
| g_signal_emit (object, file_signals[DESTROY], 0); | |
| FLAG_UNSET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION); | |
| } | |
| (*G_OBJECT_CLASS (thunar_file_parent_class)->dispose) (object); | |
| } | |
| static void | |
| thunar_file_finalize (GObject *object) | |
| { | |
| ThunarFile *file = THUNAR_FILE (object); | |
| /* verify that nobody's watching the file anymore */ | |
| #ifdef G_ENABLE_DEBUG | |
| ThunarFileWatch *file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark); | |
| if (file_watch != NULL) | |
| { | |
| g_error ("Attempt to finalize a ThunarFile, which has an active " | |
| "watch count of %d", file_watch->watch_count); | |
| } | |
| #endif | |
| /* drop the entry from the cache */ | |
| G_LOCK (file_cache_mutex); | |
| g_hash_table_remove (file_cache, file->gfile); | |
| G_UNLOCK (file_cache_mutex); | |
| /* release file info */ | |
| if (file->info != NULL) | |
| g_object_unref (file->info); | |
| /* free the custom icon name */ | |
| g_free (file->custom_icon_name); | |
| /* content type info */ | |
| g_free (file->content_type); | |
| g_free (file->icon_name); | |
| /* free display name and basename */ | |
| g_free (file->display_name); | |
| g_free (file->basename); | |
| /* free collate keys */ | |
| if (file->collate_key_nocase != file->collate_key) | |
| g_free (file->collate_key_nocase); | |
| g_free (file->collate_key); | |
| /* free the thumbnail path */ | |
| g_free (file->thumbnail_path); | |
| /* release file */ | |
| g_object_unref (file->gfile); | |
| (*G_OBJECT_CLASS (thunar_file_parent_class)->finalize) (object); | |
| } | |
| static gchar * | |
| thunar_file_info_get_name (ThunarxFileInfo *file_info) | |
| { | |
| return g_strdup (thunar_file_get_basename (THUNAR_FILE (file_info))); | |
| } | |
| static gchar* | |
| thunar_file_info_get_uri (ThunarxFileInfo *file_info) | |
| { | |
| return thunar_file_dup_uri (THUNAR_FILE (file_info)); | |
| } | |
| static gchar* | |
| thunar_file_info_get_parent_uri (ThunarxFileInfo *file_info) | |
| { | |
| GFile *parent; | |
| gchar *uri = NULL; | |
| parent = g_file_get_parent (THUNAR_FILE (file_info)->gfile); | |
| if (G_LIKELY (parent != NULL)) | |
| { | |
| uri = g_file_get_uri (parent); | |
| g_object_unref (parent); | |
| } | |
| return uri; | |
| } | |
| static gchar* | |
| thunar_file_info_get_uri_scheme (ThunarxFileInfo *file_info) | |
| { | |
| return g_file_get_uri_scheme (THUNAR_FILE (file_info)->gfile); | |
| } | |
| static gchar* | |
| thunar_file_info_get_mime_type (ThunarxFileInfo *file_info) | |
| { | |
| return g_strdup (thunar_file_get_content_type (THUNAR_FILE (file_info))); | |
| } | |
| static gboolean | |
| thunar_file_info_has_mime_type (ThunarxFileInfo *file_info, | |
| const gchar *mime_type) | |
| { | |
| if (THUNAR_FILE (file_info)->info == NULL) | |
| return FALSE; | |
| return g_content_type_is_a (thunar_file_get_content_type (THUNAR_FILE (file_info)), mime_type); | |
| } | |
| static gboolean | |
| thunar_file_info_is_directory (ThunarxFileInfo *file_info) | |
| { | |
| return thunar_file_is_directory (THUNAR_FILE (file_info)); | |
| } | |
| static GFileInfo * | |
| thunar_file_info_get_file_info (ThunarxFileInfo *file_info) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_info), NULL); | |
| if (THUNAR_FILE (file_info)->info != NULL) | |
| return g_object_ref (THUNAR_FILE (file_info)->info); | |
| else | |
| return NULL; | |
| } | |
| static GFileInfo * | |
| thunar_file_info_get_filesystem_info (ThunarxFileInfo *file_info) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_info), NULL); | |
| return g_file_query_filesystem_info (THUNAR_FILE (file_info)->gfile, | |
| THUNARX_FILESYSTEM_INFO_NAMESPACE, | |
| NULL, NULL); | |
| } | |
| static GFile * | |
| thunar_file_info_get_location (ThunarxFileInfo *file_info) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_info), NULL); | |
| return g_object_ref (THUNAR_FILE (file_info)->gfile); | |
| } | |
| static void | |
| thunar_file_info_changed (ThunarxFileInfo *file_info) | |
| { | |
| ThunarFile *file = THUNAR_FILE (file_info); | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file_info)); | |
| /* set the new thumbnail state manually, so we only emit file | |
| * changed once */ | |
| FLAG_SET_THUMB_STATE (file, THUNAR_FILE_THUMB_STATE_UNKNOWN); | |
| /* tell the file monitor that this file changed */ | |
| thunar_file_monitor_file_changed (file); | |
| } | |
| static gboolean | |
| thunar_file_denies_access_permission (const ThunarFile *file, | |
| ThunarFileMode usr_permissions, | |
| ThunarFileMode grp_permissions, | |
| ThunarFileMode oth_permissions) | |
| { | |
| ThunarFileMode mode; | |
| ThunarGroup *group; | |
| ThunarUser *user; | |
| gboolean result; | |
| GList *groups; | |
| GList *lp; | |
| /* query the file mode */ | |
| mode = thunar_file_get_mode (file); | |
| /* query the owner of the file, if we cannot determine | |
| * the owner, we can't tell if we're denied to access | |
| * the file, so we simply return FALSE then. | |
| */ | |
| user = thunar_file_get_user (file); | |
| if (G_UNLIKELY (user == NULL)) | |
| return FALSE; | |
| /* root is allowed to do everything */ | |
| if (G_UNLIKELY (effective_user_id == 0)) | |
| return FALSE; | |
| if (thunar_user_is_me (user)) | |
| { | |
| /* we're the owner, so the usr permissions must be granted */ | |
| result = ((mode & usr_permissions) == 0); | |
| /* release the user */ | |
| g_object_unref (G_OBJECT (user)); | |
| } | |
| else | |
| { | |
| group = thunar_file_get_group (file); | |
| if (G_LIKELY (group != NULL)) | |
| { | |
| /* release the file owner */ | |
| g_object_unref (G_OBJECT (user)); | |
| /* determine the effective user */ | |
| user = thunar_user_manager_get_user_by_id (user_manager, effective_user_id); | |
| if (G_LIKELY (user != NULL)) | |
| { | |
| /* check the group permissions */ | |
| groups = thunar_user_get_groups (user); | |
| for (lp = groups; lp != NULL; lp = lp->next) | |
| if (THUNAR_GROUP (lp->data) == group) | |
| { | |
| g_object_unref (G_OBJECT (user)); | |
| g_object_unref (G_OBJECT (group)); | |
| return ((mode & grp_permissions) == 0); | |
| } | |
| /* release the effective user */ | |
| g_object_unref (G_OBJECT (user)); | |
| } | |
| /* release the file group */ | |
| g_object_unref (G_OBJECT (group)); | |
| } | |
| /* check other permissions */ | |
| result = ((mode & oth_permissions) == 0); | |
| } | |
| return result; | |
| } | |
| static void | |
| thunar_file_monitor_update (GFile *path, | |
| GFileMonitorEvent event_type) | |
| { | |
| ThunarFile *file; | |
| _thunar_return_if_fail (G_IS_FILE (path)); | |
| file = thunar_file_cache_lookup (path); | |
| if (G_LIKELY (file != NULL)) | |
| { | |
| switch (event_type) | |
| { | |
| case G_FILE_MONITOR_EVENT_CREATED: | |
| case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: | |
| case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: | |
| case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: | |
| case G_FILE_MONITOR_EVENT_DELETED: | |
| thunar_file_reload (file); | |
| break; | |
| default: | |
| break; | |
| } | |
| g_object_unref (file); | |
| } | |
| } | |
| static void | |
| thunar_file_move_thumbnail_cache_file (GFile *old_file, | |
| GFile *new_file) | |
| { | |
| ThunarApplication *application; | |
| ThunarThumbnailCache *thumbnail_cache; | |
| _thunar_return_if_fail (G_IS_FILE (old_file)); | |
| _thunar_return_if_fail (G_IS_FILE (new_file)); | |
| application = thunar_application_get (); | |
| thumbnail_cache = thunar_application_get_thumbnail_cache (application); | |
| thunar_thumbnail_cache_move_file (thumbnail_cache, old_file, new_file); | |
| g_object_unref (thumbnail_cache); | |
| g_object_unref (application); | |
| } | |
| static void | |
| thunar_file_monitor_moved (ThunarFile *file, | |
| GFile *renamed_file) | |
| { | |
| GFile *previous_file; | |
| /* ref the old location */ | |
| previous_file = g_object_ref (G_OBJECT (file->gfile)); | |
| /* notify the thumbnail cache that we can now also move the thumbnail */ | |
| thunar_file_move_thumbnail_cache_file (previous_file, renamed_file); | |
| /* set the new file */ | |
| file->gfile = g_object_ref (G_OBJECT (renamed_file)); | |
| /* reload file information */ | |
| thunar_file_load (file, NULL, NULL); | |
| /* need to re-register the monitor handle for the new uri */ | |
| thunar_file_watch_reconnect (file); | |
| G_LOCK (file_cache_mutex); | |
| /* drop the previous entry from the cache */ | |
| g_hash_table_remove (file_cache, previous_file); | |
| /* drop the reference on the previous file */ | |
| g_object_unref (previous_file); | |
| /* insert the new entry */ | |
| g_hash_table_insert (file_cache, g_object_ref (file->gfile), file); | |
| G_UNLOCK (file_cache_mutex); | |
| } | |
| void | |
| thunar_file_reload_parent (ThunarFile *file) | |
| { | |
| ThunarFile *parent = NULL; | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| if (thunar_file_has_parent (file)) | |
| { | |
| GFile *parent_file; | |
| /* only reload file if it is in cache */ | |
| parent_file = g_file_get_parent (file->gfile); | |
| parent = thunar_file_cache_lookup (parent_file); | |
| g_object_unref (parent_file); | |
| } | |
| if (parent) | |
| { | |
| thunar_file_reload (parent); | |
| g_object_unref (parent); | |
| } | |
| } | |
| static void | |
| thunar_file_monitor (GFileMonitor *monitor, | |
| GFile *event_path, | |
| GFile *other_path, | |
| GFileMonitorEvent event_type, | |
| gpointer user_data) | |
| { | |
| ThunarFile *file = THUNAR_FILE (user_data); | |
| ThunarFile *other_file; | |
| _thunar_return_if_fail (G_IS_FILE_MONITOR (monitor)); | |
| _thunar_return_if_fail (G_IS_FILE (event_path)); | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| if (g_file_equal (event_path, file->gfile)) | |
| { | |
| /* the event occurred for the monitored ThunarFile */ | |
| if (event_type == G_FILE_MONITOR_EVENT_MOVED) | |
| { | |
| G_LOCK (file_rename_mutex); | |
| thunar_file_monitor_moved (file, other_path); | |
| G_UNLOCK (file_rename_mutex); | |
| return; | |
| } | |
| if (G_LIKELY (event_path)) | |
| thunar_file_monitor_update (event_path, event_type); | |
| } | |
| else | |
| { | |
| /* The event did not occur for the monitored ThunarFile, but for | |
| a file that is contained in ThunarFile which is actually a | |
| directory. */ | |
| if (event_type == G_FILE_MONITOR_EVENT_MOVED) | |
| { | |
| /* reload the target file if cached */ | |
| if (other_path == NULL) | |
| return; | |
| G_LOCK (file_rename_mutex); | |
| other_file = thunar_file_cache_lookup (other_path); | |
| if (other_file) | |
| thunar_file_reload (other_file); | |
| else | |
| other_file = thunar_file_get (other_path, NULL); | |
| if (other_file == NULL) | |
| return; | |
| /* notify the thumbnail cache that we can now also move the thumbnail */ | |
| thunar_file_move_thumbnail_cache_file (event_path, other_path); | |
| /* reload the containing target folder */ | |
| thunar_file_reload_parent (other_file); | |
| g_object_unref (other_file); | |
| G_UNLOCK (file_rename_mutex); | |
| } | |
| return; | |
| } | |
| } | |
| static void | |
| thunar_file_watch_destroyed (gpointer data) | |
| { | |
| ThunarFileWatch *file_watch = data; | |
| if (G_LIKELY (file_watch->monitor != NULL)) | |
| { | |
| g_file_monitor_cancel (file_watch->monitor); | |
| g_object_unref (file_watch->monitor); | |
| } | |
| g_slice_free (ThunarFileWatch, file_watch); | |
| } | |
| static void | |
| thunar_file_watch_reconnect (ThunarFile *file) | |
| { | |
| ThunarFileWatch *file_watch; | |
| /* recreate the monitor without changing the watch_count for file renames */ | |
| file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark); | |
| if (file_watch != NULL) | |
| { | |
| /* reset the old monitor */ | |
| if (G_LIKELY (file_watch->monitor != NULL)) | |
| { | |
| g_file_monitor_cancel (file_watch->monitor); | |
| g_object_unref (file_watch->monitor); | |
| } | |
| /* create a file or directory monitor */ | |
| file_watch->monitor = g_file_monitor (file->gfile, G_FILE_MONITOR_WATCH_MOUNTS | G_FILE_MONITOR_SEND_MOVED, NULL, NULL); | |
| if (G_LIKELY (file_watch->monitor != NULL)) | |
| { | |
| /* watch monitor for file changes */ | |
| g_signal_connect (file_watch->monitor, "changed", G_CALLBACK (thunar_file_monitor), file); | |
| } | |
| } | |
| } | |
| static void | |
| thunar_file_set_emblem_names_ready (GObject *source_object, | |
| GAsyncResult *result, | |
| gpointer user_data) | |
| { | |
| ThunarFile *file = THUNAR_FILE (user_data); | |
| GError *error = NULL; | |
| if (!g_file_set_attributes_finish (G_FILE (source_object), result, NULL, &error)) | |
| { | |
| g_warning ("Failed to set metadata: %s", error->message); | |
| g_error_free (error); | |
| g_file_info_remove_attribute (file->info, "metadata::emblems"); | |
| } | |
| thunar_file_changed (file); | |
| } | |
| static void | |
| thunar_file_info_clear (ThunarFile *file) | |
| { | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| /* release the current file info */ | |
| if (file->info != NULL) | |
| { | |
| g_object_unref (file->info); | |
| file->info = NULL; | |
| } | |
| /* unset */ | |
| file->kind = G_FILE_TYPE_UNKNOWN; | |
| /* free the custom icon name */ | |
| g_free (file->custom_icon_name); | |
| file->custom_icon_name = NULL; | |
| /* free display name and basename */ | |
| g_free (file->display_name); | |
| file->display_name = NULL; | |
| g_free (file->basename); | |
| file->basename = NULL; | |
| /* content type */ | |
| g_free (file->content_type); | |
| file->content_type = NULL; | |
| g_free (file->icon_name); | |
| file->icon_name = NULL; | |
| /* free collate keys */ | |
| if (file->collate_key_nocase != file->collate_key) | |
| g_free (file->collate_key_nocase); | |
| file->collate_key_nocase = NULL; | |
| g_free (file->collate_key); | |
| file->collate_key = NULL; | |
| /* free thumbnail path */ | |
| g_free (file->thumbnail_path); | |
| file->thumbnail_path = NULL; | |
| /* assume the file is mounted by default */ | |
| FLAG_SET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| /* set thumb state to unknown */ | |
| FLAG_SET_THUMB_STATE (file, THUNAR_FILE_THUMB_STATE_UNKNOWN); | |
| } | |
| static void | |
| thunar_file_info_reload (ThunarFile *file, | |
| GCancellable *cancellable) | |
| { | |
| const gchar *target_uri; | |
| GKeyFile *key_file; | |
| gchar *p; | |
| const gchar *display_name; | |
| gboolean is_secure = FALSE; | |
| gchar *casefold; | |
| gchar *path; | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| _thunar_return_if_fail (file->info == NULL || G_IS_FILE_INFO (file->info)); | |
| if (G_LIKELY (file->info != NULL)) | |
| { | |
| /* this is requested so often, cache it */ | |
| file->kind = g_file_info_get_file_type (file->info); | |
| if (file->kind == G_FILE_TYPE_MOUNTABLE) | |
| { | |
| target_uri = g_file_info_get_attribute_string (file->info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); | |
| if (target_uri != NULL | |
| && !g_file_info_get_attribute_boolean (file->info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT)) | |
| FLAG_SET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| else | |
| FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| } | |
| } | |
| /* determine the basename */ | |
| file->basename = g_file_get_basename (file->gfile); | |
| _thunar_assert (file->basename != NULL); | |
| /* problematic files with content type reading */ | |
| if (strcmp (file->basename, "kmsg") == 0 | |
| && g_file_is_native (file->gfile)) | |
| { | |
| path = g_file_get_path (file->gfile); | |
| if (g_strcmp0 (path, "/proc/kmsg") == 0) | |
| file->content_type = g_strdup (DEFAULT_CONTENT_TYPE); | |
| g_free (path); | |
| } | |
| /* check if this file is a desktop entry */ | |
| if (thunar_file_is_desktop_file (file, &is_secure) && is_secure) | |
| { | |
| /* determine the custom icon and display name for .desktop files */ | |
| /* query a key file for the .desktop file */ | |
| key_file = thunar_g_file_query_key_file (file->gfile, cancellable, NULL); | |
| if (key_file != NULL) | |
| { | |
| /* read the icon name from the .desktop file */ | |
| file->custom_icon_name = g_key_file_get_string (key_file, | |
| G_KEY_FILE_DESKTOP_GROUP, | |
| G_KEY_FILE_DESKTOP_KEY_ICON, | |
| NULL); | |
| if (G_UNLIKELY (exo_str_is_empty (file->custom_icon_name))) | |
| { | |
| /* make sure we set null if the string is empty else the assertion in | |
| * thunar_icon_factory_lookup_icon() will fail */ | |
| g_free (file->custom_icon_name); | |
| file->custom_icon_name = NULL; | |
| } | |
| else | |
| { | |
| /* drop any suffix (e.g. '.png') from themed icons */ | |
| if (!g_path_is_absolute (file->custom_icon_name)) | |
| { | |
| p = strrchr (file->custom_icon_name, '.'); | |
| if (p != NULL) | |
| *p = '\0'; | |
| } | |
| } | |
| /* read the display name from the .desktop file (will be overwritten later | |
| * if it's undefined here) */ | |
| file->display_name = g_key_file_get_locale_string (key_file, | |
| G_KEY_FILE_DESKTOP_GROUP, | |
| G_KEY_FILE_DESKTOP_KEY_NAME, | |
| NULL, NULL); | |
| /* drop the name if it's empty or has invalid encoding */ | |
| if (exo_str_is_empty (file->display_name) | |
| || !g_utf8_validate (file->display_name, -1, NULL)) | |
| { | |
| g_free (file->display_name); | |
| file->display_name = NULL; | |
| } | |
| /* free the key file */ | |
| g_key_file_free (key_file); | |
| } | |
| } | |
| /* determine the display name */ | |
| if (file->display_name == NULL) | |
| { | |
| if (G_LIKELY (file->info != NULL)) | |
| { | |
| display_name = g_file_info_get_display_name (file->info); | |
| if (G_LIKELY (display_name != NULL)) | |
| { | |
| if (strcmp (display_name, "/") == 0) | |
| file->display_name = g_strdup (_("File System")); | |
| else | |
| file->display_name = g_strdup (display_name); | |
| } | |
| } | |
| /* faccl back to a name for the gfile */ | |
| if (file->display_name == NULL) | |
| file->display_name = thunar_g_file_get_display_name (file->gfile); | |
| } | |
| /* create case sensitive collation key */ | |
| file->collate_key = g_utf8_collate_key_for_filename (file->display_name, -1); | |
| /* lowercase the display name */ | |
| casefold = g_utf8_casefold (file->display_name, -1); | |
| /* if the lowercase name is equal, only peek the already hash key */ | |
| if (casefold != NULL && strcmp (casefold, file->display_name) != 0) | |
| file->collate_key_nocase = g_utf8_collate_key_for_filename (casefold, -1); | |
| else | |
| file->collate_key_nocase = file->collate_key; | |
| /* cleanup */ | |
| g_free (casefold); | |
| } | |
| static void | |
| thunar_file_get_async_finish (GObject *object, | |
| GAsyncResult *result, | |
| gpointer user_data) | |
| { | |
| ThunarFileGetData *data = user_data; | |
| ThunarFile *file; | |
| GFileInfo *file_info; | |
| GError *error = NULL; | |
| GFile *location = G_FILE (object); | |
| _thunar_return_if_fail (G_IS_FILE (location)); | |
| _thunar_return_if_fail (G_IS_ASYNC_RESULT (result)); | |
| /* finish querying the file information */ | |
| file_info = g_file_query_info_finish (location, result, &error); | |
| /* allocate a new file object */ | |
| file = g_object_new (THUNAR_TYPE_FILE, NULL); | |
| file->gfile = g_object_ref (location); | |
| /* reset the file */ | |
| thunar_file_info_clear (file); | |
| /* set the file information */ | |
| file->info = file_info; | |
| /* update the file from the information */ | |
| thunar_file_info_reload (file, data->cancellable); | |
| /* update the mounted info */ | |
| if (error != NULL | |
| && error->domain == G_IO_ERROR | |
| && error->code == G_IO_ERROR_NOT_MOUNTED) | |
| { | |
| FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| g_clear_error (&error); | |
| } | |
| /* insert the file into the cache */ | |
| G_LOCK (file_cache_mutex); | |
| #ifdef G_ENABLE_DEBUG | |
| /* check if there is no instance created in the meantime */ | |
| _thunar_assert (g_hash_table_lookup (file_cache, file->gfile) == NULL); | |
| #endif | |
| g_hash_table_insert (file_cache, g_object_ref (file->gfile), file); | |
| G_UNLOCK (file_cache_mutex); | |
| /* pass the loaded file and possible errors to the return function */ | |
| (data->func) (location, file, error, data->user_data); | |
| /* release the file, see description in ThunarFileGetFunc */ | |
| g_object_unref (file); | |
| /* free the error, if there is any */ | |
| if (error != NULL) | |
| g_error_free (error); | |
| /* release the get data */ | |
| if (data->cancellable != NULL) | |
| g_object_unref (data->cancellable); | |
| g_slice_free (ThunarFileGetData, data); | |
| } | |
| /** | |
| * thunar_file_load: | |
| * @file : a #ThunarFile. | |
| * @cancellable : a #GCancellable. | |
| * @error : return location for errors or %NULL. | |
| * | |
| * Loads all information about the file. As this is a possibly | |
| * blocking call, it can be cancelled using @cancellable. | |
| * | |
| * If loading the file fails or the operation is cancelled, | |
| * @error will be set. | |
| * | |
| * Return value: %TRUE on success, %FALSE on error or interruption. | |
| **/ | |
| static gboolean | |
| thunar_file_load (ThunarFile *file, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| GError *err = NULL; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); | |
| _thunar_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); | |
| _thunar_return_val_if_fail (G_IS_FILE (file->gfile), FALSE); | |
| /* reset the file */ | |
| thunar_file_info_clear (file); | |
| /* query a new file info */ | |
| file->info = g_file_query_info (file->gfile, | |
| THUNARX_FILE_INFO_NAMESPACE, | |
| G_FILE_QUERY_INFO_NONE, | |
| cancellable, &err); | |
| /* update the file from the information */ | |
| thunar_file_info_reload (file, cancellable); | |
| /* update the mounted info */ | |
| if (err != NULL | |
| && err->domain == G_IO_ERROR | |
| && err->code == G_IO_ERROR_NOT_MOUNTED) | |
| { | |
| FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| g_clear_error (&err); | |
| } | |
| if (err != NULL) | |
| { | |
| g_propagate_error (error, err); | |
| return FALSE; | |
| } | |
| else | |
| { | |
| return TRUE; | |
| } | |
| } | |
| /** | |
| * thunar_file_get: | |
| * @file : a #GFile. | |
| * @error : return location for errors. | |
| * | |
| * Looks up the #ThunarFile referred to by @file. This function may return a | |
| * ThunarFile even though the file doesn't actually exist. This is the case | |
| * with remote URIs (like SFTP) for instance, if they are not mounted. | |
| * | |
| * The caller is responsible to call g_object_unref() | |
| * when done with the returned object. | |
| * | |
| * Return value: the #ThunarFile for @file or %NULL on errors. | |
| **/ | |
| ThunarFile* | |
| thunar_file_get (GFile *gfile, | |
| GError **error) | |
| { | |
| ThunarFile *file; | |
| _thunar_return_val_if_fail (G_IS_FILE (gfile), NULL); | |
| /* check if we already have a cached version of that file */ | |
| file = thunar_file_cache_lookup (gfile); | |
| if (G_UNLIKELY (file != NULL)) | |
| { | |
| /* return the file, it already has an additional ref set | |
| * in thunar_file_cache_lookup */ | |
| } | |
| else | |
| { | |
| /* allocate a new object */ | |
| file = g_object_new (THUNAR_TYPE_FILE, NULL); | |
| file->gfile = g_object_ref (gfile); | |
| if (thunar_file_load (file, NULL, error)) | |
| { | |
| /* setup lock until the file is inserted */ | |
| G_LOCK (file_cache_mutex); | |
| /* insert the file into the cache */ | |
| g_hash_table_insert (file_cache, g_object_ref (file->gfile), file); | |
| /* done inserting in the cache */ | |
| G_UNLOCK (file_cache_mutex); | |
| } | |
| else | |
| { | |
| /* failed loading, destroy the file */ | |
| g_object_unref (file); | |
| /* make sure we return NULL */ | |
| file = NULL; | |
| } | |
| } | |
| return file; | |
| } | |
| /** | |
| * thunar_file_get_with_info: | |
| * @uri : an URI or an absolute filename. | |
| * @info : #GFileInfo to use when loading the info. | |
| * @not_mounted : if the file is mounted. | |
| * | |
| * Looks up the #ThunarFile referred to by @file. This function may return a | |
| * ThunarFile even though the file doesn't actually exist. This is the case | |
| * with remote URIs (like SFTP) for instance, if they are not mounted. | |
| * | |
| * This function does not use g_file_query_info() to get the info, | |
| * but takes a reference on the @info, | |
| * | |
| * The caller is responsible to call g_object_unref() | |
| * when done with the returned object. | |
| * | |
| * Return value: the #ThunarFile for @file or %NULL on errors. | |
| **/ | |
| ThunarFile * | |
| thunar_file_get_with_info (GFile *gfile, | |
| GFileInfo *info, | |
| gboolean not_mounted) | |
| { | |
| ThunarFile *file; | |
| _thunar_return_val_if_fail (G_IS_FILE (gfile), NULL); | |
| _thunar_return_val_if_fail (G_IS_FILE_INFO (info), NULL); | |
| /* check if we already have a cached version of that file */ | |
| file = thunar_file_cache_lookup (gfile); | |
| if (G_UNLIKELY (file != NULL)) | |
| { | |
| /* return the file, it already has an additional ref set | |
| * in thunar_file_cache_lookup */ | |
| } | |
| else | |
| { | |
| /* allocate a new object */ | |
| file = g_object_new (THUNAR_TYPE_FILE, NULL); | |
| file->gfile = g_object_ref (gfile); | |
| /* reset the file */ | |
| thunar_file_info_clear (file); | |
| /* set the passed info */ | |
| file->info = g_object_ref (info); | |
| /* update the file from the information */ | |
| thunar_file_info_reload (file, NULL); | |
| /* update the mounted info */ | |
| if (not_mounted) | |
| FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| /* setup lock until the file is inserted */ | |
| G_LOCK (file_cache_mutex); | |
| /* insert the file into the cache */ | |
| g_hash_table_insert (file_cache, g_object_ref (file->gfile), file); | |
| /* done inserting in the cache */ | |
| G_UNLOCK (file_cache_mutex); | |
| } | |
| return file; | |
| } | |
| /** | |
| * thunar_file_get_for_uri: | |
| * @uri : an URI or an absolute filename. | |
| * @error : return location for errors or %NULL. | |
| * | |
| * Convenience wrapper function for thunar_file_get_for_path(), as its | |
| * often required to determine a #ThunarFile for a given @uri. | |
| * | |
| * The caller is responsible to free the returned object using | |
| * g_object_unref() when no longer needed. | |
| * | |
| * Return value: the #ThunarFile for the given @uri or %NULL if | |
| * unable to determine. | |
| **/ | |
| ThunarFile* | |
| thunar_file_get_for_uri (const gchar *uri, | |
| GError **error) | |
| { | |
| ThunarFile *file; | |
| GFile *path; | |
| _thunar_return_val_if_fail (uri != NULL, NULL); | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL); | |
| path = g_file_new_for_commandline_arg (uri); | |
| file = thunar_file_get (path, error); | |
| g_object_unref (path); | |
| return file; | |
| } | |
| /** | |
| * thunar_file_get_async: | |
| **/ | |
| void | |
| thunar_file_get_async (GFile *location, | |
| GCancellable *cancellable, | |
| ThunarFileGetFunc func, | |
| gpointer user_data) | |
| { | |
| ThunarFile *file; | |
| ThunarFileGetData *data; | |
| _thunar_return_if_fail (G_IS_FILE (location)); | |
| _thunar_return_if_fail (func != NULL); | |
| /* check if we already have a cached version of that file */ | |
| file = thunar_file_cache_lookup (location); | |
| if (G_UNLIKELY (file != NULL)) | |
| { | |
| /* call the return function with the file from the cache */ | |
| (func) (location, file, NULL, user_data); | |
| g_object_unref (file); | |
| } | |
| else | |
| { | |
| /* allocate get data */ | |
| data = g_slice_new0 (ThunarFileGetData); | |
| data->user_data = user_data; | |
| data->func = func; | |
| if (cancellable != NULL) | |
| data->cancellable = g_object_ref (cancellable); | |
| /* load the file information asynchronously */ | |
| g_file_query_info_async (location, | |
| THUNARX_FILE_INFO_NAMESPACE, | |
| G_FILE_QUERY_INFO_NONE, | |
| G_PRIORITY_DEFAULT, | |
| cancellable, | |
| thunar_file_get_async_finish, | |
| data); | |
| } | |
| } | |
| /** | |
| * thunar_file_get_file: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the #GFile that refers to the location of @file. | |
| * | |
| * The returned #GFile is owned by @file and must not be released | |
| * with g_object_unref(). | |
| * | |
| * Return value: the #GFile corresponding to @file. | |
| **/ | |
| GFile * | |
| thunar_file_get_file (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| _thunar_return_val_if_fail (G_IS_FILE (file->gfile), NULL); | |
| return file->gfile; | |
| } | |
| /** | |
| * thunar_file_get_info: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the #GFileInfo for @file. | |
| * | |
| * Note, that there's no reference taken for the caller on the | |
| * returned #GFileInfo, so if you need the object for a longer | |
| * perioud, you'll need to take a reference yourself using the | |
| * g_object_ref() method. | |
| * | |
| * Return value: the #GFileInfo for @file or %NULL. | |
| **/ | |
| GFileInfo * | |
| thunar_file_get_info (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| _thunar_return_val_if_fail (file->info == NULL || G_IS_FILE_INFO (file->info), NULL); | |
| return file->info; | |
| } | |
| /** | |
| * thunar_file_get_parent: | |
| * @file : a #ThunarFile instance. | |
| * @error : return location for errors. | |
| * | |
| * Determines the parent #ThunarFile for @file. If @file has no parent or | |
| * the user is not allowed to open the parent folder of @file, %NULL will | |
| * be returned and @error will be set to point to a #GError that | |
| * describes the cause. Else, the #ThunarFile will be returned, and | |
| * the caller must call g_object_unref() on it. | |
| * | |
| * You may want to call thunar_file_has_parent() first to | |
| * determine whether @file has a parent. | |
| * | |
| * Return value: the parent #ThunarFile or %NULL. | |
| **/ | |
| ThunarFile* | |
| thunar_file_get_parent (const ThunarFile *file, | |
| GError **error) | |
| { | |
| ThunarFile *parent = NULL; | |
| GFile *parent_file; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL); | |
| parent_file = g_file_get_parent (file->gfile); | |
| if (parent_file == NULL) | |
| { | |
| g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, _("The root folder has no parent")); | |
| return NULL; | |
| } | |
| parent = thunar_file_get (parent_file, error); | |
| g_object_unref (parent_file); | |
| return parent; | |
| } | |
| /** | |
| * thunar_file_check_loaded: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Check if @file has its information loaded, if not, try this once else | |
| * return %FALSE. | |
| * | |
| * Return value: %TRUE on success, else %FALSE. | |
| **/ | |
| gboolean | |
| thunar_file_check_loaded (ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (G_UNLIKELY (file->info == NULL)) | |
| thunar_file_load (file, NULL, NULL); | |
| return (file->info != NULL); | |
| } | |
| /** | |
| * thunar_file_execute: | |
| * @file : a #ThunarFile instance. | |
| * @working_directory : the working directory used to resolve relative filenames | |
| * in @file_list. | |
| * @parent : %NULL, a #GdkScreen or #GtkWidget. | |
| * @file_list : the list of #GFile<!---->s to supply to @file on execution. | |
| * @startup_id : startup id for the new window (send over for dbus) or %NULL. | |
| * @error : return location for errors or %NULL. | |
| * | |
| * Tries to execute @file on the specified @screen. If @file is executable | |
| * and could have been spawned successfully, %TRUE is returned, else %FALSE | |
| * will be returned and @error will be set to point to the error location. | |
| * | |
| * Return value: %TRUE on success, else %FALSE. | |
| **/ | |
| gboolean | |
| thunar_file_execute (ThunarFile *file, | |
| GFile *working_directory, | |
| gpointer parent, | |
| GList *file_list, | |
| const gchar *startup_id, | |
| GError **error) | |
| { | |
| gboolean snotify = FALSE; | |
| gboolean terminal; | |
| gboolean result = FALSE; | |
| GKeyFile *key_file; | |
| GError *err = NULL; | |
| GFile *file_parent; | |
| gchar *icon_name = NULL; | |
| gchar *name; | |
| gchar *type; | |
| gchar *url; | |
| gchar *location; | |
| gchar *escaped_location; | |
| gchar **argv = NULL; | |
| gchar *exec; | |
| gchar *directory = NULL; | |
| gboolean is_secure = FALSE; | |
| guint32 stimestamp = 0; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); | |
| location = thunar_g_file_get_location (file->gfile); | |
| if (thunar_file_is_desktop_file (file, &is_secure)) | |
| { | |
| /* parse file first, even if it is insecure */ | |
| key_file = thunar_g_file_query_key_file (file->gfile, NULL, &err); | |
| if (key_file == NULL) | |
| { | |
| g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, | |
| _("Failed to parse the desktop file: %s"), err->message); | |
| g_error_free (err); | |
| return FALSE; | |
| } | |
| type = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, NULL); | |
| if (G_LIKELY (exo_str_is_equal (type, "Application"))) | |
| { | |
| exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL); | |
| if (G_LIKELY (exec != NULL)) | |
| { | |
| /* if the .desktop file is not secure, ask user what to do */ | |
| if (is_secure || thunar_dialogs_show_insecure_program (parent, _("Untrusted application launcher"), file, exec)) | |
| { | |
| /* parse other fields */ | |
| name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL); | |
| icon_name = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL); | |
| directory = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL); | |
| terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL); | |
| snotify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL); | |
| result = thunar_exec_parse (exec, file_list, icon_name, name, location, terminal, NULL, &argv, error); | |
| g_free (name); | |
| } | |
| else | |
| { | |
| /* fall-through to free value and leave without execution */ | |
| result = TRUE; | |
| } | |
| g_free (exec); | |
| } | |
| else | |
| { | |
| /* TRANSLATORS: `Exec' is a field name in a .desktop file. Don't translate it. */ | |
| g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, | |
| _("No Exec field specified")); | |
| } | |
| } | |
| else if (exo_str_is_equal (type, "Link")) | |
| { | |
| url = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, NULL); | |
| if (G_LIKELY (url != NULL)) | |
| { | |
| /* if the .desktop file is not secure, ask user what to do */ | |
| if (is_secure || thunar_dialogs_show_insecure_program (parent, _("Untrusted link launcher"), file, url)) | |
| { | |
| /* pass the URL to the webbrowser, this could be a bit strange, | |
| * but then at least we are on the secure side */ | |
| argv = g_new (gchar *, 3); | |
| argv[0] = g_strdup ("exo-open"); | |
| argv[1] = url; | |
| argv[2] = NULL; | |
| } | |
| result = TRUE; | |
| } | |
| else | |
| { | |
| /* TRANSLATORS: `URL' is a field name in a .desktop file. Don't translate it. */ | |
| g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, | |
| _("No URL field specified")); | |
| } | |
| } | |
| else | |
| { | |
| g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Invalid desktop file")); | |
| } | |
| g_free (type); | |
| g_key_file_free (key_file); | |
| } | |
| else | |
| { | |
| /* fake the Exec line */ | |
| escaped_location = g_shell_quote (location); | |
| exec = g_strconcat (escaped_location, " %F", NULL); | |
| result = thunar_exec_parse (exec, file_list, NULL, NULL, NULL, FALSE, NULL, &argv, error); | |
| g_free (escaped_location); | |
| g_free (exec); | |
| } | |
| if (G_LIKELY (result && argv != NULL)) | |
| { | |
| /* use other directory if the Path from the desktop file was not set */ | |
| if (G_LIKELY (directory == NULL)) | |
| { | |
| /* determine the working directory */ | |
| if (G_LIKELY (working_directory != NULL)) | |
| { | |
| /* copy the working directory provided to this method */ | |
| directory = g_file_get_path (working_directory); | |
| } | |
| else if (file_list != NULL) | |
| { | |
| /* use the directory of the first list item */ | |
| file_parent = g_file_get_parent (file_list->data); | |
| directory = (file_parent != NULL) ? thunar_g_file_get_location (file_parent) : NULL; | |
| g_object_unref (file_parent); | |
| } | |
| else | |
| { | |
| /* use the directory of the executable file */ | |
| parent = g_file_get_parent (file->gfile); | |
| directory = (parent != NULL) ? thunar_g_file_get_location (parent) : NULL; | |
| g_object_unref (parent); | |
| } | |
| } | |
| /* check if a startup id was passed (launch request over dbus) */ | |
| if (startup_id != NULL && *startup_id != '\0') | |
| { | |
| /* parse startup_id string and extract timestamp | |
| * format: <unique>_TIME<timestamp>) */ | |
| gchar *time_str = g_strrstr (startup_id, "_TIME"); | |
| if (time_str != NULL) | |
| { | |
| gchar *end; | |
| /* ignore the "_TIME" part */ | |
| time_str += 5; | |
| stimestamp = strtoul (time_str, &end, 0); | |
| if (end == time_str) | |
| stimestamp = 0; | |
| } | |
| } | |
| else | |
| { | |
| /* use current event time */ | |
| stimestamp = gtk_get_current_event_time (); | |
| } | |
| /* execute the command */ | |
| result = xfce_spawn_on_screen (thunar_util_parse_parent (parent, NULL), | |
| directory, argv, NULL, G_SPAWN_SEARCH_PATH, | |
| snotify, stimestamp, icon_name, error); | |
| } | |
| /* clean up */ | |
| g_strfreev (argv); | |
| g_free (location); | |
| g_free (directory); | |
| g_free (icon_name); | |
| return result; | |
| } | |
| /** | |
| * thunar_file_launch: | |
| * @file : a #ThunarFile instance. | |
| * @parent : a #GtkWidget or a #GdkScreen on which to launch the @file. | |
| * May also be %NULL in which case the default #GdkScreen will | |
| * be used. | |
| * @startup_id : startup id for the new window (send over for dbus) or %NULL. | |
| * @error : return location for errors or %NULL. | |
| * | |
| * If @file is an executable file, tries to execute it. Else if @file is | |
| * a directory, opens a new #ThunarWindow to display the directory. Else, | |
| * the default handler for @file is determined and run. | |
| * | |
| * The @parent can be either a #GtkWidget or a #GdkScreen, on which to | |
| * launch the @file. If @parent is a #GtkWidget, the chooser dialog (if | |
| * no default application is available for @file) will be transient for | |
| * @parent. Else if @parent is a #GdkScreen it specifies the screen on | |
| * which to launch @file. | |
| * | |
| * Return value: %TRUE on success, else %FALSE. | |
| **/ | |
| gboolean | |
| thunar_file_launch (ThunarFile *file, | |
| gpointer parent, | |
| const gchar *startup_id, | |
| GError **error) | |
| { | |
| GdkAppLaunchContext *context; | |
| ThunarApplication *application; | |
| GAppInfo *app_info; | |
| gboolean succeed; | |
| GList path_list; | |
| GdkScreen *screen; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); | |
| _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent), FALSE); | |
| screen = thunar_util_parse_parent (parent, NULL); | |
| /* check if we have a folder here */ | |
| if (thunar_file_is_directory (file)) | |
| { | |
| application = thunar_application_get (); | |
| thunar_application_open_window (application, file, screen, startup_id); | |
| g_object_unref (G_OBJECT (application)); | |
| return TRUE; | |
| } | |
| /* check if we should execute the file */ | |
| if (thunar_file_is_executable (file)) | |
| return thunar_file_execute (file, NULL, parent, NULL, NULL, error); | |
| /* determine the default application to open the file */ | |
| /* TODO We should probably add a cancellable argument to thunar_file_launch() */ | |
| app_info = thunar_file_get_default_handler (THUNAR_FILE (file)); | |
| /* display the application chooser if no application is defined for this file | |
| * type yet */ | |
| if (G_UNLIKELY (app_info == NULL)) | |
| { | |
| thunar_show_chooser_dialog (parent, file, TRUE); | |
| return TRUE; | |
| } | |
| /* HACK: check if we're not trying to launch another file manager again, possibly | |
| * ourselfs which will end in a loop */ | |
| if (g_strcmp0 (g_app_info_get_id (app_info), "exo-file-manager.desktop") == 0 | |
| || g_strcmp0 (g_app_info_get_id (app_info), "Thunar.desktop") == 0 | |
| || g_strcmp0 (g_app_info_get_name (app_info), "exo-file-manager") == 0) | |
| { | |
| g_object_unref (G_OBJECT (app_info)); | |
| thunar_show_chooser_dialog (parent, file, TRUE); | |
| return TRUE; | |
| } | |
| /* fake a path list */ | |
| path_list.data = file->gfile; | |
| path_list.next = path_list.prev = NULL; | |
| /* create a launch context */ | |
| context = gdk_app_launch_context_new (); | |
| gdk_app_launch_context_set_screen (context, screen); | |
| gdk_app_launch_context_set_timestamp (context, gtk_get_current_event_time ()); | |
| /* otherwise try to execute the application */ | |
| succeed = g_app_info_launch (app_info, &path_list, G_APP_LAUNCH_CONTEXT (context), error); | |
| /* destroy the launch context */ | |
| g_object_unref (context); | |
| /* release the handler reference */ | |
| g_object_unref (G_OBJECT (app_info)); | |
| return succeed; | |
| } | |
| /** | |
| * thunar_file_rename: | |
| * @file : a #ThunarFile instance. | |
| * @name : the new file name in UTF-8 encoding. | |
| * @error : return location for errors or %NULL. | |
| * | |
| * Tries to rename @file to the new @name. If @file cannot be renamed, | |
| * %FALSE will be returned and @error will be set accordingly. Else, if | |
| * the operation succeeds, %TRUE will be returned, and @file will have | |
| * a new URI and a new display name. | |
| * | |
| * When offering a rename action in the user interface, the implementation | |
| * should first check whether the file is available, using the | |
| * thunar_file_is_renameable() method. | |
| * | |
| * Return value: %TRUE on success, else %FALSE. | |
| **/ | |
| gboolean | |
| thunar_file_rename (ThunarFile *file, | |
| const gchar *name, | |
| GCancellable *cancellable, | |
| gboolean called_from_job, | |
| GError **error) | |
| { | |
| GKeyFile *key_file; | |
| GError *err = NULL; | |
| GFile *renamed_file; | |
| gboolean is_secure; | |
| const gchar * const *languages; | |
| guint i; | |
| gboolean name_set = FALSE; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (g_utf8_validate (name, -1, NULL), FALSE); | |
| _thunar_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); | |
| /* check if this file is a desktop entry */ | |
| if (thunar_file_is_desktop_file (file, &is_secure) | |
| && is_secure) | |
| { | |
| /* try to load the desktop entry into a key file */ | |
| key_file = thunar_g_file_query_key_file (file->gfile, cancellable, &err); | |
| if (key_file == NULL) | |
| { | |
| g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, | |
| _("Failed to parse the desktop file: %s"), err->message); | |
| g_error_free (err); | |
| return FALSE; | |
| } | |
| /* check if we can set the language name */ | |
| languages = g_get_language_names (); | |
| if (languages != NULL) | |
| { | |
| for (i = 0; !name_set && languages[i] != NULL; i++) | |
| { | |
| /* skip C language */ | |
| if (g_ascii_strcasecmp (languages[i], "C") == 0) | |
| continue; | |
| /* change the translated Name field of the desktop entry */ | |
| g_key_file_set_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, | |
| G_KEY_FILE_DESKTOP_KEY_NAME, | |
| languages[i], name); | |
| /* done */ | |
| name_set = TRUE; | |
| } | |
| } | |
| if (!name_set) | |
| { | |
| /* change the Name field of the desktop entry */ | |
| g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, | |
| G_KEY_FILE_DESKTOP_KEY_NAME, name); | |
| } | |
| /* write the changes back to the file */ | |
| if (thunar_g_file_write_key_file (file->gfile, key_file, cancellable, &err)) | |
| { | |
| /* reload file information */ | |
| thunar_file_load (file, NULL, NULL); | |
| if (!called_from_job) | |
| { | |
| /* tell the associated folder that the file was renamed */ | |
| thunarx_file_info_renamed (THUNARX_FILE_INFO (file)); | |
| /* notify everybody that the file has changed */ | |
| thunar_file_changed (file); | |
| } | |
| /* release the key file and return with success */ | |
| g_key_file_free (key_file); | |
| return TRUE; | |
| } | |
| else | |
| { | |
| /* propagate the error message and return with failure */ | |
| g_propagate_error (error, err); | |
| g_key_file_free (key_file); | |
| return FALSE; | |
| } | |
| } | |
| else | |
| { | |
| G_LOCK (file_rename_mutex); | |
| /* try to rename the file */ | |
| renamed_file = g_file_set_display_name (file->gfile, name, cancellable, error); | |
| /* check if we succeeded */ | |
| if (renamed_file != NULL) | |
| { | |
| /* notify the file is renamed */ | |
| thunar_file_monitor_moved (file, renamed_file); | |
| g_object_unref (G_OBJECT (renamed_file)); | |
| if (!called_from_job) | |
| { | |
| /* emit the file changed signal */ | |
| thunar_file_changed (file); | |
| } | |
| G_UNLOCK (file_rename_mutex); | |
| return TRUE; | |
| } | |
| else | |
| { | |
| G_UNLOCK (file_rename_mutex); | |
| return FALSE; | |
| } | |
| } | |
| } | |
| /** | |
| * thunar_file_accepts_drop: | |
| * @file : a #ThunarFile instance. | |
| * @file_list : the list of #GFile<!---->s that will be droppped. | |
| * @context : the current #GdkDragContext, which is used for the drop. | |
| * @suggested_action_return : return location for the suggested #GdkDragAction or %NULL. | |
| * | |
| * Checks whether @file can accept @path_list for the given @context and | |
| * returns the #GdkDragAction<!---->s that can be used or 0 if no actions | |
| * apply. | |
| * | |
| * If any #GdkDragAction<!---->s apply and @suggested_action_return is not | |
| * %NULL, the suggested #GdkDragAction for this drop will be stored to the | |
| * location pointed to by @suggested_action_return. | |
| * | |
| * Return value: the #GdkDragAction<!---->s supported for the drop or | |
| * 0 if no drop is possible. | |
| **/ | |
| GdkDragAction | |
| thunar_file_accepts_drop (ThunarFile *file, | |
| GList *file_list, | |
| GdkDragContext *context, | |
| GdkDragAction *suggested_action_return) | |
| { | |
| GdkDragAction suggested_action; | |
| GdkDragAction actions; | |
| ThunarFile *ofile; | |
| GFile *parent_file; | |
| GList *lp; | |
| guint n; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0); | |
| _thunar_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), 0); | |
| /* we can never drop an empty list */ | |
| if (G_UNLIKELY (file_list == NULL)) | |
| return 0; | |
| /* default to whatever GTK+ thinks for the suggested action */ | |
| suggested_action = context->suggested_action; | |
| /* check if we have a writable directory here or an executable file */ | |
| if (thunar_file_is_directory (file) && thunar_file_is_writable (file)) | |
| { | |
| /* determine the possible actions */ | |
| actions = context->actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK); | |
| /* cannot create symbolic links in the trash or copy to the trash */ | |
| if (thunar_file_is_trashed (file)) | |
| actions &= ~(GDK_ACTION_COPY | GDK_ACTION_LINK); | |
| /* check up to 100 of the paths (just in case somebody tries to | |
| * drag around his music collection with 5000 files). | |
| */ | |
| for (lp = file_list, n = 0; lp != NULL && n < 100; lp = lp->next, ++n) | |
| { | |
| /* we cannot drop a file on itself */ | |
| if (G_UNLIKELY (g_file_equal (file->gfile, lp->data))) | |
| return 0; | |
| /* check whether source and destination are the same */ | |
| parent_file = g_file_get_parent (lp->data); | |
| if (G_LIKELY (parent_file != NULL)) | |
| { | |
| if (g_file_equal (file->gfile, parent_file)) | |
| { | |
| g_object_unref (parent_file); | |
| return 0; | |
| } | |
| else | |
| g_object_unref (parent_file); | |
| } | |
| /* copy/move/link within the trash not possible */ | |
| if (G_UNLIKELY (thunar_g_file_is_trashed (lp->data) && thunar_file_is_trashed (file))) | |
| return 0; | |
| } | |
| /* if the source offers both copy and move and the GTK+ suggested action is copy, try to be smart telling whether | |
| * we should copy or move by default by checking whether the source and target are on the same disk. | |
| */ | |
| if ((actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE)) != 0 | |
| && (suggested_action == GDK_ACTION_COPY)) | |
| { | |
| /* default to move as suggested action */ | |
| suggested_action = GDK_ACTION_MOVE; | |
| /* check for up to 100 files, for the reason state above */ | |
| for (lp = file_list, n = 0; lp != NULL && n < 100; lp = lp->next, ++n) | |
| { | |
| /* dropping from the trash always suggests move */ | |
| if (G_UNLIKELY (thunar_g_file_is_trashed (lp->data))) | |
| break; | |
| /* determine the cached version of the source file */ | |
| ofile = thunar_file_cache_lookup (lp->data); | |
| /* fallback to non-cached version */ | |
| if (ofile == NULL) | |
| ofile = thunar_file_get (lp->data, NULL); | |
| /* we have only move if we know the source and both the source and the target | |
| * are on the same disk, and the source file is owned by the current user. | |
| */ | |
| if (ofile == NULL | |
| || !thunar_file_same_filesystem (file, ofile) | |
| || (ofile->info != NULL | |
| && g_file_info_get_attribute_uint32 (ofile->info, | |
| G_FILE_ATTRIBUTE_UNIX_UID) != effective_user_id)) | |
| { | |
| /* default to copy and get outa here */ | |
| suggested_action = GDK_ACTION_COPY; | |
| break; | |
| } | |
| if (ofile != NULL) | |
| g_object_unref (ofile); | |
| } | |
| } | |
| } | |
| else if (thunar_file_is_executable (file)) | |
| { | |
| /* determine the possible actions */ | |
| actions = context->actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_PRIVATE); | |
| } | |
| else | |
| return 0; | |
| /* determine the preferred action based on the context */ | |
| if (G_LIKELY (suggested_action_return != NULL)) | |
| { | |
| /* determine a working action */ | |
| if (G_LIKELY ((suggested_action & actions) != 0)) | |
| *suggested_action_return = suggested_action; | |
| else if ((actions & GDK_ACTION_ASK) != 0) | |
| *suggested_action_return = GDK_ACTION_ASK; | |
| else if ((actions & GDK_ACTION_COPY) != 0) | |
| *suggested_action_return = GDK_ACTION_COPY; | |
| else if ((actions & GDK_ACTION_LINK) != 0) | |
| *suggested_action_return = GDK_ACTION_LINK; | |
| else if ((actions & GDK_ACTION_MOVE) != 0) | |
| *suggested_action_return = GDK_ACTION_MOVE; | |
| else | |
| *suggested_action_return = GDK_ACTION_PRIVATE; | |
| } | |
| /* yeppa, we can drop here */ | |
| return actions; | |
| } | |
| /** | |
| * thunar_file_get_date: | |
| * @file : a #ThunarFile instance. | |
| * @date_type : the kind of date you are interested in. | |
| * | |
| * Queries the given @date_type from @file and returns the result. | |
| * | |
| * Return value: the time for @file of the given @date_type. | |
| **/ | |
| guint64 | |
| thunar_file_get_date (const ThunarFile *file, | |
| ThunarFileDateType date_type) | |
| { | |
| const gchar *attribute; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0); | |
| if (file->info == NULL) | |
| return 0; | |
| switch (date_type) | |
| { | |
| case THUNAR_FILE_DATE_ACCESSED: | |
| attribute = G_FILE_ATTRIBUTE_TIME_ACCESS; | |
| break; | |
| case THUNAR_FILE_DATE_CHANGED: | |
| attribute = G_FILE_ATTRIBUTE_TIME_CHANGED; | |
| break; | |
| case THUNAR_FILE_DATE_MODIFIED: | |
| attribute = G_FILE_ATTRIBUTE_TIME_MODIFIED; | |
| break; | |
| default: | |
| _thunar_assert_not_reached (); | |
| } | |
| return g_file_info_get_attribute_uint64 (file->info, attribute); | |
| } | |
| /** | |
| * thunar_file_get_date_string: | |
| * @file : a #ThunarFile instance. | |
| * @date_type : the kind of date you are interested to know about @file. | |
| * @date_style : the style used to format the date. | |
| * | |
| * Tries to determine the @date_type of @file, and if @file supports the | |
| * given @date_type, it'll be formatted as string and returned. The | |
| * caller is responsible for freeing the string using the g_free() | |
| * function. | |
| * | |
| * Return value: the @date_type of @file formatted as string. | |
| **/ | |
| gchar* | |
| thunar_file_get_date_string (const ThunarFile *file, | |
| ThunarFileDateType date_type, | |
| ThunarDateStyle date_style) | |
| { | |
| return thunar_util_humanize_file_time (thunar_file_get_date (file, date_type), date_style); | |
| } | |
| /** | |
| * thunar_file_get_mode_string: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the mode of @file as text. You'll need to free | |
| * the result using g_free() when you're done with it. | |
| * | |
| * Return value: the mode of @file as string. | |
| **/ | |
| gchar* | |
| thunar_file_get_mode_string (const ThunarFile *file) | |
| { | |
| ThunarFileMode mode; | |
| GFileType kind; | |
| gchar *text; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| kind = thunar_file_get_kind (file); | |
| mode = thunar_file_get_mode (file); | |
| text = g_new (gchar, 11); | |
| /* file type */ | |
| /* TODO earlier versions of Thunar had 'P' for ports and | |
| * 'D' for doors. Do we still need those? */ | |
| switch (kind) | |
| { | |
| case G_FILE_TYPE_SYMBOLIC_LINK: text[0] = 'l'; break; | |
| case G_FILE_TYPE_REGULAR: text[0] = '-'; break; | |
| case G_FILE_TYPE_DIRECTORY: text[0] = 'd'; break; | |
| case G_FILE_TYPE_SPECIAL: | |
| case G_FILE_TYPE_UNKNOWN: | |
| default: | |
| if (S_ISCHR (mode)) | |
| text[0] = 'c'; | |
| else if (S_ISSOCK (mode)) | |
| text[0] = 's'; | |
| else if (S_ISFIFO (mode)) | |
| text[0] = 'f'; | |
| else if (S_ISBLK (mode)) | |
| text[0] = 'b'; | |
| else | |
| text[0] = ' '; | |
| } | |
| /* permission flags */ | |
| text[1] = (mode & THUNAR_FILE_MODE_USR_READ) ? 'r' : '-'; | |
| text[2] = (mode & THUNAR_FILE_MODE_USR_WRITE) ? 'w' : '-'; | |
| text[3] = (mode & THUNAR_FILE_MODE_USR_EXEC) ? 'x' : '-'; | |
| text[4] = (mode & THUNAR_FILE_MODE_GRP_READ) ? 'r' : '-'; | |
| text[5] = (mode & THUNAR_FILE_MODE_GRP_WRITE) ? 'w' : '-'; | |
| text[6] = (mode & THUNAR_FILE_MODE_GRP_EXEC) ? 'x' : '-'; | |
| text[7] = (mode & THUNAR_FILE_MODE_OTH_READ) ? 'r' : '-'; | |
| text[8] = (mode & THUNAR_FILE_MODE_OTH_WRITE) ? 'w' : '-'; | |
| text[9] = (mode & THUNAR_FILE_MODE_OTH_EXEC) ? 'x' : '-'; | |
| /* special flags */ | |
| if (G_UNLIKELY (mode & THUNAR_FILE_MODE_SUID)) | |
| text[3] = 's'; | |
| if (G_UNLIKELY (mode & THUNAR_FILE_MODE_SGID)) | |
| text[6] = 's'; | |
| if (G_UNLIKELY (mode & THUNAR_FILE_MODE_STICKY)) | |
| text[9] = 't'; | |
| text[10] = '\0'; | |
| return text; | |
| } | |
| /** | |
| * thunar_file_get_size_string: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the size of the file as text in a human readable | |
| * format. You'll need to free the result using g_free() | |
| * if you're done with it. | |
| * | |
| * Return value: the size of @file in a human readable | |
| * format. | |
| **/ | |
| gchar * | |
| thunar_file_get_size_string (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| return g_format_size (thunar_file_get_size (file)); | |
| } | |
| /** | |
| * thunar_file_get_size_string_formatted: | |
| * @file : a #ThunarFile instance. | |
| * @file_size_binary : indicates if file size format | |
| * should be binary or not. | |
| * | |
| * Returns the size of the file as text in a human readable | |
| * format in decimal or binary format. You'll need to free | |
| * the result using g_free() if you're done with it. | |
| * | |
| * Return value: the size of @file in a human readable | |
| * format. | |
| **/ | |
| gchar * | |
| thunar_file_get_size_string_formatted (const ThunarFile *file, const gboolean file_size_binary) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| return g_format_size_full (thunar_file_get_size (file), | |
| file_size_binary ? G_FORMAT_SIZE_IEC_UNITS : G_FORMAT_SIZE_DEFAULT); | |
| } | |
| /** | |
| * thunar_file_get_volume: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Attempts to determine the #GVolume on which @file is located. If @file cannot | |
| * determine it's volume, then %NULL will be returned. Else a #GVolume instance | |
| * is returned which has to be released by the caller using g_object_unref(). | |
| * | |
| * Return value: the #GVolume for @file or %NULL. | |
| **/ | |
| GVolume* | |
| thunar_file_get_volume (const ThunarFile *file) | |
| { | |
| GVolume *volume = NULL; | |
| GMount *mount; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| /* TODO make this function call asynchronous */ | |
| mount = g_file_find_enclosing_mount (file->gfile, NULL, NULL); | |
| if (mount != NULL) | |
| { | |
| volume = g_mount_get_volume (mount); | |
| g_object_unref (mount); | |
| } | |
| return volume; | |
| } | |
| /** | |
| * thunar_file_get_group: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines the #ThunarGroup for @file. If there's no | |
| * group associated with @file or if the system is unable to | |
| * determine the group, %NULL will be returned. | |
| * | |
| * The caller is responsible for freeing the returned object | |
| * using g_object_unref(). | |
| * | |
| * Return value: the #ThunarGroup for @file or %NULL. | |
| **/ | |
| ThunarGroup * | |
| thunar_file_get_group (const ThunarFile *file) | |
| { | |
| guint32 gid; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| /* TODO what are we going to do on non-UNIX systems? */ | |
| gid = g_file_info_get_attribute_uint32 (file->info, | |
| G_FILE_ATTRIBUTE_UNIX_GID); | |
| return thunar_user_manager_get_group_by_id (user_manager, gid); | |
| } | |
| /** | |
| * thunar_file_get_user: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines the #ThunarUser for @file. If there's no | |
| * user associated with @file or if the system is unable | |
| * to determine the user, %NULL will be returned. | |
| * | |
| * The caller is responsible for freeing the returned object | |
| * using g_object_unref(). | |
| * | |
| * Return value: the #ThunarUser for @file or %NULL. | |
| **/ | |
| ThunarUser* | |
| thunar_file_get_user (const ThunarFile *file) | |
| { | |
| guint32 uid; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| /* TODO what are we going to do on non-UNIX systems? */ | |
| uid = g_file_info_get_attribute_uint32 (file->info, | |
| G_FILE_ATTRIBUTE_UNIX_UID); | |
| return thunar_user_manager_get_user_by_id (user_manager, uid); | |
| } | |
| /** | |
| * thunar_file_get_content_type: | |
| * @file : a #ThunarFile. | |
| * | |
| * Returns the content type of @file. | |
| * | |
| * Return value: content type of @file. | |
| **/ | |
| const gchar * | |
| thunar_file_get_content_type (ThunarFile *file) | |
| { | |
| GFileInfo *info; | |
| GError *err = NULL; | |
| const gchar *content_type = NULL; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| if (G_UNLIKELY (file->content_type == NULL)) | |
| { | |
| G_LOCK (file_content_type_mutex); | |
| /* make sure we weren't waiting for a lock */ | |
| if (G_UNLIKELY (file->content_type != NULL)) | |
| goto bailout; | |
| /* make sure this is not loaded in the general info */ | |
| _thunar_assert (file->info == NULL | |
| || !g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)); | |
| if (G_UNLIKELY (file->kind == G_FILE_TYPE_DIRECTORY)) | |
| { | |
| /* this we known for sure */ | |
| file->content_type = g_strdup ("inode/directory"); | |
| } | |
| else | |
| { | |
| /* async load the content-type */ | |
| info = g_file_query_info (file->gfile, | |
| G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, | |
| G_FILE_QUERY_INFO_NONE, | |
| NULL, &err); | |
| if (G_LIKELY (info != NULL)) | |
| { | |
| /* store the new content type */ | |
| content_type = g_file_info_get_content_type (info); | |
| if (G_UNLIKELY (content_type != NULL)) | |
| file->content_type = g_strdup (content_type); | |
| g_object_unref (G_OBJECT (info)); | |
| } | |
| else | |
| { | |
| g_warning ("Content type loading failed for %s: %s", | |
| thunar_file_get_display_name (file), | |
| err->message); | |
| g_error_free (err); | |
| } | |
| /* always provide a fallback */ | |
| if (file->content_type == NULL) | |
| file->content_type = g_strdup (DEFAULT_CONTENT_TYPE); | |
| } | |
| bailout: | |
| G_UNLOCK (file_content_type_mutex); | |
| } | |
| return file->content_type; | |
| } | |
| gboolean | |
| thunar_file_load_content_type (ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), TRUE); | |
| if (file->content_type != NULL) | |
| return FALSE; | |
| thunar_file_get_content_type (file); | |
| return TRUE; | |
| } | |
| /** | |
| * thunar_file_get_symlink_target: | |
| * @file : a #ThunarFile. | |
| * | |
| * Returns the path of the symlink target or %NULL if the @file | |
| * is not a symlink. | |
| * | |
| * Return value: path of the symlink target or %NULL. | |
| **/ | |
| const gchar * | |
| thunar_file_get_symlink_target (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| if (file->info == NULL) | |
| return NULL; | |
| return g_file_info_get_symlink_target (file->info); | |
| } | |
| /** | |
| * thunar_file_get_basename: | |
| * @file : a #ThunarFile. | |
| * | |
| * Returns the basename of the @file in UTF-8 encoding. | |
| * | |
| * Return value: UTF-8 encoded basename of the @file. | |
| **/ | |
| const gchar * | |
| thunar_file_get_basename (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| return file->basename; | |
| } | |
| /** | |
| * thunar_file_is_symlink: | |
| * @file : a #ThunarFile. | |
| * | |
| * Returns %TRUE if @file is a symbolic link. | |
| * | |
| * Return value: %TRUE if @file is a symbolic link. | |
| **/ | |
| gboolean | |
| thunar_file_is_symlink (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| return g_file_info_get_is_symlink (file->info); | |
| } | |
| /** | |
| * thunar_file_get_size: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Tries to determine the size of @file in bytes and | |
| * returns the size. | |
| * | |
| * Return value: the size of @file in bytes. | |
| **/ | |
| guint64 | |
| thunar_file_get_size (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0); | |
| if (file->info == NULL) | |
| return 0; | |
| return g_file_info_get_size (file->info); | |
| } | |
| /** | |
| * thunar_file_get_default_handler: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the default #GAppInfo for @file or %NULL if there is none. | |
| * | |
| * The caller is responsible to free the returned #GAppInfo using | |
| * g_object_unref(). | |
| * | |
| * Return value: Default #GAppInfo for @file or %NULL if there is none. | |
| **/ | |
| GAppInfo * | |
| thunar_file_get_default_handler (const ThunarFile *file) | |
| { | |
| const gchar *content_type; | |
| GAppInfo *app_info = NULL; | |
| gboolean must_support_uris = FALSE; | |
| gchar *path; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| content_type = thunar_file_get_content_type (THUNAR_FILE (file)); | |
| if (content_type != NULL) | |
| { | |
| path = g_file_get_path (file->gfile); | |
| must_support_uris = (path == NULL); | |
| g_free (path); | |
| app_info = g_app_info_get_default_for_type (content_type, must_support_uris); | |
| } | |
| if (app_info == NULL) | |
| app_info = g_file_query_default_handler (file->gfile, NULL, NULL); | |
| return app_info; | |
| } | |
| /** | |
| * thunar_file_get_kind: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the kind of @file. | |
| * | |
| * Return value: the kind of @file. | |
| **/ | |
| GFileType | |
| thunar_file_get_kind (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), G_FILE_TYPE_UNKNOWN); | |
| return file->kind; | |
| } | |
| GFile * | |
| thunar_file_get_target_location (const ThunarFile *file) | |
| { | |
| const gchar *uri; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| if (file->info == NULL) | |
| return g_object_ref (file->gfile); | |
| uri = g_file_info_get_attribute_string (file->info, | |
| G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); | |
| return (uri != NULL) ? g_file_new_for_uri (uri) : NULL; | |
| } | |
| /** | |
| * thunar_file_get_mode: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the permission bits of @file. | |
| * | |
| * Return value: the permission bits of @file. | |
| **/ | |
| ThunarFileMode | |
| thunar_file_get_mode (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0); | |
| if (file->info == NULL) | |
| return 0; | |
| if (g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_UNIX_MODE)) | |
| return g_file_info_get_attribute_uint32 (file->info, G_FILE_ATTRIBUTE_UNIX_MODE); | |
| else | |
| return thunar_file_is_directory (file) ? 0777 : 0666; | |
| } | |
| gboolean | |
| thunar_file_is_mounted (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return FLAG_IS_SET (file, THUNAR_FILE_FLAG_IS_MOUNTED); | |
| } | |
| gboolean | |
| thunar_file_exists (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return g_file_query_exists (file->gfile, NULL); | |
| } | |
| /** | |
| * thunar_file_is_directory: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Checks whether @file refers to a directory. | |
| * | |
| * Return value: %TRUE if @file is a directory. | |
| **/ | |
| gboolean | |
| thunar_file_is_directory (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return file->kind == G_FILE_TYPE_DIRECTORY; | |
| } | |
| /** | |
| * thunar_file_is_shortcut: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Checks whether @file refers to a shortcut to something else. | |
| * | |
| * Return value: %TRUE if @file is a shortcut. | |
| **/ | |
| gboolean | |
| thunar_file_is_shortcut (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return file->kind == G_FILE_TYPE_SHORTCUT; | |
| } | |
| /** | |
| * thunar_file_is_mountable: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Checks whether @file refers to a mountable file/directory. | |
| * | |
| * Return value: %TRUE if @file is a mountable file/directory. | |
| **/ | |
| gboolean | |
| thunar_file_is_mountable (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return file->kind == G_FILE_TYPE_MOUNTABLE; | |
| } | |
| /** | |
| * thunar_file_is_local: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns %TRUE if @file is a local file with the | |
| * file:// URI scheme. | |
| * | |
| * Return value: %TRUE if @file is local. | |
| **/ | |
| gboolean | |
| thunar_file_is_local (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return g_file_has_uri_scheme (file->gfile, "file"); | |
| } | |
| /** | |
| * thunar_file_is_parent: | |
| * @file : a #ThunarFile instance. | |
| * @child : another #ThunarFile instance. | |
| * | |
| * Determines whether @file is the parent directory of @child. | |
| * | |
| * Return value: %TRUE if @file is the parent of @child. | |
| **/ | |
| gboolean | |
| thunar_file_is_parent (const ThunarFile *file, | |
| const ThunarFile *child) | |
| { | |
| gboolean is_parent = FALSE; | |
| GFile *parent; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (child), FALSE); | |
| parent = g_file_get_parent (child->gfile); | |
| if (parent != NULL) | |
| { | |
| is_parent = g_file_equal (file->gfile, parent); | |
| g_object_unref (parent); | |
| } | |
| return is_parent; | |
| } | |
| /** | |
| * thunar_file_is_ancestor: | |
| * @file : a #ThunarFile instance. | |
| * @ancestor : another #GFile instance. | |
| * | |
| * Determines whether @file is somewhere inside @ancestor, | |
| * possibly with intermediate folders. | |
| * | |
| * Return value: %TRUE if @ancestor contains @file as a | |
| * child, grandchild, great grandchild, etc. | |
| **/ | |
| gboolean | |
| thunar_file_is_gfile_ancestor (const ThunarFile *file, | |
| GFile *ancestor) | |
| { | |
| gboolean is_ancestor = FALSE; | |
| GFile *current = NULL; | |
| GFile *tmp; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (G_IS_FILE (ancestor), FALSE); | |
| for (current = g_object_ref (file->gfile); | |
| is_ancestor == FALSE && current != NULL; | |
| tmp = g_file_get_parent (current), g_object_unref (current), current = tmp) | |
| { | |
| if (G_UNLIKELY (g_file_equal (current, ancestor))) | |
| is_ancestor = TRUE; | |
| } | |
| if (current != NULL) | |
| g_object_unref (current); | |
| return is_ancestor; | |
| } | |
| /** | |
| * thunar_file_is_ancestor: | |
| * @file : a #ThunarFile instance. | |
| * @ancestor : another #ThunarFile instance. | |
| * | |
| * Determines whether @file is somewhere inside @ancestor, | |
| * possibly with intermediate folders. | |
| * | |
| * Return value: %TRUE if @ancestor contains @file as a | |
| * child, grandchild, great grandchild, etc. | |
| **/ | |
| gboolean | |
| thunar_file_is_ancestor (const ThunarFile *file, | |
| const ThunarFile *ancestor) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (ancestor), FALSE); | |
| return thunar_file_is_gfile_ancestor (file, ancestor->gfile); | |
| } | |
| /** | |
| * thunar_file_is_executable: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines whether the owner of the current process is allowed | |
| * to execute the @file (or enter the directory refered to by | |
| * @file). On UNIX it also returns %TRUE if @file refers to a | |
| * desktop entry. | |
| * | |
| * Return value: %TRUE if @file can be executed. | |
| **/ | |
| gboolean | |
| thunar_file_is_executable (const ThunarFile *file) | |
| { | |
| ThunarPreferences *preferences; | |
| gboolean can_execute = FALSE; | |
| gboolean exec_shell_scripts = FALSE; | |
| const gchar *content_type; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| if (g_file_info_get_attribute_boolean (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) | |
| { | |
| /* get the content type of the file */ | |
| content_type = thunar_file_get_content_type (THUNAR_FILE (file)); | |
| if (G_LIKELY (content_type != NULL)) | |
| { | |
| can_execute = g_content_type_can_be_executable (content_type); | |
| if (can_execute) | |
| { | |
| /* check if the shell scripts should be executed or opened by default */ | |
| preferences = thunar_preferences_get (); | |
| g_object_get (preferences, "misc-exec-shell-scripts-by-default", &exec_shell_scripts, NULL); | |
| g_object_unref (preferences); | |
| /* do never execute plain text files which are not shell scripts but marked executable */ | |
| if (g_strcmp0 (content_type, "text/plain") == 0) | |
| can_execute = FALSE; | |
| else if (g_content_type_is_a (content_type, "text/plain") && ! exec_shell_scripts) | |
| can_execute = FALSE; | |
| } | |
| } | |
| } | |
| return can_execute || thunar_file_is_desktop_file (file, NULL); | |
| } | |
| /** | |
| * thunar_file_is_readable: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines whether the owner of the current process is allowed | |
| * to read the @file. | |
| * | |
| * Return value: %TRUE if @file can be read. | |
| **/ | |
| static gboolean | |
| thunar_file_is_readable (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| if (!g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) | |
| return TRUE; | |
| return g_file_info_get_attribute_boolean (file->info, | |
| G_FILE_ATTRIBUTE_ACCESS_CAN_READ); | |
| } | |
| /** | |
| * thunar_file_is_writable: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines whether the owner of the current process is allowed | |
| * to write the @file. | |
| * | |
| * Return value: %TRUE if @file can be read. | |
| **/ | |
| gboolean | |
| thunar_file_is_writable (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| if (!g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) | |
| return TRUE; | |
| return g_file_info_get_attribute_boolean (file->info, | |
| G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); | |
| } | |
| /** | |
| * thunar_file_is_hidden: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Checks whether @file can be considered a hidden file. | |
| * | |
| * Return value: %TRUE if @file is a hidden file, else %FALSE. | |
| **/ | |
| gboolean | |
| thunar_file_is_hidden (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| return g_file_info_get_is_hidden (file->info) | |
| || g_file_info_get_is_backup (file->info); | |
| } | |
| /** | |
| * thunar_file_is_home: | |
| * @file : a #ThunarFile. | |
| * | |
| * Checks whether @file refers to the users home directory. | |
| * | |
| * Return value: %TRUE if @file is the users home directory. | |
| **/ | |
| gboolean | |
| thunar_file_is_home (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return thunar_g_file_is_home (file->gfile); | |
| } | |
| /** | |
| * thunar_file_is_regular: | |
| * @file : a #ThunarFile. | |
| * | |
| * Checks whether @file refers to a regular file. | |
| * | |
| * Return value: %TRUE if @file is a regular file. | |
| **/ | |
| gboolean | |
| thunar_file_is_regular (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return file->kind == G_FILE_TYPE_REGULAR; | |
| } | |
| /** | |
| * thunar_file_is_trashed: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns %TRUE if @file is a local file that resides in | |
| * the trash bin. | |
| * | |
| * Return value: %TRUE if @file is in the trash, or | |
| * the trash folder itself. | |
| **/ | |
| gboolean | |
| thunar_file_is_trashed (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return thunar_g_file_is_trashed (file->gfile); | |
| } | |
| /** | |
| * thunar_file_is_desktop_file: | |
| * @file : a #ThunarFile. | |
| * @is_secure : if %NULL do a simple check, else it will set this boolean | |
| * to indicate if the desktop file is safe see bug #5012 | |
| * for more info. | |
| * | |
| * Returns %TRUE if @file is a .desktop file. The @is_secure return value | |
| * will tell if the .desktop file is also secure. | |
| * | |
| * Return value: %TRUE if @file is a .desktop file. | |
| **/ | |
| gboolean | |
| thunar_file_is_desktop_file (const ThunarFile *file, | |
| gboolean *is_secure) | |
| { | |
| const gchar * const *data_dirs; | |
| guint n; | |
| gchar *path; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| /* only allow regular files with a .desktop extension */ | |
| if (!g_str_has_suffix (file->basename, ".desktop") | |
| || file->kind != G_FILE_TYPE_REGULAR) | |
| return FALSE; | |
| /* don't check more if not needed */ | |
| if (is_secure == NULL) | |
| return TRUE; | |
| /* desktop files outside xdg directories need to be executable for security reasons */ | |
| if (g_file_info_get_attribute_boolean (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) | |
| { | |
| /* has +x */ | |
| *is_secure = TRUE; | |
| } | |
| else | |
| { | |
| /* assume the file is not safe */ | |
| *is_secure = FALSE; | |
| /* deskopt files in xdg directories are also fine... */ | |
| if (g_file_is_native (thunar_file_get_file (file))) | |
| { | |
| data_dirs = g_get_system_data_dirs (); | |
| if (G_LIKELY (data_dirs != NULL)) | |
| { | |
| path = g_file_get_path (thunar_file_get_file (file)); | |
| for (n = 0; data_dirs[n] != NULL; n++) | |
| { | |
| if (g_str_has_prefix (path, data_dirs[n])) | |
| { | |
| /* has known prefix, can launch without problems */ | |
| *is_secure = TRUE; | |
| break; | |
| } | |
| } | |
| g_free (path); | |
| } | |
| } | |
| } | |
| return TRUE; | |
| } | |
| /** | |
| * thunar_file_get_display_name: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the @file name in the UTF-8 encoding, which is | |
| * suitable for displaying the file name in the GUI. | |
| * | |
| * Return value: the @file name suitable for display. | |
| **/ | |
| const gchar * | |
| thunar_file_get_display_name (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| return file->display_name; | |
| } | |
| /** | |
| * thunar_file_get_deletion_date: | |
| * @file : a #ThunarFile instance. | |
| * @date_style : the style used to format the date. | |
| * | |
| * Returns the deletion date of the @file if the @file | |
| * is located in the trash. Otherwise %NULL will be | |
| * returned. | |
| * | |
| * The caller is responsible to free the returned string | |
| * using g_free() when no longer needed. | |
| * | |
| * Return value: the deletion date of @file if @file is | |
| * in the trash, %NULL otherwise. | |
| **/ | |
| gchar* | |
| thunar_file_get_deletion_date (const ThunarFile *file, | |
| ThunarDateStyle date_style) | |
| { | |
| const gchar *date; | |
| time_t deletion_time; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| _thunar_return_val_if_fail (G_IS_FILE_INFO (file->info), NULL); | |
| date = g_file_info_get_attribute_string (file->info, G_FILE_ATTRIBUTE_TRASH_DELETION_DATE); | |
| if (G_UNLIKELY (date == NULL)) | |
| return NULL; | |
| /* try to parse the DeletionDate (RFC 3339 string) */ | |
| deletion_time = thunar_util_time_from_rfc3339 (date); | |
| /* humanize the time value */ | |
| return thunar_util_humanize_file_time (deletion_time, date_style); | |
| } | |
| /** | |
| * thunar_file_get_original_path: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the original path of the @file if the @file | |
| * is located in the trash. Otherwise %NULL will be | |
| * returned. | |
| * | |
| * Return value: the original path of @file if @file is | |
| * in the trash, %NULL otherwise. | |
| **/ | |
| const gchar * | |
| thunar_file_get_original_path (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| if (file->info == NULL) | |
| return NULL; | |
| return g_file_info_get_attribute_byte_string (file->info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH); | |
| } | |
| /** | |
| * thunar_file_get_item_count: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the number of items in the trash, if @file refers to the | |
| * trash root directory. Otherwise returns 0. | |
| * | |
| * Return value: number of files in the trash if @file is the trash | |
| * root dir, 0 otherwise. | |
| **/ | |
| guint32 | |
| thunar_file_get_item_count (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0); | |
| if (file->info == NULL) | |
| return 0; | |
| return g_file_info_get_attribute_uint32 (file->info, | |
| G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT); | |
| } | |
| /** | |
| * thunar_file_is_chmodable: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines whether the owner of the current process is allowed | |
| * to changed the file mode of @file. | |
| * | |
| * Return value: %TRUE if the mode of @file can be changed. | |
| **/ | |
| gboolean | |
| thunar_file_is_chmodable (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| /* we can only change the mode if we the euid is | |
| * a) equal to the file owner id | |
| * or | |
| * b) the super-user id | |
| * and the file is not in the trash. | |
| */ | |
| if (file->info == NULL) | |
| { | |
| return (effective_user_id == 0 && !thunar_file_is_trashed (file)); | |
| } | |
| else | |
| { | |
| return ((effective_user_id == 0 | |
| || effective_user_id == g_file_info_get_attribute_uint32 (file->info, | |
| G_FILE_ATTRIBUTE_UNIX_UID)) | |
| && !thunar_file_is_trashed (file)); | |
| } | |
| } | |
| /** | |
| * thunar_file_is_renameable: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines whether @file can be renamed using | |
| * #thunar_file_rename(). Note that the return | |
| * value is just a guess and #thunar_file_rename() | |
| * may fail even if this method returns %TRUE. | |
| * | |
| * Return value: %TRUE if @file can be renamed. | |
| **/ | |
| gboolean | |
| thunar_file_is_renameable (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| return g_file_info_get_attribute_boolean (file->info, | |
| G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME); | |
| } | |
| gboolean | |
| thunar_file_can_be_trashed (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| if (file->info == NULL) | |
| return FALSE; | |
| return g_file_info_get_attribute_boolean (file->info, | |
| G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH); | |
| } | |
| /** | |
| * thunar_file_get_emblem_names: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Determines the names of the emblems that should be displayed for | |
| * @file. The returned list is owned by the caller, but the list | |
| * items - the name strings - are owned by @file. So the caller | |
| * must call g_list_free(), but don't g_free() the list items. | |
| * | |
| * Note that the strings contained in the returned list are | |
| * not garantied to exist over the next iteration of the main | |
| * loop. So in case you need the list of emblem names for | |
| * a longer time, you'll need to take a copy of the strings. | |
| * | |
| * Return value: the names of the emblems for @file. | |
| **/ | |
| GList* | |
| thunar_file_get_emblem_names (ThunarFile *file) | |
| { | |
| guint32 uid; | |
| gchar **emblem_names; | |
| GList *emblems = NULL; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| /* leave if there is no info */ | |
| if (file->info == NULL) | |
| return NULL; | |
| /* determine the custom emblems */ | |
| emblem_names = g_file_info_get_attribute_stringv (file->info, "metadata::emblems"); | |
| if (G_UNLIKELY (emblem_names != NULL)) | |
| { | |
| for (; *emblem_names != NULL; ++emblem_names) | |
| emblems = g_list_append (emblems, *emblem_names); | |
| } | |
| if (thunar_file_is_symlink (file)) | |
| emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_SYMBOLIC_LINK); | |
| /* determine the user ID of the file owner */ | |
| /* TODO what are we going to do here on non-UNIX systems? */ | |
| uid = file->info != NULL | |
| ? g_file_info_get_attribute_uint32 (file->info, G_FILE_ATTRIBUTE_UNIX_UID) | |
| : 0; | |
| /* we add "cant-read" if either (a) the file is not readable or (b) a directory, that lacks the | |
| * x-bit, see http://bugzilla.xfce.org/show_bug.cgi?id=1408 for the details about this change. | |
| */ | |
| if (!thunar_file_is_readable (file) | |
| || (thunar_file_is_directory (file) | |
| && thunar_file_denies_access_permission (file, THUNAR_FILE_MODE_USR_EXEC, | |
| THUNAR_FILE_MODE_GRP_EXEC, | |
| THUNAR_FILE_MODE_OTH_EXEC))) | |
| { | |
| emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_CANT_READ); | |
| } | |
| else if (G_UNLIKELY (uid == effective_user_id && !thunar_file_is_writable (file))) | |
| { | |
| /* we own the file, but we cannot write to it, that's why we mark it as "cant-write", so | |
| * users won't be surprised when opening the file in a text editor, but are unable to save. | |
| */ | |
| emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_CANT_WRITE); | |
| } | |
| return emblems; | |
| } | |
| /** | |
| * thunar_file_set_emblem_names: | |
| * @file : a #ThunarFile instance. | |
| * @emblem_names : a #GList of emblem names. | |
| * | |
| * Sets the custom emblem name list of @file to @emblem_names | |
| * and stores them in the @file<!---->s metadata. | |
| **/ | |
| void | |
| thunar_file_set_emblem_names (ThunarFile *file, | |
| GList *emblem_names) | |
| { | |
| GList *lp; | |
| gchar **emblems = NULL; | |
| gint n; | |
| GFileInfo *info; | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| _thunar_return_if_fail (G_IS_FILE_INFO (file->info)); | |
| /* allocate a zero-terminated array for the emblem names */ | |
| emblems = g_new0 (gchar *, g_list_length (emblem_names) + 1); | |
| /* turn the emblem_names list into a zero terminated array */ | |
| for (lp = emblem_names, n = 0; lp != NULL; lp = lp->next) | |
| { | |
| /* skip special emblems */ | |
| if (strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_SYMBOLIC_LINK) == 0 | |
| || strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_CANT_READ) == 0 | |
| || strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_CANT_WRITE) == 0 | |
| || strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_DESKTOP) == 0) | |
| continue; | |
| /* add the emblem to our list */ | |
| emblems[n++] = g_strdup (lp->data); | |
| } | |
| /* set the value in the current info */ | |
| if (n == 0) | |
| g_file_info_remove_attribute (file->info, "metadata::emblems"); | |
| else | |
| g_file_info_set_attribute_stringv (file->info, "metadata::emblems", emblems); | |
| /* set meta data to the daemon */ | |
| info = g_file_info_new (); | |
| g_file_info_set_attribute_stringv (info, "metadata::emblems", emblems); | |
| g_file_set_attributes_async (file->gfile, info, | |
| G_FILE_QUERY_INFO_NONE, | |
| G_PRIORITY_DEFAULT, | |
| NULL, | |
| thunar_file_set_emblem_names_ready, | |
| file); | |
| g_object_unref (G_OBJECT (info)); | |
| g_strfreev (emblems); | |
| } | |
| /** | |
| * thunar_file_set_custom_icon: | |
| * @file : a #ThunarFile instance. | |
| * @custom_icon : the new custom icon for the @file. | |
| * @error : return location for errors or %NULL. | |
| * | |
| * Tries to change the custom icon of the .desktop file referred | |
| * to by @file. If that fails, %FALSE is returned and the | |
| * @error is set accordingly. | |
| * | |
| * Return value: %TRUE if the icon of @file was changed, %FALSE otherwise. | |
| **/ | |
| gboolean | |
| thunar_file_set_custom_icon (ThunarFile *file, | |
| const gchar *custom_icon, | |
| GError **error) | |
| { | |
| GKeyFile *key_file; | |
| _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| _thunar_return_val_if_fail (custom_icon != NULL, FALSE); | |
| key_file = thunar_g_file_query_key_file (file->gfile, NULL, error); | |
| if (key_file == NULL) | |
| return FALSE; | |
| g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, | |
| G_KEY_FILE_DESKTOP_KEY_ICON, custom_icon); | |
| if (thunar_g_file_write_key_file (file->gfile, key_file, NULL, error)) | |
| { | |
| /* tell everybody that we have changed */ | |
| thunar_file_changed (file); | |
| g_key_file_free (key_file); | |
| return TRUE; | |
| } | |
| else | |
| { | |
| g_key_file_free (key_file); | |
| return FALSE; | |
| } | |
| } | |
| /** | |
| * thunar_file_is_desktop: | |
| * @file : a #ThunarFile. | |
| * | |
| * Checks whether @file refers to the users desktop directory. | |
| * | |
| * Return value: %TRUE if @file is the users desktop directory. | |
| **/ | |
| gboolean | |
| thunar_file_is_desktop (const ThunarFile *file) | |
| { | |
| GFile *desktop; | |
| gboolean is_desktop = FALSE; | |
| desktop = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP)); | |
| is_desktop = g_file_equal (file->gfile, desktop); | |
| g_object_unref (desktop); | |
| return is_desktop; | |
| } | |
| const gchar * | |
| thunar_file_get_thumbnail_path (ThunarFile *file) | |
| { | |
| GChecksum *checksum; | |
| gchar *filename; | |
| gchar *uri; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| /* if the thumbstate is known to be not there, return null */ | |
| if (thunar_file_get_thumb_state (file) == THUNAR_FILE_THUMB_STATE_NONE) | |
| return NULL; | |
| if (G_UNLIKELY (file->thumbnail_path == NULL)) | |
| { | |
| checksum = g_checksum_new (G_CHECKSUM_MD5); | |
| if (G_LIKELY (checksum != NULL)) | |
| { | |
| uri = thunar_file_dup_uri (file); | |
| g_checksum_update (checksum, (const guchar *) uri, strlen (uri)); | |
| g_free (uri); | |
| filename = g_strconcat (g_checksum_get_string (checksum), ".png", NULL); | |
| g_checksum_free (checksum); | |
| /* The thumbnail is in the format/location | |
| * $XDG_CACHE_HOME/thumbnails/(nromal|large)/MD5_Hash_Of_URI.png | |
| * for version 0.8.0 if XDG_CACHE_HOME is defined, otherwise | |
| * /homedir/.thumbnails/(normal|large)/MD5_Hash_Of_URI.png | |
| * will be used, which is also always used for versions prior | |
| * to 0.7.0. | |
| */ | |
| /* build and check if the thumbnail is in the new location */ | |
| file->thumbnail_path = g_build_path ("/", g_get_user_cache_dir(), | |
| "thumbnails", "normal", | |
| filename, NULL); | |
| if (!g_file_test(file->thumbnail_path, G_FILE_TEST_EXISTS)) | |
| { | |
| /* Fallback to old version */ | |
| g_free(file->thumbnail_path); | |
| file->thumbnail_path = g_build_filename (xfce_get_homedir (), ".thumbnails", | |
| "normal", filename, NULL); | |
| if(!g_file_test(file->thumbnail_path, G_FILE_TEST_EXISTS)) | |
| { | |
| /* Thumbnail doesn't exist in either spot */ | |
| g_free(file->thumbnail_path); | |
| file->thumbnail_path = NULL; | |
| } | |
| } | |
| g_free (filename); | |
| } | |
| } | |
| return file->thumbnail_path; | |
| } | |
| /** | |
| * thunar_file_get_thumb_state: | |
| * @file : a #ThunarFile. | |
| * | |
| * Returns the current #ThunarFileThumbState for @file. This | |
| * method is intended to be used by #ThunarIconFactory only. | |
| * | |
| * Return value: the #ThunarFileThumbState for @file. | |
| **/ | |
| ThunarFileThumbState | |
| thunar_file_get_thumb_state (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), THUNAR_FILE_THUMB_STATE_UNKNOWN); | |
| return FLAG_GET_THUMB_STATE (file); | |
| } | |
| /** | |
| * thunar_file_set_thumb_state: | |
| * @file : a #ThunarFile. | |
| * @thumb_state : the new #ThunarFileThumbState. | |
| * | |
| * Sets the #ThunarFileThumbState for @file to @thumb_state. | |
| * This will cause a "file-changed" signal to be emitted from | |
| * #ThunarFileMonitor. | |
| **/ | |
| void | |
| thunar_file_set_thumb_state (ThunarFile *file, | |
| ThunarFileThumbState state) | |
| { | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| /* check if the state changes */ | |
| if (thunar_file_get_thumb_state (file) == state) | |
| return; | |
| /* set the new thumbnail state */ | |
| FLAG_SET_THUMB_STATE (file, state); | |
| /* remove path if the type is not supported */ | |
| if (state == THUNAR_FILE_THUMB_STATE_NONE | |
| && file->thumbnail_path != NULL) | |
| { | |
| g_free (file->thumbnail_path); | |
| file->thumbnail_path = NULL; | |
| } | |
| /* if the file has a thumbnail, reload it */ | |
| if (state == THUNAR_FILE_THUMB_STATE_READY) | |
| thunar_file_monitor_file_changed (file); | |
| } | |
| /** | |
| * thunar_file_get_custom_icon: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Queries the custom icon from @file if any, else %NULL is returned. | |
| * The custom icon can be either a themed icon name or an absolute path | |
| * to an icon file in the local file system. | |
| * | |
| * Return value: the custom icon for @file or %NULL. | |
| **/ | |
| const gchar * | |
| thunar_file_get_custom_icon (const ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| return file->custom_icon_name; | |
| } | |
| /** | |
| * thunar_file_get_preview_icon: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Returns the preview icon for @file if any, else %NULL is returned. | |
| * | |
| * Return value: the custom icon for @file or %NULL, the GIcon is owner | |
| * by the file, so do not unref it. | |
| **/ | |
| GIcon * | |
| thunar_file_get_preview_icon (const ThunarFile *file) | |
| { | |
| GObject *icon; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| _thunar_return_val_if_fail (G_IS_FILE_INFO (file->info), NULL); | |
| icon = g_file_info_get_attribute_object (file->info, G_FILE_ATTRIBUTE_PREVIEW_ICON); | |
| if (G_LIKELY (icon != NULL)) | |
| return G_ICON (icon); | |
| return NULL; | |
| } | |
| GFilesystemPreviewType | |
| thunar_file_get_preview_type (const ThunarFile *file) | |
| { | |
| GFilesystemPreviewType preview; | |
| GFileInfo *info; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), G_FILESYSTEM_PREVIEW_TYPE_NEVER); | |
| _thunar_return_val_if_fail (G_IS_FILE (file->gfile), G_FILESYSTEM_PREVIEW_TYPE_NEVER); | |
| info = g_file_query_filesystem_info (file->gfile, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, NULL, NULL); | |
| if (G_LIKELY (info != NULL)) | |
| { | |
| preview = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW); | |
| g_object_unref (G_OBJECT (info)); | |
| } | |
| else | |
| { | |
| /* assume we don't know */ | |
| preview = G_FILESYSTEM_PREVIEW_TYPE_NEVER; | |
| } | |
| return preview; | |
| } | |
| static const gchar * | |
| thunar_file_get_icon_name_for_state (const gchar *icon_name, | |
| ThunarFileIconState icon_state) | |
| { | |
| if (exo_str_is_empty (icon_name)) | |
| return NULL; | |
| /* check if we have an accept icon for the icon we found */ | |
| if (icon_state != THUNAR_FILE_ICON_STATE_DEFAULT | |
| && (strcmp (icon_name, "inode-directory") == 0 | |
| || strcmp (icon_name, "folder") == 0)) | |
| { | |
| if (icon_state == THUNAR_FILE_ICON_STATE_DROP) | |
| return "folder-drag-accept"; | |
| else if (icon_state == THUNAR_FILE_ICON_STATE_OPEN) | |
| return "folder-open"; | |
| } | |
| return icon_name; | |
| } | |
| /** | |
| * thunar_file_get_icon_name: | |
| * @file : a #ThunarFile instance. | |
| * @icon_state : the state of the @file<!---->s icon we are interested in. | |
| * @icon_theme : the #GtkIconTheme on which to lookup up the icon name. | |
| * | |
| * Returns the name of the icon that can be used to present @file, based | |
| * on the given @icon_state and @icon_theme. | |
| * | |
| * Return value: the icon name for @file in @icon_theme. | |
| **/ | |
| const gchar * | |
| thunar_file_get_icon_name (ThunarFile *file, | |
| ThunarFileIconState icon_state, | |
| GtkIconTheme *icon_theme) | |
| { | |
| GFile *icon_file; | |
| GIcon *icon = NULL; | |
| const gchar * const *names; | |
| gchar *icon_name = NULL; | |
| gchar *path; | |
| const gchar *special_names[] = { NULL, "folder", NULL }; | |
| guint i; | |
| const gchar *special_dir; | |
| GFileInfo *fileinfo; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL); | |
| _thunar_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL); | |
| /* return cached name */ | |
| if (G_LIKELY (file->icon_name != NULL)) | |
| return thunar_file_get_icon_name_for_state (file->icon_name, icon_state); | |
| /* the system root folder has a special icon */ | |
| if (thunar_file_is_directory (file)) | |
| { | |
| if (G_LIKELY (thunar_file_is_local (file))) | |
| { | |
| path = g_file_get_path (file->gfile); | |
| if (G_LIKELY (path != NULL)) | |
| { | |
| if (strcmp (path, G_DIR_SEPARATOR_S) == 0) | |
| *special_names = "drive-harddisk"; | |
| else if (strcmp (path, xfce_get_homedir ()) == 0) | |
| *special_names = "user-home"; | |
| else | |
| { | |
| for (i = 0; i < G_N_ELEMENTS (thunar_file_dirs); i++) | |
| { | |
| special_dir = g_get_user_special_dir (thunar_file_dirs[i].type); | |
| if (special_dir != NULL | |
| && strcmp (path, special_dir) == 0) | |
| { | |
| *special_names = thunar_file_dirs[i].icon_name; | |
| break; | |
| } | |
| } | |
| } | |
| g_free (path); | |
| } | |
| } | |
| else if (!thunar_file_has_parent (file)) | |
| { | |
| if (g_file_has_uri_scheme (file->gfile, "trash")) | |
| { | |
| special_names[0] = thunar_file_get_item_count (file) > 0 ? "user-trash-full" : "user-trash"; | |
| special_names[1] = "user-trash"; | |
| } | |
| else if (g_file_has_uri_scheme (file->gfile, "network")) | |
| { | |
| special_names[0] = "network-workgroup"; | |
| } | |
| else if (g_file_has_uri_scheme (file->gfile, "recent")) | |
| { | |
| special_names[0] = "document-open-recent"; | |
| } | |
| else if (g_file_has_uri_scheme (file->gfile, "computer")) | |
| { | |
| special_names[0] = "computer"; | |
| } | |
| } | |
| if (*special_names != NULL) | |
| { | |
| names = special_names; | |
| goto check_names; | |
| } | |
| } | |
| else if (thunar_file_is_mountable (file) | |
| || g_file_has_uri_scheme (file->gfile, "network")) | |
| { | |
| /* query the icon (computer:// and network:// backend) */ | |
| fileinfo = g_file_query_info (file->gfile, | |
| G_FILE_ATTRIBUTE_STANDARD_ICON, | |
| G_FILE_QUERY_INFO_NONE, NULL, NULL); | |
| if (G_LIKELY (fileinfo != NULL)) | |
| { | |
| /* take the icon from the info */ | |
| icon = g_file_info_get_icon (fileinfo); | |
| if (G_LIKELY (icon != NULL)) | |
| g_object_ref (icon); | |
| /* release */ | |
| g_object_unref (G_OBJECT (fileinfo)); | |
| if (G_LIKELY (icon != NULL)) | |
| goto check_icon; | |
| } | |
| } | |
| /* try again later */ | |
| if (file->info == NULL) | |
| return NULL; | |
| /* lookup for content type, just like gio does for local files */ | |
| icon = g_content_type_get_icon (thunar_file_get_content_type (file)); | |
| if (G_LIKELY (icon != NULL)) | |
| { | |
| check_icon: | |
| if (G_IS_THEMED_ICON (icon)) | |
| { | |
| names = g_themed_icon_get_names (G_THEMED_ICON (icon)); | |
| check_names: | |
| if (G_LIKELY (names != NULL)) | |
| { | |
| for (i = 0; names[i] != NULL; ++i) | |
| if (*names[i] != '(' /* see gnome bug 688042 */ | |
| && gtk_icon_theme_has_icon (icon_theme, names[i])) | |
| { | |
| icon_name = g_strdup (names[i]); | |
| break; | |
| } | |
| } | |
| } | |
| else if (G_IS_FILE_ICON (icon)) | |
| { | |
| icon_file = g_file_icon_get_file (G_FILE_ICON (icon)); | |
| if (icon_file != NULL) | |
| icon_name = g_file_get_path (icon_file); | |
| } | |
| if (G_LIKELY (icon != NULL)) | |
| g_object_unref (icon); | |
| } | |
| /* store new name, fallback to legacy names, or empty string to avoid recursion */ | |
| g_free (file->icon_name); | |
| if (G_LIKELY (icon_name != NULL)) | |
| file->icon_name = icon_name; | |
| else if (file->kind == G_FILE_TYPE_DIRECTORY | |
| && gtk_icon_theme_has_icon (icon_theme, "folder")) | |
| file->icon_name = g_strdup ("folder"); | |
| else | |
| file->icon_name = g_strdup (""); | |
| return thunar_file_get_icon_name_for_state (file->icon_name, icon_state); | |
| } | |
| /** | |
| * thunar_file_watch: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Tells @file to watch itself for changes. Not all #ThunarFile | |
| * implementations must support this, but if a #ThunarFile | |
| * implementation implements the thunar_file_watch() method, | |
| * it must also implement the thunar_file_unwatch() method. | |
| * | |
| * The #ThunarFile base class implements automatic "ref | |
| * counting" for watches, that says, you can call thunar_file_watch() | |
| * multiple times, but the virtual method will be invoked only | |
| * once. This also means that you MUST call thunar_file_unwatch() | |
| * for every thunar_file_watch() invokation, else the application | |
| * will abort. | |
| **/ | |
| void | |
| thunar_file_watch (ThunarFile *file) | |
| { | |
| ThunarFileWatch *file_watch; | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark); | |
| if (file_watch == NULL) | |
| { | |
| file_watch = g_slice_new (ThunarFileWatch); | |
| file_watch->watch_count = 1; | |
| /* create a file or directory monitor */ | |
| file_watch->monitor = g_file_monitor (file->gfile, G_FILE_MONITOR_WATCH_MOUNTS | G_FILE_MONITOR_SEND_MOVED, NULL, NULL); | |
| if (G_LIKELY (file_watch->monitor != NULL)) | |
| { | |
| /* watch monitor for file changes */ | |
| g_signal_connect (file_watch->monitor, "changed", G_CALLBACK (thunar_file_monitor), file); | |
| } | |
| /* attach to file */ | |
| g_object_set_qdata_full (G_OBJECT (file), thunar_file_watch_quark, file_watch, thunar_file_watch_destroyed); | |
| } | |
| else | |
| { | |
| /* increase watch count */ | |
| _thunar_return_if_fail (G_IS_FILE_MONITOR (file_watch->monitor)); | |
| file_watch->watch_count++; | |
| } | |
| } | |
| /** | |
| * thunar_file_unwatch: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * See thunar_file_watch() for a description of how watching | |
| * #ThunarFile<!---->s works. | |
| **/ | |
| void | |
| thunar_file_unwatch (ThunarFile *file) | |
| { | |
| ThunarFileWatch *file_watch; | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark); | |
| if (file_watch != NULL) | |
| { | |
| /* remove if this was the last ref */ | |
| if (--file_watch->watch_count == 0) | |
| g_object_set_qdata (G_OBJECT (file), thunar_file_watch_quark, NULL); | |
| } | |
| else | |
| { | |
| _thunar_assert_not_reached (); | |
| } | |
| } | |
| /** | |
| * thunar_file_reload: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Tells @file to reload its internal state, e.g. by reacquiring | |
| * the file info from the underlying media. | |
| * | |
| * You must be able to handle the case that @file is | |
| * destroyed during the reload call. | |
| * | |
| * Return value: As this function can be used as a callback function | |
| * for thunar_file_reload_idle, it will always return FALSE to prevent | |
| * being called repeatedly. | |
| **/ | |
| gboolean | |
| thunar_file_reload (ThunarFile *file) | |
| { | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); | |
| /* clear file pxmap cache */ | |
| thunar_icon_factory_clear_pixmap_cache (file); | |
| if (!thunar_file_load (file, NULL, NULL)) | |
| { | |
| /* destroy the file if we cannot query any file information */ | |
| thunar_file_destroy (file); | |
| return FALSE; | |
| } | |
| /* ... and tell others */ | |
| thunar_file_changed (file); | |
| return FALSE; | |
| } | |
| /** | |
| * thunar_file_reload_idle: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Schedules a reload of the @file by calling thunar_file_reload | |
| * when idle. | |
| * | |
| **/ | |
| void | |
| thunar_file_reload_idle (ThunarFile *file) | |
| { | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| g_idle_add ((GSourceFunc) thunar_file_reload, file); | |
| } | |
| /** | |
| * thunar_file_destroy: | |
| * @file : a #ThunarFile instance. | |
| * | |
| * Emits the ::destroy signal notifying all reference holders | |
| * that they should release their references to the @file. | |
| * | |
| * This method is very similar to what gtk_object_destroy() | |
| * does for #GtkObject<!---->s. | |
| **/ | |
| void | |
| thunar_file_destroy (ThunarFile *file) | |
| { | |
| _thunar_return_if_fail (THUNAR_IS_FILE (file)); | |
| if (!FLAG_IS_SET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION)) | |
| { | |
| /* take an additional reference on the file, as the file-destroyed | |
| * invocation may already release the last reference. | |
| */ | |
| g_object_ref (G_OBJECT (file)); | |
| /* tell the file monitor that this file was destroyed */ | |
| thunar_file_monitor_file_destroyed (file); | |
| /* run the dispose handler */ | |
| g_object_run_dispose (G_OBJECT (file)); | |
| /* release our reference */ | |
| g_object_unref (G_OBJECT (file)); | |
| } | |
| } | |
| /** | |
| * thunar_file_compare_by_name: | |
| * @file_a : the first #ThunarFile. | |
| * @file_b : the second #ThunarFile. | |
| * @case_sensitive : whether the comparison should be case-sensitive. | |
| * | |
| * Compares @file_a and @file_b by their display names. If @case_sensitive | |
| * is %TRUE the comparison will be case-sensitive. | |
| * | |
| * Return value: -1 if @file_a should be sorted before @file_b, 1 if | |
| * @file_b should be sorted before @file_a, 0 if equal. | |
| **/ | |
| gint | |
| thunar_file_compare_by_name (const ThunarFile *file_a, | |
| const ThunarFile *file_b, | |
| gboolean case_sensitive) | |
| { | |
| gint result = 0; | |
| #ifdef G_ENABLE_DEBUG | |
| /* probably too expensive to do the instance check every time | |
| * this function is called, so only for debugging builds. | |
| */ | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_a), 0); | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_b), 0); | |
| #endif | |
| /* case insensitive checking */ | |
| if (G_LIKELY (!case_sensitive)) | |
| result = strcmp (file_a->collate_key_nocase, file_b->collate_key_nocase); | |
| /* fall-back to case sensitive */ | |
| if (result == 0) | |
| result = strcmp (file_a->collate_key, file_b->collate_key); | |
| /* this happens in the trash */ | |
| if (result == 0) | |
| { | |
| result = g_strcmp0 (thunar_file_get_original_path (file_a), | |
| thunar_file_get_original_path (file_b)); | |
| } | |
| return result; | |
| } | |
| static gboolean | |
| thunar_file_same_filesystem (const ThunarFile *file_a, | |
| const ThunarFile *file_b) | |
| { | |
| const gchar *filesystem_id_a; | |
| const gchar *filesystem_id_b; | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_a), FALSE); | |
| _thunar_return_val_if_fail (THUNAR_IS_FILE (file_b), FALSE); | |
| /* return false if we have no information about one of the files */ | |
| if (file_a->info == NULL || file_b->info == NULL) | |
| return FALSE; | |
| /* determine the filesystem IDs */ | |
| filesystem_id_a = g_file_info_get_attribute_string (file_a->info, | |
| G_FILE_ATTRIBUTE_ID_FILESYSTEM); | |
| filesystem_id_b = g_file_info_get_attribute_string (file_b->info, | |
| G_FILE_ATTRIBUTE_ID_FILESYSTEM); | |
| /* compare the filesystem IDs */ | |
| return exo_str_is_equal (filesystem_id_a, filesystem_id_b); | |
| } | |
| /** | |
| * thunar_file_cache_lookup: | |
| * @file : a #GFile. | |
| * | |
| * Looks up the #ThunarFile for @file in the internal file | |
| * cache and returns the file present for @file in the | |
| * cache or %NULL if no #ThunarFile is cached for @file. | |
| * | |
| * Note that no reference is taken for the caller. | |
| * | |
| * This method should not be used but in very rare cases. | |
| * Consider using thunar_file_get() instead. | |
| * | |
| * Return value: the #ThunarFile for @file in the internal | |
| * cache, or %NULL. If you are done with the | |
| * file, use g_object_unref to release. | |
| **/ | |
| ThunarFile * | |
| thunar_file_cache_lookup (const GFile *file) | |
| { | |
| ThunarFile *cached_file; | |
| _thunar_return_val_if_fail (G_IS_FILE (file), NULL); | |
| G_LOCK (file_cache_mutex); | |
| /* allocate the ThunarFile cache on-demand */ | |
| if (G_UNLIKELY (file_cache == NULL)) | |
| { | |
| file_cache = g_hash_table_new_full (g_file_hash, | |
| (GEqualFunc) g_file_equal, | |
| (GDestroyNotify) g_object_unref, | |
| NULL); | |
| } | |
| cached_file = g_hash_table_lookup (file_cache, file); | |
| if (cached_file != NULL) | |
| { | |
| /* take a reference to avoid too-early releases outside the | |
| * file_cache_mutex, resuling in destroyed files being used | |
| * in running code */ | |
| g_object_ref (cached_file); | |
| } | |
| G_UNLOCK (file_cache_mutex); | |
| return cached_file; | |
| } | |
| gchar * | |
| thunar_file_cached_display_name (const GFile *file) | |
| { | |
| ThunarFile *cached_file; | |
| gchar *display_name; | |
| /* check if we have a ThunarFile for it in the cache (usually is the case) */ | |
| cached_file = thunar_file_cache_lookup (file); | |
| if (cached_file != NULL) | |
| { | |
| /* determine the display name of the file */ | |
| display_name = g_strdup (thunar_file_get_display_name (cached_file)); | |
| g_object_unref (cached_file); | |
| } | |
| else | |
| { | |
| /* determine something a hopefully good approximation of the display name */ | |
| display_name = thunar_g_file_get_display_name (G_FILE (file)); | |
| } | |
| return display_name; | |
| } | |
| static gint | |
| compare_app_infos (gconstpointer a, | |
| gconstpointer b) | |
| { | |
| return g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b)) ? 0 : 1; | |
| } | |
| /** | |
| * thunar_file_list_get_applications: | |
| * @file_list : a #GList of #ThunarFile<!---->s. | |
| * | |
| * Returns the #GList of #GAppInfo<!---->s that can be used to open | |
| * all #ThunarFile<!---->s in the given @file_list. | |
| * | |
| * The caller is responsible to free the returned list using something like: | |
| * <informalexample><programlisting> | |
| * g_list_free_full (list, g_object_unref); | |
| * </programlisting></informalexample> | |
| * | |
| * Return value: the list of #GAppInfo<!---->s that can be used to open all | |
| * items in the @file_list. | |
| **/ | |
| GList* | |
| thunar_file_list_get_applications (GList *file_list) | |
| { | |
| GList *applications = NULL; | |
| GList *list; | |
| GList *next; | |
| GList *ap; | |
| GList *lp; | |
| GAppInfo *default_application; | |
| const gchar *previous_type = NULL; | |
| const gchar *current_type; | |
| /* determine the set of applications that can open all files */ | |
| for (lp = file_list; lp != NULL; lp = lp->next) | |
| { | |
| current_type = thunar_file_get_content_type (lp->data); | |
| /* no need to check anything if this file has the same mimetype as the previous file */ | |
| if (current_type != NULL && previous_type != NULL) | |
| if (G_LIKELY (g_content_type_equals (previous_type, current_type))) | |
| continue; | |
| /* store the previous type */ | |
| previous_type = current_type; | |
| /* determine the list of applications that can open this file */ | |
| if (G_UNLIKELY (current_type != NULL)) | |
| { | |
| list = g_app_info_get_all_for_type (current_type); | |
| /* move any default application in front of the list */ | |
| default_application = g_app_info_get_default_for_type (current_type, FALSE); | |
| if (G_LIKELY (default_application != NULL)) | |
| { | |
| for (ap = list; ap != NULL; ap = ap->next) | |
| { | |
| if (g_app_info_equal (ap->data, default_application)) | |
| { | |
| g_object_unref (ap->data); | |
| list = g_list_delete_link (list, ap); | |
| break; | |
| } | |
| } | |
| list = g_list_prepend (list, default_application); | |
| } | |
| } | |
| else | |
| list = NULL; | |
| if (G_UNLIKELY (applications == NULL)) | |
| { | |
| /* first file, so just use the applications list */ | |
| applications = list; | |
| } | |
| else | |
| { | |
| /* keep only the applications that are also present in list */ | |
| for (ap = applications; ap != NULL; ap = next) | |
| { | |
| /* grab a pointer on the next application */ | |
| next = ap->next; | |
| /* check if the application is present in list */ | |
| if (g_list_find_custom (list, ap->data, compare_app_infos) == NULL) | |
| { | |
| /* drop our reference on the application */ | |
| g_object_unref (G_OBJECT (ap->data)); | |
| /* drop this application from the list */ | |
| applications = g_list_delete_link (applications, ap); | |
| } | |
| } | |
| /* release the list of applications for this file */ | |
| g_list_free_full (list, g_object_unref); | |
| } | |
| /* check if the set is still not empty */ | |
| if (G_LIKELY (applications == NULL)) | |
| break; | |
| } | |
| /* remove hidden applications */ | |
| for (ap = applications; ap != NULL; ap = next) | |
| { | |
| /* grab a pointer on the next application */ | |
| next = ap->next; | |
| if (!thunar_g_app_info_should_show (ap->data)) | |
| { | |
| /* drop our reference on the application */ | |
| g_object_unref (G_OBJECT (ap->data)); | |
| /* drop this application from the list */ | |
| applications = g_list_delete_link (applications, ap); | |
| } | |
| } | |
| return applications; | |
| } | |
| /** | |
| * thunar_file_list_to_thunar_g_file_list: | |
| * @file_list : a #GList of #ThunarFile<!---->s. | |
| * | |
| * Transforms the @file_list to a #GList of #GFile<!---->s for | |
| * the #ThunarFile<!---->s contained within @file_list. | |
| * | |
| * The caller is responsible to free the returned list using | |
| * thunar_g_file_list_free() when no longer needed. | |
| * | |
| * Return value: the list of #GFile<!---->s for @file_list. | |
| **/ | |
| GList* | |
| thunar_file_list_to_thunar_g_file_list (GList *file_list) | |
| { | |
| GList *list = NULL; | |
| GList *lp; | |
| for (lp = g_list_last (file_list); lp != NULL; lp = lp->prev) | |
| list = g_list_prepend (list, g_object_ref (THUNAR_FILE (lp->data)->gfile)); | |
| return list; | |
| } |